Zuul的核心源码解析
在 Zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个
zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的
zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理
器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支
持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的
过滤器,再执行 route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误
的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。
(1)初始化
SpringCloud对Zuul的封装使得发布一个ZuulServer无比简单,根据自动装载原则可以在 spring-
cloud-netflix-zuul-2.1.0.RELEASE.jar 下找到 spring.factories
ZuulServerAutoConfiguration ,ZuulProxyAutoConfiguration 是Zuul服务端的自动配置类,这些
配置类究竟负责什么工作,我们继续来看
@Configuration @Import({RestClientRibbonConfiguration.class, OkHttpRibbonConfiguration.class, HttpClientRibbonConfiguration.class, HttpClientConfiguration.class}) @ConditionalOnBean({Marker.class}) public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration { //省略 }
ZuulProxyAutoConfiguration 继承了 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 doesn't // 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 doesn't // 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(value = "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(); } } }
CompositeRouteLocator :组合路由定位器,看入参就知道应该是会保存好多个RouteLocator,构造过程中其实仅包括一个DiscoveryClientRouteLocator。
SimpleRouteLocator :默认的路由定位器,主要负责维护配置文件中的路由配置。
ZuulController :Zuul创建的一个Controller,用于将请求交由ZuulServlet处理。
ZuulHandlerMapping :这个会添加到SpringMvc的HandlerMapping链中,只有选择了ZuulHandlerMapping的请求才能出发到Zuul的后续流程。
注册 ZuulFilterInitializer,通过FilterLoader加载应用中所有的过滤器并将过滤器注册到FilterRegistry,那我们接下来一起看下过滤器是如何被加载到应用中的。
public class ZuulFilterInitializer { private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class); private final Map<String, ZuulFilter> filters; private final CounterFactory counterFactory; private final TracerFactory tracerFactory; private final FilterLoader filterLoader; private final FilterRegistry filterRegistry; public ZuulFilterInitializer(Map<String, ZuulFilter> filters, CounterFactory counterFactory, TracerFactory tracerFactory, FilterLoader filterLoader, FilterRegistry filterRegistry) { this.filters = filters; this.counterFactory = counterFactory; this.tracerFactory = tracerFactory; this.filterLoader = filterLoader; this.filterRegistry = filterRegistry; } @PostConstruct public void contextInitialized() { log.info("Starting filter initializer"); TracerFactory.initialize(tracerFactory); CounterFactory.initialize(counterFactory); for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) { filterRegistry.put(entry.getKey(), entry.getValue()); } } @PreDestroy public void contextDestroyed() { log.info("Stopping filter initializer"); for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) { filterRegistry.remove(entry.getKey()); } clearLoaderCache(); TracerFactory.initialize(null); CounterFactory.initialize(null); } private void clearLoaderCache() { Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType"); ReflectionUtils.makeAccessible(field); @SuppressWarnings("rawtypes") Map cache = (Map) ReflectionUtils.getField(field, filterLoader); cache.clear(); } }
( 2)请求转发
在Zuul的自动配置中我们看到了 ZuulHandlerMapping ,为SpringMVC中 HandlerMapping 的拓展实
现,会自动的添加到HandlerMapping链中。
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { private final RouteLocator routeLocator; private final ZuulController zuul; private ErrorController errorController; private PathMatcher pathMatcher = new AntPathMatcher(); private volatile boolean dirty = true; public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) { this.routeLocator = routeLocator; this.zuul = zuul; this.setOrder(-200); } private void registerHandlers() { Collection<Route> routes = this.routeLocator.getRoutes(); if (routes.isEmpty()) { this.logger.warn("No routes found from RouteLocator"); } else { Iterator var2 = routes.iterator(); while(var2.hasNext()) { Route route = (Route)var2.next(); this.registerHandler(route.getFullPath(), this.zuul); } } } }
其主要目的就是把所有路径的请求导入到 ZuulController上.另外的功效是当觉察RouteLocator路由表变
更,则更新自己dirty状态,重新注册所有Route到ZuulController。
public class ZuulController extends ServletWrappingController { public ZuulController() { //在这里已经设置了ZuulServlet this.setServletClass(ZuulServlet.class); this.setServletName("zuul"); this.setSupportedMethods((String[])null); } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView var3; try { //在这里面会调用ZuulServlet的service方法 var3 = super.handleRequestInternal(request, response); } finally { RequestContext.getCurrentContext().unset(); } return var3; } }
在 ZuulController 中的 handleRequest 方法,会调用已经注册的 ZuulServlet 完成业务请求,我们
进入 ZuulServlet 看下内部是如何处理的
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { try { this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { this.preRoute(); } catch (ZuulException var13) { this.error(var13); this.postRoute(); return; } try { this.route(); } catch (ZuulException var12) { this.error(var12); this.postRoute(); return; } try { this.postRoute(); } catch (ZuulException var11) { this.error(var11); } } catch (Throwable var14) { this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
( 3)过滤器
Zuul默认注入的过滤器可以在 spring -cloud-netflix-core.jar 中找到
Zuul网关存在的问题
在实际使用中我们会发现直接使用Zuul会存在诸多问题,包括:
性能问题
Zuul1x 版本本质上就是一个同步Servlet,采用多线程阻塞模型进行请求转发。简单讲,每来
一个请求,Servlet容器要为该请求分配一个线程专门负责处理这个请求,直到响应返回客户
端这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被
阻塞,阻塞期间线程资源被占用,不能干其它事情。我们知道Servlet容器线程池的大小是有
限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成
容器无法接受新的请求。
不支持任何长连接,如 websocket