美文网首页Spring Cloud
Spring Cloud Zuul 分析(三)之ZuulFilt

Spring Cloud Zuul 分析(三)之ZuulFilt

作者: Blog | 来源:发表于2021-03-14 12:36 被阅读0次

    ZuulFilter的两种初始化过程我们在前面已经分析过,这一节我们也直奔主题,讲讲ZuulFilter初始化之后的调用过程,看看整个调用过程中Zuul是如何处理的,都经过了哪些步骤?下面我们就以routing filters执行过程进行分析!


    ZuulServletFilter

    public class ZuulServletFilter implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            try {
                ......
                try {
                    routing();
                } catch (ZuulException e) {
                    error(e);
                    postRouting();
                    return;
                }
                ......
            } catch (Throwable e) {
                error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
            } finally {
                RequestContext.getCurrentContext().unset();
            }
        }
        //routing filters执行阶段,请求下游服务在此阶段执行
        void routing() throws ZuulException {
            zuulRunner.route();
        }
    }
    

    ZuulRunner

    public class ZuulRunner {
        ......
        //初始化,默认直接使用HttpServletRequest输入流,输出则使用HttpServletResponseWrapper包装流
        public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
            RequestContext ctx = RequestContext.getCurrentContext();
            if (bufferRequests) {
                ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
            } else {
                ctx.setRequest(servletRequest);
            }
            ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
        }
        ......
        public void route() throws ZuulException {
            FilterProcessor.getInstance().route();
        }
        ......
    }
    

    ZuulRunner这个对象类中我们看见了有HttpServletRequest、HttpServletResponse、HttpServletRequestWrapper、HttpServletResponseWrapper四种类型
    HttpServletRequest中参数是无法修改的,HttpServletResponse中输出流是无法读取的,但是往往我们有很多需求是需要进行请求参数的修改及响应输出流读取的,这个时候就需要我们使用HttpServletRequestWrapper、HttpServletResponseWrapper这两个包装类,
    HttpServletRequestWrapper作为HttpServletRequest的包装类,主要职责就是可以替换HttpServletRequest中的参数
    HttpServletResponseWrapper作为HttpServletResponse的包装类,主要职责就是可以读取HttpServletResponse中的输出流!


    FilterProcessor#route()

    public class FilterProcessor {
        ......
        public void route() throws ZuulException {
            try {
                runFilters("route");
            } catch (ZuulException e) {
                throw e;
            } catch (Throwable e) {
                throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
            }
        }
        ......
        public Object runFilters(String sType) throws Throwable {
            if (RequestContext.getCurrentContext().debugRouting()) {
                Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
            }
            boolean bResult = false;
            //根据filter类型获取所有相关类型的ZuulFilter
            List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
            if (list != null) {
                for (int i = 0; i < list.size(); i++) {
                    ZuulFilter zuulFilter = list.get(i);
                    //执行ZuulFilter#runFilter()调用IZuulFilter#run()方法
                    Object result = processZuulFilter(zuulFilter);
                    if (result != null && result instanceof Boolean) {
                        bResult |= ((Boolean) result);
                    }
                }
            }
            return bResult;
        }
        ......
    }
    

    从片段中我们也比较清晰的得知调用过程为:
    1.根据filter类型获取所有相关类型的ZuulFilter
    2.执行ZuulFilter的run()方法
    那么我们看看如何获取List<ZuulFilter>这个集合对象的。


    FilterLoader#getFiltersByType()

    public class FilterLoader {
        ......
        public List<ZuulFilter> getFiltersByType(String filterType) {
            //如果hashFiltersByType缓存对象中已经有当前filterType类型的ZuulFilter集合则使用缓存
            List<ZuulFilter> list = hashFiltersByType.get(filterType);
            if (list != null) return list;
            list = new ArrayList<ZuulFilter>();
            //获取全局对象filterRegistry中保存的所有ZuulFilter
            Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
            //添加到list对象中
            for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
                ZuulFilter filter = iterator.next();
                if (filter.filterType().equals(filterType)) {
                    list.add(filter);
                }
            }
            //根据filterOrder优先级排序,默认从小到大
            Collections.sort(list);
            //添加到hashFiltersByType对象中
            hashFiltersByType.putIfAbsent(filterType, list);
            return list;
        }
        ......
    }
    

    通过filterType类型获取对应的List<ZuulFilter>集合对象,其中笔者刚开始以为List<ZuulFilter> list = hashFiltersByType.get(filterType);这个地方有问题,因为单独看这个地方会有一个问题,就是这个地方获取出来如果有数据,那么就直接返回了,那比如我动态增加了一个Groovy文件并且类型相同的ZuulFilter,那这个地方看起来好想是有问题的。
    所以带着疑问,我去仔细看了看初始化Groovy文件并初始化为ZuulFilter对象后添加到全局filterRegistry的过程,在FilterLoader#putFilter()这个方法中,有两行非常重要的代码,List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
    hashFiltersByType.remove(filter.filterType());这里如果有相同类型的,那么就直接就删除了filterType对应的List<ZuulFilter>集合数据,在后面重新赋值,所以疑问迎刃而解!


    FilterProcessor#processZuulFilter()

    public class FilterProcessor {
        ......
        //执行ZuulFilter的run()逻辑
        public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
            ......
            try {
                ......
                //重点:执行ZuulFilter#runFilter()内部会调用run()方法
                ZuulFilterResult result = filter.runFilter();
                ExecutionStatus s = result.getStatus();
                execTime = System.currentTimeMillis() - ltime;
                //失败、成功的逻辑
                switch (s) {
                    case FAILED:
                        t = result.getException();
                        ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                        break;
                    case SUCCESS:
                        o = result.getResult();
                        ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                        if (bDebug) {
                            Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                            Debug.compareContextState(filterName, copy);
                        }
                        break;
                    default:
                        break;
                }
                if (t != null) throw t;
                usageNotifier.notify(filter, s);
                return o;
            } catch (Throwable e) {
               ......
            }
        }
        ......
    }
    

    在processZuulFilter这个方法体中,会记录每个ZuulFilter的耗时时间,以及执行过程中的成功、失败、异常的状态都会通知给Netflix Servo 实现监控信息采集(JMX -Java Management Extensions标准),监控信息不过多分析(PS:开启JMX,然后使用JConsole或Visual VM进行预览)


    ZuulFilter#runFilter()

    public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
        ......
        public ZuulFilterResult runFilter() {
            ZuulFilterResult zr = new ZuulFilterResult();
            //默认false,及所有ZuulFilter都有效
            if (!isFilterDisabled()) {
                //是否满足过滤条件
                if (shouldFilter()) {
                    Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                    try {
                        //重点:实现了ZuulFilter都会重写这个方法,执行具体的业务逻辑方法
                        Object res = run();
                        zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                    } catch (Throwable e) {
                        t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                        zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                        zr.setException(e);
                    } finally {
                        t.stopAndLog();
                    }
                } else {
                    zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
                }
            }
            return zr;
        }
        ......
    }
    

    分析到这里,执行run()方法,因为我们是分析route 类型的filterType,并且是调用下游服务的ZuulFilter,所以我们最终会执行到RibbonRoutingFilter这个实现类,那这个实现类都做了什么事情呢?我们接着往下!


    RibbonRoutingFilter

    public class RibbonRoutingFilter extends ZuulFilter {
        ......
        //配置的routes路由为使用serviceId方式,非url配置方式
        @Override
        public boolean shouldFilter() {
            RequestContext ctx = RequestContext.getCurrentContext();
            return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                    && ctx.sendZuulResponse());
        }
    
        @Override
        public Object run() {
            RequestContext context = RequestContext.getCurrentContext();
            //添加忽略的头信息,zuul的ignored-headers配置,会过滤这些头信息参数
            this.helper.addIgnoredHeaders();
            try {
                //构建Ribbon命令执行的上下文,保存相关参数的对象
                RibbonCommandContext commandContext = buildCommandContext(context);
                //调用下游服务,返回结果
                ClientHttpResponse response = forward(commandContext);
                setResponse(response);
                return response;
            }
            catch (ZuulException ex) {
                throw new ZuulRuntimeException(ex);
            }
            catch (Exception ex) {
                throw new ZuulRuntimeException(ex);
            }
        }
        ......
    }
    

    这里我们简单解释下在run()方法中经过的步骤RibbonRoutingFilter(routing filters)->Hystrix->Ribbon-> Response。如果想了解routing filters->Hystrix->Ribbon-> Response整个详细过程请参阅:Spring Cloud Hystrix 分析(四)之Zuul集成

    1. 构建Ribbon命令执行的上下文,保存相关参数的对象,封装RibbonCommandContext对象(头信息、请求参数信息)
    2. 封装HttpClientRibbonCommand(RibbonCommand)对象,设置负载均衡客户端,而且内部继承HystrixExecutable,可以使用Hystrix熔断功能
    3. 执行命令,最终调用到HystrixCommand#execute()
    4. AbstractRibbonCommand#run()方法中this.client.executeWithLoadBalancer(request, config);通过负载均衡客户端进行下游服务的调用并返回结果

    文章到这里也接近尾声,本节主要讲解了route类型ZuulFilter的整个调用过程,其他类型的也基本差不多的,都是相同类型的ZuulFilter按照从小到大的优先级进行执行,如果文章对你有所帮助,就点赞关注吧!

    相关文章

      网友评论

        本文标题:Spring Cloud Zuul 分析(三)之ZuulFilt

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