SpringCloud Zuul 原理分析

目的

分析zuul运行的过程

源码分析

现象到本质的分析
从第一次请求的那个提示开始分析, 对DynamicServerListLoadBalancer 进行分析debug 找到入口
2019-08-02 15:40:26.655  INFO 5964 --- [nio-8769-exec-1] s.c.a.AnnotationConfigApplicationContext : Refreshing SpringClientFactory-service-feign: startup date [Fri Aug 02 15:40:26 CST 2019]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@ceb4bd2
2019-08-02 15:40:26.727  INFO 5964 --- [nio-8769-exec-1] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2019-08-02 15:40:26.949  INFO 5964 --- [nio-8769-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: service-feign.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-08-02 15:40:26.972  INFO 5964 --- [nio-8769-exec-1] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-service-feign
2019-08-02 15:40:26.998  INFO 5964 --- [nio-8769-exec-1] c.netflix.loadbalancer.BaseLoadBalancer  : Client: service-feign instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=service-feign,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-08-02 15:40:27.005  INFO 5964 --- [nio-8769-exec-1] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2019-08-02 15:40:27.033  INFO 5964 --- [nio-8769-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: service-feign.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-08-02 15:40:27.035  INFO 5964 --- [nio-8769-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client service-feign initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=service-feign,current list of Servers=[peer1:8765],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:1;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:peer1:8765;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@2cb46fd5
2019-08-02 15:40:28.290  INFO 5964 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: service-feign.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

debug分析到调用链

其实zuul思路就简单了ServletWrappingController将请求交给ZuulServlet
ZuulServlet集成了HttpServlet 实现了自己的service方法从而接管spring自己的spring mvc service 内部的业务逻辑
所以重点分析ZuulServlet就可以了 (spring boot auto configuration 的就不分析了)

service 方法分为几个阶段 post,route,pre,error ; ZuulFilter 里面的属性filterType定义的类型;
public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    
    //内部维护的是FilterProcessor.getInstance() 一个实例对象进行方法的调用
    private ZuulRunner zuulRunner;


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                //可以理解为拦截器要preinteceptor执行的业务
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //进行方法的调用的地方,重点关注这个地方
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
}  



class FilterProcessor{
    
   public Object runFilters(String sType) throws Throwable {
       if (RequestContext.getCurrentContext().debugRouting()) {
           Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
       }
       boolean bResult = false;
       //获取过滤器的集合
       //pre [ServletDetecationFilter,Servlet30WrapperFilter,FormBodyWrapperFilter,MyFilter,DebugFilter,
       // PreDecoreationFilter] 等等一系系列的过滤器
       
       //route [RibbonRoutingFilter, SimpleHostRoutingFilter,SendForwardFilter]
       
       //每个过滤器会根据当前获取器定义的filterType来决定这个过滤器在那个过程执行
       //例如MyFilter 设置的filterType设置了为“pre” 那么会在ZuulServlet.service()方法里面的pre对应的方法里面进行执行;
       
       List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
       if (list != null) {
           for (int i = 0; i < list.size(); i++) {
               ZuulFilter zuulFilter = list.get(i);
               //不同的filter开始调度执行run方法
               Object result = processZuulFilter(zuulFilter);
               if (result != null && result instanceof Boolean) {
                   bResult |= ((Boolean) result);
               }
           }
       }
       return bResult;
   } 
}

//如果pre = route , filter 为RibbonRoutingFilter的时候会去调用我们的目标方法
class RibbonRoutingFilter extends ZuulFilter {
    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders();
        try {
            //创建CommandContext
            RibbonCommandContext commandContext = buildCommandContext(context);
            //调用forward执行请求
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        catch (ZuulException ex) {
            throw new ZuulRuntimeException(ex);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
    }
    
    protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
        Map<String, Object> info = this.helper.debug(context.getMethod(),
                context.getUri(), context.getHeaders(), context.getParams(),
                context.getRequestEntity());

        //执行create的时候道理和fein是一样的, 由于上线文没有创建, 所以需要去调用NamedContextFactory创建context环境实例
        RibbonCommand command = this.ribbonCommandFactory.create(context);
        try {
            //通过RibbonCommond执行请求
            ClientHttpResponse response = command.execute();
            this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
            return response;
        }
        catch (HystrixRuntimeException ex) {
            return handleException(info, ex);
        }

    }
}
zuul:
  routes:
    api-a:
      path: /api-a/**
      serviceId: service-ribbon
    api-b:
      path: /api-b/**
      serviceId: service-feign
通过测试发现zull是远程调用的,也就是跨服务调用的, 通过ribbon进行客户端的接口的调用;

请求的链:
org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#forward
com.netflix.hystrix.AbstractCommand#toObservable
com.netflix.hystrix.HystrixCommand#getExecutionObservable
org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand#run
com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)
org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient#execute
org.apache.http.impl.client.CloseableHttpClient#execute(org.apache.http.client.methods.HttpUriRequest)
org.apache.http.impl.client.CloseableHttpClient#determineTarget


回过头思考为什么请求会到达ZuulServlet呢?
结果debug发现
@EnableZuulProxy这是一个标记型的注解,会在spring的上下文注入一个zuulProxyMarkerBean, 
autoConfiguration会更加是否有这个标记从而决定某些bean是否会进行自动的配置加载;
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
//如果没有开启@EnableZuulProxy那么就不加载这个类, 说明zuul接管spring mvc默认的请求就是在这个类进行了配置
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {

	@Autowired
	protected ZuulProperties zuulProperties;

	@Autowired
	protected ServerProperties server;

	@Autowired(required = false)
	private ErrorController errorController;

	@Bean
	public HasFeatures zuulFeature() {
		return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
	}

	@Bean
	@Primary
	public CompositeRouteLocator primaryRouteLocator(
			Collection<RouteLocator> routeLocators) {
		return new CompositeRouteLocator(routeLocators);
	}

	@Bean
	@ConditionalOnMissingBean(SimpleRouteLocator.class)
	public SimpleRouteLocator simpleRouteLocator() {
		return new SimpleRouteLocator(this.server.getServlet().getServletPrefix(),
				this.zuulProperties);
	}

    //zuul 控制器
	@Bean
	public ZuulController zuulController() {
		return new ZuulController();
	}

    //请求mapping映射器, 将请求经过DispacherServlet.doDispach映射到ZuulController进行处理
    //mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
	@Bean
	public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
		ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
		mapping.setErrorController(this.errorController);
		return mapping;
	}

	@Bean
	public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
		return new ZuulRefreshListener();
	}
}

总结

zuul智能路由是通过ZuulServlet接管了HttpServlet的请求[zuul 注入了zuulController,以及自己的zuulHandlerMapping],
实现了自己的service方法制定自己的业务逻辑, 经过不同的Filter (ZuulFilter的实现类)
这些过滤器会在post,route,pre,error 等好几个阶段来在目标方法的前后进行执行;
最终是通过HystrixCommand来执行请求的发送获取结果;