概述spring
在微服務場景的開發下,網關的重要性不言而喻。Zuul是Netflix開源的微服務網關,Spring Cloud zuul是spring對Zuul進行的整合與加強。本文主要從源碼角度對其初始化的過程。mvc
主要包含如下內容app
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的加強,主要有如下增長
2.路由配置 ZuulProperties
ZuulServerAutoConfiguration裏定義了zuul的基礎配置,查看其源碼發現首先注入了ZuulProperties。
從其名稱能夠看出其爲定義了Zuul的配置屬性信息。Zuul的具體有哪些配置,讀者能夠自行查看ZuulProperties源碼。在ZuulProperties裏還有一個內部類Router定義了一個路由的相關配置。
3.路由定位器 RouteLocator
Zuul經過RouteLocator獲取路由規則,其類關係以下
/服務名稱/**
映射成路由規則在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初始化時主要作了一下事情