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

概述spring

  在微服務場景的開發下,網關的重要性不言而喻。Zuul是Netflix開源的微服務網關,Spring Cloud zuul是spring對Zuul進行的整合與加強。本文主要從源碼角度對其初始化的過程。mvc

主要包含如下內容app

  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。咱們查看一下倆個註解的源碼ide

@EnableZuulProxy    微服務

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

} 

@EnableZuulServerui

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

}

@EnableZuulProxy是個複合註解,其引入了@EnableCircuitBreaker,整合了Hystrix,而@EnableZuulServer並無。除此以外倆個註解還導入了不一樣的配置類,@EnableZuulProxy導入了ZuulProxyMarkerConfiguration,而EnableZuulServer導入了ZuulServerMarkerConfiguration。this

咱們再次查看這倆個配置類的源碼,找出其區別。spa

ZuulProxyMarkerConfiguration.net

/**
 * 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路徑,獲取路由的信息。
相關文章
相關標籤/搜索