一篇掌握SpringCloud之服务网关zuul

导读:本篇文章讲解 一篇掌握SpringCloud之服务网关zuul,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Zuul基础

1.概述

通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的:
在这里插入图片描述
前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,似乎一个微服务框架已经完成了。

服务网关gateway 是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由均衡负载功能之外,它还具备了权限控制(鉴权)等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

2.ZUUL概述

官网:https://github.com/Netflix/zuul
在这里插入图片描述
Zuul:维基百科:
电影《捉鬼敢死队》中的怪兽,Zuul,在纽约引发了巨大骚乱。
事实上,在微服务架构中,Zuul就是守门的大Boss!一夫当关,万夫莫开!!

Spring Cloud Zuul路由是微服务架构的不可或缺的一部分,提供动态路由,监控,弹性,安全等的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。

zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。

Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。

zuul的例子可以参考netflix在github上的 simple webapp,可以按照netflix 在github wiki 上文档说明来进行使用。

在这里插入图片描述

在Spring Cloud体系中, Spring Cloud Zuul就是提供负载均衡、反向代理、权限认证的一个API gateway。

3.Zuul加入后的架构

在这里插入图片描述
在这里插入图片描述

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现鉴权、动态路由等等操作。Zuul就是我们服务的统一入口

4.快速入门

4.1.新建工程

填写基本信息
在这里插入图片描述
添加Zuul依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.bruceliu.springcloud.zuul</groupId>
    <artifactId>springcloud_zuul</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>

        <!--导入Zuul的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4.2.编写启动类

通过@EnableZuulProxy 注解开启Zuul的功能:

/**
 * @author bruceliu
 * @create 2019-08-05 21:52
 * @description
 */
@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
public class ZuulDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulDemoApplication.class, args);
    }
}

4.3.编写配置

server:
  port: 10010 #服务端口
spring:
  application:
    name: api-gateway #指定服务名

4.4.编写路由规则

我们需要用Zuul来代理user-service服务,先看一下控制面板中的服务状态:
在这里插入图片描述

ip为:127.0.0.1
端口为:8001

映射规则:

zuul:
  routes:
    user-service: # 这里是路由id,随意写
      path: /user-service/** # 这里是映射路径
      url: http://127.0.0.1:8001 # 映射路径对应的实际url地址

我们将符合path 规则的一切请求,都代理到 url参数指定的地址
本例中,我们将 /user-service/**开头的请求,代理到http://127.0.0.1:8001

4.5.启动测试

访问的路径中需要加上配置规则的映射路径,我们访问:http://127.0.0.1:10010/user-service/get/1
在这里插入图片描述

4.6.面向服务的路由

在刚才的路由规则中,我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。
我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!

4.6.1.添加Eureka客户端依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

4.6.2.开启Eureka客户端发现功能

/**
 * @author bruceliu
 * @create 2019-08-05 21:52
 * @description
 */
@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
@EnableDiscoveryClient
public class ZuulDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulDemoApplication.class, args);
    }
}

4.6.3.添加Eureka配置,获取服务信息

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

4.6.4.修改映射配置,通过服务名称获取

因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。

zuul:
  routes:
    user-service: # 这里是路由id,随意写
      path: /user-service/** # 这里是映射路径
      serviceId: springcloud-demo-service # 指定服务名称

4.6.5.启动测试

再次启动,这次Zuul进行代理时,会利用Ribbon进行负载均衡访问:
在这里插入图片描述
日志中可以看到使用了负载均衡器:
在这里插入图片描述

Zuul过滤器

我们了解了 Spring Cloud Zuul 作为网关所具备的最基本功能:路由(Router),下面我们将关注 Spring Cloud Zuul 的另一核心功能:过滤器(Filter)
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP,本文我们就来具体介绍下自定义的zuul过滤器

1.Filter的使用场景

场景非常多:

请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了

异常处理:一般会在error类型和post类型过滤器中结合来处理。

服务调用时长统计:pre和post结合使用。

2.IZuulFilter接口

ZuulFilter是过滤器的顶级父类。
在这里插入图片描述
在这里我们看一下其中定义的4个最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();  //过滤器的类型  4种

    abstract public int filterOrder();
    
    boolean shouldFilter();// 来自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

网关过滤器的自定义方法有四个,过滤器的类型有四个,分别如下:
在这里插入图片描述

3.Filter 的生命周期

这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。

Filter 的生命周期有 4 个,分别是 “PRE”、“ROUTING”、“POST” 和“ERROR”,整个生命周期可以用下图来表示
在这里插入图片描述
Zuul 大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。

PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。

POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul 还允许我们创建自定义的过滤器类型。例如,我们可以定制一种 STATIC 类型的过滤器,直接在 Zuul 中生成响应,而不将请求转发到后端的微服务。
  • 正常流程:
    • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

在这里插入图片描述

所有内置过滤器列表:
在这里插入图片描述Zuul中默认实现的Filter执行顺序:
在这里插入图片描述如何禁用指定的Filter

可以在 application.yml 中配置需要禁用的 filter,格式为

zuul.<SimpleClassName>.<filterType>.disable=true

比如要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter就设置

zuul:
  SendResponseFilter:
    post:
      disable: true

4.自定义Filter

4.1.创建Filter

首先自定义一个 Filter,继承 ZuulFilter 抽象类,在 run() 方法中添加具体业务逻辑,具体如下:

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class LogFilter extends ZuulFilter{

    private Logger logger = LoggerFactory.getLogger(LogFilter.class);

    /**
     * 过滤方法
     */
    @Override
    public Object run() {
        // 获取Request上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        logger.info("LogFilter .... 请求的路径是{},请求提交的方式是{}", request.getRequestURL().toString(),request.getMethod());
        return null;
    }

    /**
     * 是否开启过滤:默认false
     */
    @Override
    public boolean shouldFilter() {
        // TODO Auto-generated method stub
        return true;
    }

    /**
     * 多个过滤器中的执行顺序,数值越小,优先级越高
     */
    @Override
    public int filterOrder() {
        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * 过滤器的类型
     */
    @Override
    public String filterType() {
        // TODO Auto-generated method stub
        return "pre";
    }

}

在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器。这四个方法分别定义了:

filterType():过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
filterOrder():过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。通过数字指定,数字越大,优先级越低。
shouldFilter():判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
run():过滤器的具体逻辑。

4.2.启动网关测试

重新启动service-api-gateway,并发起下面的请求,对上面定义的过滤器做一个验证:
启动一个provider服务后,启动我们的网关服务,然后请求访问,查询控制台输出
在这里插入图片描述
注意控制台:
在这里插入图片描述
日志有输出,说明自定义的网关过滤器执行了。

5.过滤器小案例

5.1.服务鉴权

自定义过滤器:

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;


@Component
public class MyFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(MyFilter.class);

    /**
     * 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。 这里定义为pre,代表会在请求被路由之前执行。
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * filter执行顺序,通过数字指定。 数字越大,优先级越低。
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return -1;
    }

    /**
     * 判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。 实际运用中我们可以利用该函数来指定过滤器的有效范围。
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器的具体逻辑
     *
     * @return
     */
    @Override
    public Object run() {
        //过滤器要执行的过滤操作
        // 登录校验逻辑。
        // 1)获取Zuul提供的请求上下文对象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 2) 从上下文中获取request对象
        HttpServletRequest req = ctx.getRequest();
        System.out.println("=============================================");
        System.out.println("用户的请地址是:"+req.getRequestURI());
        System.out.println("用户的所有请求参数是:");
        Map<String, String[]> map = req.getParameterMap();
        Set<Map.Entry<String, String[]>> entries = map.entrySet();
        for (Map.Entry<String, String[]> entry : entries) {
            System.out.println(entry.getKey()+"---"+ Arrays.toString(entry.getValue()));
        }
        System.out.println("=============================================");
        // 3) 从请求中获取token
        String token = req.getParameter("access-token");
        // 4) 判断
        if(token == null || "".equals(token.trim())){
            // 没有token,登录校验失败,拦截
            ctx.setSendZuulResponse(false);
            //设置信息
            ctx.setResponseBody("No Rrights!");
            // 返回401状态码。也可以考虑重定向到登录页。
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        // 校验通过,可以考虑把用户信息放入上下文,继续向后执行
        return null;
    }
}

在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器。这四个方法分别定义了:

filterType():过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
filterOrder():过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。通过数字指定,数字越大,优先级越低。
shouldFilter():判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
run():过滤器的具体逻辑。这里我们通过ctx.setSendZuulResponse(false)令 Zuul 过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回 body 内容进行编辑等。

重新启动service-api-gateway,并发起下面的请求,对上面定义的过滤器做一个验证:
访问 http://127.0.0.1:2222/springcloud-consumer/test 返回 userToken is null
在这里插入图片描述
访问 http://127.0.0.1:2222/springcloud-consumer/test?access-token=bruce

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94142.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!