Spring Cloud Zuul 初始化源码深度解析

烈酒焚心 提交于 2020-03-08 22:16:16

概述

  在微服务场景的开发下,网关的重要性不言而喻。Zuul是Netflix开源的微服务网关,Spring Cloud zuul是spring对Zuul进行的整合与增强。本文主要从源码角度对其初始化的过程。

主要包含以下内容

  1. @EnableZuulProxy和@EnableZuulServer的区别
  2. 路由配置 ZuulProperties
  3. 路由定位器 RouteLocator
  4. 与spring mvc的集成 ZuulControler和ZuulHandlerMapping 
  5. Zuul的饥饿加载 zuul.ribbon.eager-load.enabled 
  6. zuul的事件监听机制,动态路由的基石。ZuulRefreshListener
  7. Filter初始化
  8. Zuul初始化总结

 

1.@EnableZuulProxy和@EnableZuulServer的区别

 在我们使用Spring Cloud Zuul通常是在启动类上添加@EnableZuulProxy注解或@EnableZuulServer。我们查看一下俩个注解的源码

@EnableZuulProxy    

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {

} 

@EnableZuulServer

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ZuulServerMarkerConfiguration.class)
public @interface EnableZuulServer {

}

@EnableZuulProxy是个复合注解,其引入了@EnableCircuitBreaker,整合了Hystrix,而@EnableZuulServer并没有。除此之外俩个注解还导入了不同的配置类,@EnableZuulProxy导入了ZuulProxyMarkerConfiguration,而EnableZuulServer导入了ZuulServerMarkerConfiguration。

我们再次查看这俩个配置类的源码,找出其区别。

ZuulProxyMarkerConfiguration

/**
 * Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
 * forward requests to backend servers. The backends can be registered manually through
 * configuration or via DiscoveryClient.
 *
 * @see EnableZuulServer for how to get a Zuul server without any proxying
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {

}

 ZuulServerMarkerConfiguration

/**
 * Responsible for adding in a marker bean to trigger activation of
 * {@link ZuulServerAutoConfiguration}.
 *
 * @author Biju Kunjummen
 */

@Configuration(proxyBeanMethods = false)
public class ZuulServerMarkerConfiguration {

    @Bean
    public Marker zuulServerMarkerBean() {
        return new Marker();
    }

    class Marker {

    }

}

从源码可知ZuulProxyMarkerConfiguration指向了ZuulProxyAutoConfiguration, ZuulServerMarkerConfiguration指向了ZuulServerAutoConfiguration。

ZuulProxyAutoConfiguration源码如下,从源码中可以看出ZuulProxyAutoConfiguration的源码发现其继承了ZuulServerAutoConfiguration,并且主要新增了如下bean

  1.DiscoveryClientRouteLocator

    主要有两个功能,第一是从DiscoveryClient(如Eureka)发现路由信息,第二个是动态刷新路由信息

  2.多了3个Filter

  PreDecorationFilter  为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据

  RibbonRoutingFilter 通过Ribbon和Hystrix来向服务实例发起请

  SimpleHostRoutingFilter 主要用来转发serviceId为空的,即直接使用httpclient来转发请求的

 RoutesEndpoint

  在同时引入Spring boot actuator时回新增一个routes端点,可以通过/routes查询具体的路由信息。

@Configuration(proxyBeanMethods = false)
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
        HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

    @SuppressWarnings("rawtypes")
    @Autowired(required = false)
    private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();

    @Autowired(required = false)
    private Registration registration;

    @Autowired
    private DiscoveryClient discovery;

    @Autowired
    private ServiceRouteMapper serviceRouteMapper;

    @Override
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Discovery)",
                ZuulProxyAutoConfiguration.class);
    }

    @Bean
    @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
    public DiscoveryClientRouteLocator discoveryRouteLocator() {
        return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(),
                this.discovery, this.zuulProperties, this.serviceRouteMapper,
                this.registration);
    }

    // pre filters
    @Bean
    @ConditionalOnMissingBean(PreDecorationFilter.class)
    public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
            ProxyRequestHelper proxyRequestHelper) {
        return new PreDecorationFilter(routeLocator,
                this.server.getServlet().getContextPath(), this.zuulProperties,
                proxyRequestHelper);
    }

    // route filters
    @Bean
    @ConditionalOnMissingBean(RibbonRoutingFilter.class)
    public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
            RibbonCommandFactory<?> ribbonCommandFactory) {
        RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                this.requestCustomizers);
        return filter;
    }

    @Bean
    @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
            CloseableHttpClient.class })
    public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
            ZuulProperties zuulProperties,
            ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
            ApacheHttpClientFactory httpClientFactory) {
        return new SimpleHostRoutingFilter(helper, zuulProperties,
                connectionManagerFactory, httpClientFactory);
    }

    @Bean
    @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
    public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
            ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
        return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
    }

    @Bean
    @ConditionalOnMissingBean(ServiceRouteMapper.class)
    public ServiceRouteMapper serviceRouteMapper() {
        return new SimpleServiceRouteMapper();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.springframework.boot.actuate.health.Health")
    protected static class NoActuatorConfiguration {

        @Bean
        public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
            ProxyRequestHelper helper = new ProxyRequestHelper(zuulProperties);
            return helper;
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Health.class)
    protected static class EndpointConfiguration {

        @Autowired(required = false)
        private HttpTraceRepository traces;

        @Bean
        @ConditionalOnEnabledEndpoint
        public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {
            return new RoutesEndpoint(routeLocator);
        }

        @ConditionalOnEnabledEndpoint
        @Bean
        public FiltersEndpoint filtersEndpoint() {
            FilterRegistry filterRegistry = FilterRegistry.instance();
            return new FiltersEndpoint(filterRegistry);
        }

        @Bean
        public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
            TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);
            if (this.traces != null) {
                helper.setTraces(this.traces);
            }
            return helper;
        }

    }

}

总结

@EnableZuulProxy是@EnableZuulServer的增强,主要有以下增加

  1. 和服务发现的集成,当有新服务注册到服务中心时可以动态添加新服务的路由。
  2. 和Ribon和Hystrix的集成。拥有了负载均衡和熔断的功能。
  3. 和SpringBootActuator的集成,新增routers端点,可以通过请求/routes路径,获取路由的信息。

2.路由配置 ZuulProperties

 ZuulServerAutoConfiguration里定义了zuul的基础配置,查看其源码发现首先注入了ZuulProperties。

 

 

从其名称可以看出其为定义了Zuul的配置属性信息。Zuul的具体有哪些配置,读者可以自行查看ZuulProperties源码。在ZuulProperties里还有一个内部类Router定义了一个路由的相关配置。

3.路由定位器 RouteLocator

Zuul通过RouteLocator获取路由规则,其类关系如下

 

  • SimpleRouteLocator:主要加载配置文件的路由规则
  • DiscoveryClientRouteLocator:服务发现的路由定位器,去注册中心如Eureka,consul等拿到服务名称,以这样的方式/服务名称/**映射成路由规则
  • CompositeRouteLocator:复合路由定位器,主要集成所有的路由定位器(如配置文件路由定位器,服务发现定位器,自定义路由定位器等)来路由定位。
  • RefreshableRouteLocator:路由刷新接口,只有实现了此接口的路由定位器才能被刷新。

ZuulServerAutoConfiguration和ZuulProxyAutoConfiguration里也定义了相关的bean。

ZuulServerAutoConfiguration

 

    @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().getContextPath(),
                this.zuulProperties);
    }

 

 ZuulProxyAutoConfiguration

    @Bean
    @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
    public DiscoveryClientRouteLocator discoveryRouteLocator() {
        return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(),
                this.discovery, this.zuulProperties, this.serviceRouteMapper,
                this.registration);
    }

 

4.与spring mvc的集成 ZuulControler和ZuulHandlerMapping 

Zuul是用来处理Http请求的,其底层是基于Servlet和一系列的Filter,而这些事如何和spring mvc集成呢?ZuulServerAutoConfiguration里定义了ZuulControler和ZuulHandlerMapping,用来和spring mcv集成。其源码如下

    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
            ZuulController zuulController) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
        mapping.setErrorController(this.errorController);
        mapping.setCorsConfigurations(getCorsConfigurations());
        return mapping;
    }

在Spring mvc的执行过程中通过HanderMapping找到对应请求的HandleAdapter。而ZuulHandleMapping便是zuul与spring mvc集成时实现此功能的类。

ZuulConntroller

public class ZuulController extends ServletWrappingController {

    public ZuulController() {
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // Allow all
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        try {
            // We don't care about the other features of the base class, just want to
            // handle the request
            return super.handleRequestInternal(request, response);
        }
        finally {
            // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
            RequestContext.getCurrentContext().unset();
        }
    }

}

 

查看ZuulController的源码可以发现其继承ServletWrappingController,ServletWrappingController的功能就是将Servlet保装成Controller。通过其源码发现最终的执行委托给了ZuulServlet。

 

5.Zuul的饥饿加载 zuul.ribbon.eager-load.enabled 

 在ZuulServerAutoConfiguration定义了ZuulRouteApplicationContextInitializerBean

    @Bean
    @ConditionalOnProperty("zuul.ribbon.eager-load.enabled")
    public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
            SpringClientFactory springClientFactory) {
        return new ZuulRouteApplicationContextInitializer(springClientFactory,
                zuulProperties);
    }

在服务消费方调用服务提供方接口的时候,第一次请求经常会超时,而之后的调用就没有问题了。造成第一次服务调用出现失败的原因主要是Ribbon进行客户端负载均衡的Client并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的Client,所以第一次调用的耗时不仅仅包含发送HTTP请求的时间,还包含了创建RibbonClient的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的显现。通过将zuul.ribbon.eager-load.enabled属性配置为true,让它m们提前创建,而不是在第一次调用的时候创建。可以解决此问题。

 

6.Zuul的事件监听机制,动态路由的基石。ZuulRefreshListener

ZuulServerAutoConfiguration里定义了ZuulRefreshListener。其源码如下:

private static class ZuulRefreshListener
            implements ApplicationListener<ApplicationEvent> {

        @Autowired
        private ZuulHandlerMapping zuulHandlerMapping;

        private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ContextRefreshedEvent
                    || event instanceof RefreshScopeRefreshedEvent|| event instanceof RoutesRefreshedEvent|| event instanceof InstanceRegisteredEvent) {
                reset();
            }
            else if (event instanceof ParentHeartbeatEvent) {
                ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
                resetIfNeeded(e.getValue());
            }
            else if (event instanceof HeartbeatEvent) {
                HeartbeatEvent e = (HeartbeatEvent) event;
                resetIfNeeded(e.getValue());
            }
        }

        private void resetIfNeeded(Object value) {
            if (this.heartbeatMonitor.update(value)) {
                reset();
            }
        }

        private void reset() {
            this.zuulHandlerMapping.setDirty(true);
        }

    }

 

从源码中可以看出Zuul会接收3种事件,RefreshScopeRefreshedEvent、RoutesRefreshedEvent、InstanceRegisteredEvent通知实现了RefreshableRouteLocator的路由定位器,重新加载路由规则,

此外心跳续约监听器HeartbeatMointor也会触发此动作。

7.Filter的初始化

Filter是Zuul的核心,在ZuulServerAutoConfiguration和ZuulProxyAutoConfiguration定义了很多Filter,主要包括以下Filter

图片出自Spring Cloud 微服务实战

8.Zuul初始化总结,zuul初始化时主要做了一下事情

  1. 通过ZuulProperties加载zuul的属性配置
  2. 通过RouterLocator路由定位器加载路由规则。
  3. 初始化与spring mvc的集成的相关bean ZuulControler和ZuulHandlerMapping 
  4. 当 zuul.ribbon.eager-load.enabled=true时初始化Zuul的饥饿加载实现beanZuulRouteApplicationContextInitializerBean。
  5. 监听RefreshScopeRefreshedEvent、RoutesRefreshedEvent、InstanceRegisteredEvent三个事件,实现路由的动态刷新。
  6. 初始化一系列核心的Filter。
  7. 初始化和服务发现的集成的bean:DiscoveryClientRouteLocator,当有新服务注册到服务中心时可以动态添加新服务的路由。
  8. 初始化和Ribon和Hystrix的集成的过滤器RibbonRoutingFilter。拥有了负载均衡和熔断的功能。
  9. 初始化和SpringBootActuator的集成bean:RoutesEndpoint,新增routers端点,可以通过请求/routes路径,获取路由的信息。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!