SpringCloud-Zuul源碼分析和路由改造
在使用SpringCloud的時候準備使用Zuul作為微服務的網關,Zuul的默認路由方式主要是兩種,一種是在配置 文件裏直接指定靜態路由,另一種是根據註冊在Eureka的服務名自動匹配。比如如果有一個名為service1的服 務,通過 http://www.domain.com/service1/xxx 就能訪問到這個服務。但是這和我預想的需求還是有些差距。 網上有許多有關動態路由的實現方法,大致思想是不從Eureka拉取註冊服務信息,而是在資料庫裏自己維護一 份路由表,定時讀取資料庫拉取路由,來實現自動更新。而我的需求更進一步,我希望對外暴露的網關介面是 一個固定的url,如http://www.domain.com/gateway ,然後根據一個頭信息service來指定想要訪問的服務,而不是在 url後面拼接服務名。同時我也不想將我註冊到Eureka的服務名直接暴露在api中,而是做一層映射,讓我可以 靈活指定serivce名。
例如:
- 訪問 http://www.domain.com/gateway ,headers包含service=service.a,調用後端service1
- 訪問 http://www.domain.com/gateway ,headers包含service=service.b,調用後端service2
現在研究一下Zuul的源碼來看看怎麼實現這個功能。
我們都知道在Springboot啟動類上加一個@EnableZuulProxy就啟用了Zuul。從這個註解點進去看看:
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
它引入了ZuulProxyMarkerConfiguration這個配置,進去看下。
/**
* Responsible for adding in a marker bean to trigger activation of
* {@link ZuulProxyAutoConfiguration}.
*
* @author Biju Kunjummen
*/
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
從注釋中看到這個是用於激活ZuulProxyAutoConfiguration,看看這個類
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
//...
}
這個配置下面註冊了很多組件,不過先暫時不看,它同時繼承自ZuulServerAutoConfiguration,看看這個類:
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@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;
private Map<String, CorsConfiguration> corsConfigurations;
@Autowired(required = false)
private List<WebMvcConfigurer> configurers = emptyList();
@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().getContextPath(),
this.zuulProperties);
}
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
if (this.corsConfigurations == null) {
ZuulCorsRegistry registry = new ZuulCorsRegistry();
this.configurers.forEach(configurer -> configurer.addCorsMappings(registry));
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false", matchIfMissing = true)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(
new ZuulServlet(), this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesnt
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
@Bean
@ConditionalOnMissingBean(name = "zuulServletFilter")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true", matchIfMissing = false)
public FilterRegistrationBean zuulServletFilter() {
final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setUrlPatterns(
Collections.singleton(this.zuulProperties.getServletPattern()));
filterRegistration.setFilter(new ZuulServletFilter());
filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
// The whole point of exposing this servlet is to provide a route that doesnt
// buffer requests.
filterRegistration.addInitParameter("buffer-requests", "false");
return filterRegistration;
}
// pre filters
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
@Bean
public FormBodyWrapperFilter formBodyWrapperFilter() {
return new FormBodyWrapperFilter();
}
@Bean
public DebugFilter debugFilter() {
return new DebugFilter();
}
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter() {
return new Servlet30WrapperFilter();
}
// post filters
@Bean
public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
return new SendResponseFilter(zuulProperties);
}
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter();
}
@Bean
@ConditionalOnProperty("zuul.ribbon.eager-load.enabled")
public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
SpringClientFactory springClientFactory) {
return new ZuulRouteApplicationContextInitializer(springClientFactory,
zuulProperties);
}
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
filterLoader, filterRegistry);
}
}
@Configuration
@ConditionalOnClass(MeterRegistry.class)
protected static class ZuulCounterFactoryConfiguration {
@Bean
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnMissingBean(CounterFactory.class)
public CounterFactory counterFactory(MeterRegistry meterRegistry) {
return new DefaultCounterFactory(meterRegistry);
}
}
@Configuration
protected static class ZuulMetricsConfiguration {
@Bean
@ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
@ConditionalOnMissingBean(CounterFactory.class)
public CounterFactory counterFactory() {
return new EmptyCounterFactory();
}
@ConditionalOnMissingBean(TracerFactory.class)
@Bean
public TracerFactory tracerFactory() {
return new EmptyTracerFactory();
}
}
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);
}
}
private static class ZuulCorsRegistry extends CorsRegistry {
@Override
protected Map<String, CorsConfiguration> getCorsConfigurations() {
return super.getCorsConfigurations();
}
}
}
這個配置類裏註冊了很多bean:
- SimpleRouteLocator:默認的路由定位器,主要負責維護配置文件中的路由配置。
- DiscoveryClientRouteLocator:繼承自SimpleRouteLocator,該類會將配置文件中的靜態路由配置以及服務發現(比如eureka)中的路由信息進行合併,主要是靠它路由到具體服務。
- CompositeRouteLocator:組合路由定位器,看入參就知道應該是會保存好多個RouteLocator,構造過程中其實僅包括一個DiscoveryClientRouteLocator。
- ZuulController:Zuul創建的一個Controller,用於將請求交由ZuulServlet處理。
- ZuulHandlerMapping:這個會添加到SpringMvc的HandlerMapping鏈中,只有選擇了ZuulHandlerMapping的請求才能出發到Zuul的後續流程。
還有一些其他的Filter,不一一看了。
其中,ZuulServlet是整個流程的核心,請求的過程是具體這樣的,當Zuulservlet收到請求後, 會創建一個ZuulRunner對象,該對象中初始化了RequestContext:作為存儲整個請求的一些數據,並被所有的Zuulfilter共享。ZuulRunner中還有一個 FilterProcessor,FilterProcessor作為執行所有的Zuulfilter的管理器。FilterProcessor從filterloader 中獲取zuulfilter,而zuulfilter是被filterFileManager所 載入,並支持groovy熱載入,採用了輪詢的方式熱載入。有了這些filter之後,zuulservelet首先執行的Pre類型的過濾器,再執行route類型的過濾器, 最後執行的是post 類型的過濾器,如果在執行這些過濾器有錯誤的時候則會執行error類型的過濾器。執行完這些過濾器,最終將請求的結果返回給客戶端。 RequestContext就是會一直跟著整個請求週期的上下文對象,filters之間有什麼信息需要傳遞就set一些值進去就行了。
有個示例圖可以幫助理解一下: