美文网首页
Spring Cloud 学习(20) --- Zuul(二)

Spring Cloud 学习(20) --- Zuul(二)

作者: laiyy0728 | 来源:发表于2019-04-10 16:14 被阅读0次

    Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,能够在进行 HTTP 请求或响应的时候执行相关操作。

    Zuul Filter

    Zuul Filter 的特点

    • Filter 类型:Filter 类型决定了当前的 Filter 在整个 Filter 链中的执行顺序。
    • Filter 执行顺序:同一种类型的 Filter 通过 filterOrder() 来设置执行顺序
    • Filter 执行条件:Filter 执行所需的标准、条件
    • Filter 执行效果:符合某个条件,产生的执行结果

    Zuul 内部提供了一个动态读取、编译、运行这些 Filter 的机制。Filter 之间不直接通信,在请求线程中会通过 RequestContext 共享状态,内部使用 ThreadLocal 实现,也可以在 Filter 之间使用 ThreadLocal 收集自己需要的状态、数据

    Zuul Filter 的执行逻辑源码在 com.netflix.zuul.http.ZuulServlet

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();        // 通过 RequestContext 获取共享状态
            context.setZuulEngineRan();
    
            try {
                this.preRoute();        // 执行请求之前的操作
            } catch (ZuulException var13) {
                this.error(var13);      // 出现错误的操作
                this.postRoute();
                return;
            }
    
            try {
                this.route();       // 路由操作
            } catch (ZuulException var12) {
                this.error(var12);
                this.postRoute();
                return;
            }
    
            try {
                this.postRoute();       // 请求操作
            } catch (ZuulException var11) {
                this.error(var11);
            }
        } catch (Throwable var14) {
            this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
    
    

    Zuul 生命周期

    Zuul 官方文档中,生命周期图。


    Zuul Life Cycle

    但官方文档的生命周期图不太准确。

    • 在 postRoute 执行之前,即 postFilter 执行之前,如果没有出现过错误,会调用 error 方法,并调用 this.error(new ZuulException) 打印堆栈信息
    • 在 postRoute 执行之前就已经报错,会调用 error 方法,再调用 postRoute,但是之后会直接 return,不会调用 this.error(new ZuulException) 打印堆栈信息

    由此可以看出,整个 Filter 调用链的重点可能是 postFilter 也可能是 errorFilter

    Zuul Life Cycle

    pre、route 出现错误后,进入 error,再进入 post,再返回
    pre、route 没有出现错误,进入 post,如果出现错误,再进入 error,再返回

    • pre:在 Zuul 按照规则路由到下级服务之前执行。如果需要对请求进行预处理,如:鉴权、限流等,都需要在此 Filter 实现
    • route:Zuul 路由动作的执行者,是 Http Client、Ribbon 构建和发送原始 HTTP 请求的地方
    • post:源服务返回结果或异常信息发生后执行,如果需要对返回值信息做处理,需要实现此类 Filter
    • error:整个生命周期发生异常,都会进入 error Filter,可做全局异常处理。

    Filter 之间,通过 com.netflix.zuul.context.RequestContext 类进行通信,内部采用 ThreadLocal 保存每个请求的一些信息,包括:请求路由、错误信息、HttpServletRequest、HTTPServletResponse,扩展了 ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息


    Zuul 原生 Filter

    整合 spring-boot-starter-actuator 后,查看 idea 控制台 endpoints 栏的 mappings,可以看到多了几个 Actuator 端点

    routes 端点

    访问 http://localhost:8989/actuator/routes 可以查看当前 zuul server 映射了几个路径、服务

    {
        "/provider/**": "spring-cloud-provider-service-simple",
        "/spring-cloud-provider-service-simple/**": "spring-cloud-provider-service-simple"
    }
    

    访问 http://localhost:8989/actuator/routes/details 可以查看具体的映射信息

    {
        "/provider/**": {
            "id": "spring-cloud-provider-service-simple",       // serviceId
            "fullPath": "/provider/**",     // 映射 path
            "location": "spring-cloud-provider-service-simple",     // 服务名称,实际上也是 serviceId
            "path": "/**",      // 实际访问路径 
            "prefix": "/provider",      // 访问前缀
            "retryable": false,     // 是否开启重试
            "customSensitiveHeaders": false,        // 是否自定义了敏感 header
            "prefixStripped": true      // 是否去掉前缀(如果为 false,则实际访问时需要加 前缀,且实际请求的访问路径也会加上前缀)
        },
        "/spring-cloud-provider-service-simple/**": {
            "id": "spring-cloud-provider-service-simple",
            "fullPath": "/spring-cloud-provider-service-simple/**",
            "location": "spring-cloud-provider-service-simple",
            "path": "/**",
            "prefix": "/spring-cloud-provider-service-simple",
            "retryable": false,
            "customSensitiveHeaders": false,
            "prefixStripped": true
        }
    }
    

    filters 端点

    访问 http://localhost:8989/actuator/filters ,返回当前 zuul 的所有 filters

    Zuul Filters

    内置 Filters

    名称 类型 顺序 描述
    ServletDetectionFilter pre -3 通过 Spring Dispatcher 检查请求是否通过
    Servlet30WrapperFilter pre -2 适配 HttpServletRequest 为 Servlet30RequestWrapper 对象
    FormBodyWrapperFilter pre -1 解析表单数据,并为下游请求进行重新编码
    DebugFilter pre 1 Debug 路由标识
    PreDecorationFilter pre 5 处理请求上下文供后续使用,设置下游相关头信息
    RibbonRoutingFilter route 10 使用 Ribbon、Hystrix、嵌入式 HTTP 客户端发送请求
    SimpleHostRoutingFilter route 100 使用 Apache Httpclient 发送请求
    SendForwardFilter route 500 使用 Servlet 转发请求
    SendResponseFilter post 1000 将代理请求的响应写入当前响应
    SendErrorFilter error 0 如果 RequestContext.getThrowable() 不为空,则转发到 error.path 哦诶之的路径

    如果使用 @EnableZuulServer 注解,将减少 PreDecorationFilterRibbonRoutingFilterSimpleHostRoutingFilter

    如果要替换到某个原生的 Filter,可以自实现一个和原生 Filter 名称、类型一样的 Filter,并替换。或者禁用掉某个filter,并自实现一个新的。
    禁用语法: zuul.{SimpleClassName}.{filterType}.disable=true,如 zuul.SendErrorFilter.error.disable=true


    多级业务处理

    在 Zuul Filter 链体系中,可以把一组业务逻辑细分,然后封装到一个个紧密结合的 Filter,设置处理顺序,组成一组 Filter 链。

    自定义实现 Filter

    在 Zuul 中实现自定义 Filter,继承 ZuulFilter 类即可,ZuulFilter 是一个抽象类,需要实现以下几个方法

    • String filterType:使用返回值设定 Filter 类型,可以设置为 prerouteposterror
    • int filterOrder:使用返回值设置 Filter 执行次序
    • boolean shouldFilter:使用返回值设定该 Filter 是否执行,可以作为开关来使用
    • Object run:Filter 的核心执行逻辑
    // 自定义 ZuulFilter
    public class FirstPreFilter extends ZuulFilter {
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            System.out.println("自定义 Filter,类型为 pre!");
            return null;
        }
    }
    
    
    // 注入 Spring 容器
    @Bean
    public FirstPreFilter firstPreFilter(){
        return new FirstPreFilter();
    }
    

    此时访问 http://localhost:8989/provider/get-result ,查看控制台:

    Initializing Servlet 'dispatcherServlet'
    Completed initialization in 0 ms
    自定义 Filter,类型为 pre!
    Flipping property: spring-cloud-provider-service-simple.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    Shutdown hook installed for: NFLoadBalancer-PingTimer-spring-cloud-provider-service-simple
    

    业务处理

    使用 SecondFilter 验证是否传入参数 a,ThirdPreFilter 验证是否传入参数 b,在 PostFilter 统一处理返回内容。

    SecondPreFilter

    public class SecondPreFilter extends ZuulFilter {
        private static final Logger LOGGER = LoggerFactory.getLogger(SecondPreFilter.class);
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 2;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            LOGGER.info(">>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<");
            // 获取上下文
            RequestContext requestContext = RequestContext.getCurrentContext();
            // 从上下文获取 request
            HttpServletRequest request = requestContext.getRequest();
            // 从 request 获取参数 a
            String a = request.getParameter("a");
            // 如果参数 a 为空
            if (StringUtils.isBlank(a)) {
                LOGGER.info(">>>>>>>>>>>>>>>> 参数 a 为空! <<<<<<<<<<<<<<<<");
                // 禁止路由,禁止访问下游服务
                requestContext.setSendZuulResponse(false);
                // 设置 responseBody,供 postFilter 使用
                requestContext.setResponseBody("{\"status\": 500, \"message\": \"参数 a 为空!\"}");
                // 用于下游 Filter 判断是否执行
                requestContext.set("logic-is-success", false);
                // Filter 结束
                return null;
            }
            requestContext.set("logic-is-success", true);
            return null;
        }
    }
    

    ThirdPreFilter

    public class ThirdPreFilter extends ZuulFilter {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(ThirdPreFilter.class);
    
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 3;
        }
    
        @Override
        public boolean shouldFilter() {
            RequestContext context = RequestContext.getCurrentContext();
            // 获取上下文中的 logic-is-success 中的值,用于判断当前 filter 是否执行
            return (boolean) context.get("logic-is-success");
        }
    
        @Override
        public Object run() throws ZuulException {
            LOGGER.info(">>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<");
            // 获取上下文
            RequestContext requestContext = RequestContext.getCurrentContext();
            // 从上下文获取 request
            HttpServletRequest request = requestContext.getRequest();
            // 从 request 获取参数 a
            String a = request.getParameter("b");
            // 如果参数 a 为空
            if (StringUtils.isBlank(a)) {
                LOGGER.info(">>>>>>>>>>>>>>>> 参数 b 为空! <<<<<<<<<<<<<<<<");
                // 禁止路由,禁止访问下游服务
                requestContext.setSendZuulResponse(false);
                // 设置 responseBody,供 postFilter 使用
                requestContext.setResponseBody("{\"status\": 500, \"message\": \"参数 b 为空!\"}");
                // 用于下游 Filter 判断是否执行
                requestContext.set("logic-is-success", false);
                // Filter 结束
                return null;
            }
            requestContext.set("logic-is-success", true);
            return null;
        }
    }
    

    PostFilter

    
    public class PostFilter extends ZuulFilter {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(PostFilter.class);
    
        @Override
        public String filterType() {
            return FilterConstants.POST_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            LOGGER.info(">>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<");
            RequestContext context = RequestContext.getCurrentContext();
            // 处理返回中文乱码
            context.getResponse().setCharacterEncoding("UTF-8");
            // 获取上下文保存的 responseBody
            String responseBody = context.getResponseBody();
            // 如果 responseBody 不为空,则证明流程中有异常发生
            if (StringUtils.isNotBlank(responseBody)) {
                // 设置返回状态码
                context.setResponseStatusCode(500);
                // 替换响应报文
                context.setResponseBody(responseBody);
            }
            return null;
        }
    }
    

    访问 http://localhost:8989/provider/addhttp://localhost:8989/provider/add?a=1http://localhost:8989/provider/add?a=1&b=1 ,查看控制台

    控制台:

    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>>>>> 参数 a 为空! <<<<<<<<<<<<<<<<
    2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.PostFilter              : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
    
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.ThirdPreFilter          : >>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.ThirdPreFilter          : >>>>>>>>>>>>>>>> 参数 b 为空! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:13.005  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.PostFilter              : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
    
    2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
    2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.SecondPreFilter         : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.ThirdPreFilter          : >>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<
    2019-02-18 14:10:28.500  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.PostFilter              : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
    

    返回值:

    {"status": 500, "message": "参数 a 为空!"}
    
    {"status": 500, "message": "参数 b 为空!"}
    
    result is : a + b = 2
    

    由此验证自定义 Zuul Filter 成功。

    相关文章

      网友评论

          本文标题:Spring Cloud 学习(20) --- Zuul(二)

          本文链接:https://www.haomeiwen.com/subject/brcoiqtx.html