接着上一篇继续讲Zuul,上一篇搭建了Zuul的环境简单说明了怎么使用,本篇查缺补漏将一些经常使用的配置贴出来。文末咱们会一块儿分析一下Zuul的源码,升华一下本篇的格调!java
忽略全部微服务或某些微服务web
默认状况下,只要引入了zuul后,就会自动一个默认的路由配置,但有些时候咱们可能不想要默认的路由配置规则,想本身进行定义,忽略全部微服务:(后面写 * ):正则表达式
zuul: ignored-services: "*"
忽略某些微服务:(直接写微服务的名字=>能够理解为spring.application.name的值,多个以都好分隔)spring
zuul: ignored-services: product-provider,product-consumer-8201
忽略全部为服务,只路由指定的微服务api
zuul: # 排除全部的服务,只由指定的服务进行路由 ignored-services: "*" routes: eureka-client: path: /client1/** serviceId: eureka-client
经过path和url访问到具体的某台机器上浏览器
有时候咱们测试的时候须要访问到具体的某台机器上,而不但愿负载均衡到别的机器上或者须要访问到第三方的某台机器上:服务器
zuul: routes: product-provider: path: /product/** url: http://localhost:8202/
注意:cookie
敏感头的传递(好比Cookie等)全局设置和某个微服务设置多线程
有些时候咱们微服务上游可能想传递一些请求头到下游的服务,好比Token、Cookie等值,默认状况下,zuul 不会将 Cookie,Set-Cookie,Authorization 这三个头传递到下游服务,若是须要传递,就须要忽略这些敏感头。app
zuul: #全部服务路径前统一加上前缀 prefix: /api # 排除某些路由, 支持正则表达式 ignored-patterns: - /**/modify/pwd # 排除服务 ignored-services: user-center routes: eureka-client: path: /client1/** serviceId: eureka-client sensitiveHeaders: #当前这个路由的header为空 sensitiveHeaders: Cookie,Set-cookie #全局路由都带这些header
开启Zuul很简单,在启动类上添加Zuul 开启注解:
@EnableZuulProxy
/** * 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 { }
上面的注释上有一句话:EnableZuulServer 是不使用代理功能来获取Zuul server。开启Zuul 网关有两种方式:
简单来讲,@EnableZuulProxy可理解为@EnableZuulServer的加强版,当Zuul与Eureka、Ribbon等组件配合使用时,咱们使用@EnableZuulProxy。
接着看 EnableZuulProxy,在类头引用了ZuulProxyMarkerConfiguration
,ZuulProxyAutoConfiguration 的做用是开启 ZuulProxyAutoConfiguration
的标记。
ZuulProxyAutoConfiguration 继承了 ZuulServerAutoConfiguration,是 ZuulServerAutoConfiguration 的超集。该类注入了DiscoveryClient、RibbonCommandFactoryConfiguration用做负载均衡相关的。注入了一些列的filters,好比PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter。
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); } /** * Zuul建立的一个Controller,用于将请求交由ZuulServlet处理 * */ @Bean public ZuulController zuulController() { return new ZuulController(); } /** * 会添加到SpringMvc的HandlerMapping链中, *只有选择了ZuulHandlerMapping的请求才能出发到Zuul的后续流程 * */ @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); mapping.setCorsConfigurations(getCorsConfigurations()); return mapping; } /** * ZuulServlet是整个流程的核心 * * */ @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; } ...... ...... ...... }
在 ZuulServerAutoConfiguration 中定义了几个核心对象:
ZuulServlet是整个流程的核心,大体的请求过程以下:
当Zuulservlet收到请求后, 会建立一个ZuulRunner对象,该对象中初始化了RequestContext:存储请求的ServletRequest 和 ServletResponse对象,并被当前请求链上的全部Zuulfilter共享;
ZuulRunner中还有一个 FilterProcessor,FilterProcessor做为执行全部的Zuulfilter的管理器;
FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载;
有了这些filter以后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器, 最后执行的是post 类型的过滤器,若是在执行这些过滤器有错误的时候则会执行error类型的过滤器;
执行完这些过滤器,最终将请求的结果返回给客户端。
RequestContext就是会一直跟着整个请求周期的上下文对象,filters之间有什么信息须要传递就set一些值进去就好了。
ZuulServlet 掌控全部url的流转,咱们先看它作了什么工做:
public class ZuulServlet extends HttpServlet { private static final long serialVersionUID = -3374242278843351500L; private ZuulRunner zuulRunner; @Override public void init(ServletConfig config) throws ServletException { super.init(config); String bufferReqsStr = config.getInitParameter("buffer-requests"); boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false; zuulRunner = new ZuulRunner(bufferReqs); } @Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } } /** * executes "post" ZuulFilters * * @throws ZuulException */ void postRoute() throws ZuulException { zuulRunner.postRoute(); } /** * executes "route" filters * * @throws ZuulException */ void route() throws ZuulException { zuulRunner.route(); } /** * executes "pre" filters * * @throws ZuulException */ void preRoute() throws ZuulException { zuulRunner.preRoute(); } /** * initializes request * * @param servletRequest * @param servletResponse */ void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { zuulRunner.init(servletRequest, servletResponse); } /** * sets error context info and executes "error" filters * * @param e */ void error(ZuulException e) { RequestContext.getCurrentContext().setThrowable(e); zuulRunner.error(); } @RunWith(MockitoJUnitRunner.class) public static class UnitTest { @Mock HttpServletRequest servletRequest; @Mock HttpServletResponseWrapper servletResponse; @Mock FilterProcessor processor; @Mock PrintWriter writer; @Before public void before() { MockitoAnnotations.initMocks(this); } @Test public void testProcessZuulFilter() { ZuulServlet zuulServlet = new ZuulServlet(); zuulServlet = spy(zuulServlet); RequestContext context = spy(RequestContext.getCurrentContext()); try { FilterProcessor.setProcessor(processor); RequestContext.testSetCurrentContext(context); when(servletResponse.getWriter()).thenReturn(writer); zuulServlet.init(servletRequest, servletResponse); verify(zuulServlet, times(1)).init(servletRequest, servletResponse); assertTrue(RequestContext.getCurrentContext().getRequest() instanceof HttpServletRequestWrapper); assertTrue(RequestContext.getCurrentContext().getResponse() instanceof HttpServletResponseWrapper); zuulServlet.preRoute(); verify(processor, times(1)).preRoute(); zuulServlet.postRoute(); verify(processor, times(1)).postRoute(); // verify(context, times(1)).unset(); zuulServlet.route(); verify(processor, times(1)).route(); RequestContext.testSetCurrentContext(null); } catch (Exception e) { e.printStackTrace(); } } } }
ZuulServlet 继承了HttpServlet,主要的做用就是对HTTP请求进行拦截作对应的处理。直接看实现方法 service()中的实现:
@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); //这里从RequestContext中取出当前线程中封装好的对象而后在该对象上打上zuul处理的标记 RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
第一句init方法:
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { RequestContext ctx = RequestContext.getCurrentContext(); if (bufferRequests) { ctx.setRequest(new HttpServletRequestWrapper(servletRequest)); } else { ctx.setRequest(servletRequest); } ctx.setResponse(new HttpServletResponseWrapper(servletResponse)); }
在这里调用了一个RequestContext类将HttpServletRequest保存进去,而 RequestContext 类自己也比较特殊:
public class RequestContext extends ConcurrentHashMap<String, Object> { rotected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() { @Override protected RequestContext initialValue() { try { return contextClass.newInstance(); } catch (Throwable e) { throw new RuntimeException(e); } } }; public static RequestContext getCurrentContext() { if (testContext != null) return testContext; RequestContext context = threadLocal.get(); return context; } }
它自己就是一个Map,须要注意的是,该对象的使用方式并非直接new 一个新对象,而是调用getCurrentContext()方法,该方法中返回的是 threadLocal 封装的反射生成new RequestContext的方式来建立对象。确保每一个建立的 RequestContext 只在当前线程内有效,即在当前线程内,getCurrentContext()方法取出的是同一个 RequestContext对象。
继续回到service()方法,拿到了封装好了的RequestContext方法以后,下面进入四个route中,上节已经讲过这4个route都属于Filter的生命周期,在这里完成请求的过滤,转发,后置逻辑处理。route完成以后,最后的finally方法中调用了RequestContext.getCurrentContext().unset()
方法,既然使用了threadLocal,必然使用完要清除,否则极可能就内存泄漏。
小憩一会:
分析到 ZuulServlet,不知你是否发现Zuul的核心。对于Zuul实现网关的功能其实就是围绕着HttpServlet拿到ServletRequest,对请求作过滤操做,拿到ServletResponse 对返回结果作后置处理操做。HttpServlet是单实例多线程的处理模型,若是存在某一个请求比较耗时,那么该线程就会一直阻塞直处处理完成返回成功才结束。倘若这样的请求不少,对Zuul所在的服务器压力仍是不小。
上面已经分析得出Zuul是基于Servlet这一套逻辑来作的,往下跟就变得简单。SpringMVC是如何处理请求的呢?你们应该都比较熟悉,浏览器发出一个请求到达服务端,首先到达DispatcherServlet,Servlet容器将请求交给HandlerMapping,找到对应的Controller访问路径和处理方法对应关系,接着交由HandlerAdapter路由到真实的处理逻辑中去进行处理。
上面我贴出来 ZuulServerAutoConfiguration#ZuulHandlerMapping,定义了ZuulHandlerMapping bean对象。
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { }
ZuulHandlerMapping 自身继承了AbstractUrlHandlerMapping,即经过url来查找对应的处理器。判断的核心逻辑在 lookupHandler方法中:
@Override protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) { return null; } //判断urlPath是否被忽略,若是忽略则返回null if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null; RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey("forward.to")) { return null; } if (this.dirty) { synchronized (this) { if (this.dirty) { //若是没有加载过路由或者路由有刷新,则加载路由 registerHandlers(); this.dirty = false; } } } return super.lookupHandler(urlPath, request); } private void registerHandlers() { Collection<Route> routes = this.routeLocator.getRoutes(); if (routes.isEmpty()) { this.logger.warn("No routes found from RouteLocator"); } else { for (Route route : routes) { //调用父类,注册处理器,这里全部路径的处理器都是ZuulController registerHandler(route.getFullPath(), this.zuul); } } }
总体逻辑就是在路由加载的时候须要为每一个路由指定处理器,由于Zuul不负责逻辑处理,因此它也没有对应的Controller可使用,那怎么办呢,注册处理器的时候,使用的是ZuulController
,是Controller
的子类,对应的适配器是SimpleControllerHandlerAdapter
,也就说每个路由规则公共处理器都是ZuulController
,这个处理器最终会调用ZuulServlet
通过zuul定义的和自定义的拦截器。
上面还有一句:
Collection<Route> routes = this.routeLocator.getRoutes();
RouteLocator的做用是路由定位器,先看它有哪些实现类:
/服务名称/**
映射成路由规则;从实现类的功能看路由定位器的做用就是区分当前从哪里加载路由进行注册。上面这几个实现类都实现了Ordered类,加载的顺序依照getOrder()
数值大小来定。
至此咱们已经把Zuul最核心的路由部分撸了一遍,从Spring MVC 加载Servlet 的过程入手,到自定义 ZuulServlet 进行处理,进而使用Zuul中定义的各类Filter来作逻辑过滤。原理其实很简单,重要的是思想。做为一个网关,它是很重要的服务,这种实现方式你们以为是否优雅,是否还有别的实现方式呢?若是是你你会如何实现网关,这些问题你们能够慢慢思考。