陈同学
微服务
Accelerator
About
# Spring Cloud源码学习之Zuul [Zuul](https://github.com/Netflix/zuul) 由Netflix公司开发,是基于JVM的路由器和服务端负载均衡器。事实上,网关承载着路由、负载均衡、安全认证、流量控制、灰度发布、熔断等很多重要功能。 本文将简要学习Zuul的理论和源码,看看其核心功能 Router 和 Filter。 ## 从看电影说起 先以看电影场景来类比下网关。**上映2部3D电影,A电影在1、2号厅放映,B电影在3号厅放映**。 ![](https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2018/09/06/04760587827c42c2955bdaa211e55c28.png)简易流程如下(S表示Step,灰色用来类比网关功能): * **S1**.观众进入电影院大厅,凭验证码或二维码取票,到时间点后检票入场(没票则拒绝入场),拿3D眼镜 > [pre]取票(eg:普通token转JWT token)、检票(安全认证)、发3D眼镜(预处理请求之类) * **S2**.观众根据电影票上的厅号进入1、2、3厅 > [route]看A或B电影(路由)、看A电影的进12厅(负载均衡) * **S3**.看完电影后退回3D眼镜 > [postRoute]处理Response 当然,观众就是流量(Request)。生活中例子其实很多,如:景区检票、小区大门物业、公司大门安保、交通安检、各类活动入场等等。 网关,只是在计算机世界而已,并无特别之处,就是一个**网络关卡**。 ## Zuul核心功能 [Router and Filter: Zuul](https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_router_and_filter_zuul) 是Spring Cloud手册上的标题,Zuul两个核心功能就是:**路由** 与 **过滤**。我们可以基于这两个核心功能做大量的事情。 **路由** 是根据规则做流量转发,犹如婚礼落座、交警指挥交通等。路由用来转发数据流量,无特别之处。 **过滤** 是在请求处理前后做些事情,想玩转请求都可通过Filter来实现。你就是剪刀手,想把请求剪成什么模样全看你心情。 ## 源码学习 > 本文源码基于spring-cloud-netflix-core:1.3.0.RELEASE,下面所有示例代码中仅为必要代码,并非全部源码,文章以适合阅读为宜。 现在Servlet虽然退居幕后,但指挥棒还是在Servlet这位指挥官手中,下面看看源码。 ###魔法入口 用注解 `@EnableZuulProxy` 标记Spring Boot应用,便获得Zuul的所有能力。 ```java @Configuration public class ZuulProxyConfiguration extends ZuulConfiguration {} ``` 注解Import了 `ZuulProxyConfiguration`,配置中注入**RibbonRoutingFilter** 和 **SimpleHostRoutingFilter** 两个用于路由的过滤器。 ```java @Configuration public class ZuulProxyConfiguration extends ZuulConfiguration { @Bean public RibbonRoutingFilter ribbonRoutingFilter(..., RibbonCommandFactory<?> ribbonCommandFactory) { } @Bean public SimpleHostRoutingFilter simpleHostRoutingFilter(...) { return new SimpleHostRoutingFilter(helper, zuulProperties); } } ``` 请求通过网关到达具体服务的整个过程,都是通过Filter来搞定。**pre** 类型Filter负责事前处理,**route** 类型Filter负责路由,**postRoute** 类型Filter负责善后,**error** 类型负责擦屁股(处理异常)。 其他Filter各司其职,这里不做介绍。 ### 指挥官战略 ZuulProxyConfiguration继承ZuulConfiguration,一个是标准Zuul配置,一个是具备proxy功能的配置。 ZuulConfiguration核心功能之一就是创建**zuulServlet**,zuulServlet才是请求的实际处理者。而Zuul的所有强大功能,都得益于这位Boss的顶层设计。 ```java @Configuration public class ZuulConfiguration { @Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(), this.zuulProperties.getServletPattern()); return servlet; } } ``` 现在开始复习大学课程,学习Servlet的 **init()、service()** 方法。熟知的**DispatcherServlet**也不过只是HttpServlet的 "重孙类" 而已。 ```java public class ZuulServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { super.init(config); zuulRunner = new ZuulRunner(bufferReqs); } @Override public void service(servletRequest, servletResponse){ try { init(...); // 执行所有pre类型Filter try { preRoute();} catch (ZuulException e) { error(e); postRoute(); return;} // 执行所有route类型Filter try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } // 执行所有postRoute类型Filter try { postRoute(); } catch (ZuulException e) { error(e); return; } } } } ``` 整个Zuul的设计如此简单。按 **路由前、路由中、路由后 **顺序执行各类Filter,出错就catch住并进行error处理。 ### 指挥官实操 在 preRoute()、route()、postRoute()、error(e)四个函数中,做的事情都是一样的,就是运行对应的Filter。以**route()**为例: ```java void route() throws ZuulException { zuulRunner.route(); } ``` 用 **ZuulRunner** 这位跑腿大叔来代为执行,跑腿大叔果然只负责跑腿,转身就丢给路由专家 **FilterProcessor**。 ```java public class FilterProcessor { public void route() throws ZuulException { runFilters("route"); } } ``` FilterProcessor不愧是专家,提供了个 **runFilters()** 这个通用函数,不管什么类型,来者不拒。**runFilters("route")、runFilters("error")、runFilters("post")、runFilters("pre")** 各种过滤器通通可以帮忙执行。 ```java public Object runFilters(String sType) throws Throwable { List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType); if (list != null) { for (int i = 0; i < list.size(); i++) { ZuulFilter zuulFilter = list.get(i); Object result = processZuulFilter(zuulFilter); } } } ``` **runFilters()** 就是从 **FilterRegistry** 中取出对应类型的Filters,以for循环逐一执行各Filter。 另,现在XXRegistry、XXHub特别多,像Docker Registry、Dockerhub、Github等等,各种同类交友集中地。FilterRegistry也就是各位Filter的宿舍,各Filter创建之后就进入了Registry。 ### 路由怎么实现 一路上修修补补请求倒不是什么难事,可路由毕竟是个体力活,要把请求从网关转发到背后真正的服务。 像Nginx、Apache、Haproxy等都有着数据转发的功能,那Zuul又如何实现? 入口处特意提到 **SimpleHostRoutingFilter** 和 **RibbonRoutingFilter**,这两位就是在两种场景下负责路由的哥们。先看看SimpleHostRoutingFilter。 ```java public class SimpleHostRoutingFilter extends ZuulFilter { private CloseableHttpClient httpClient; @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && ...; } @Override public Object run() { CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); } } ``` **CloseableHttpClient** 是apache **httpclient** 中的类。SimpleHostRoutingFilter中两个重要方法,一是shouldFilter()即什么情况下会进行路由;而是run()即路由处理逻辑。 继续跟一下run()中**forward**的实际处理,其实就是 **用HttpClient发起了个HTTP请求**。 ```java httpclient.execute(httpHost, httpRequest); ``` 至于是走SimpleHostRoutingFilter还是RibbonRoutingFilter,下面简单说明下。 如果是下面这种配置,RouteHost有值,走的就是SimpleHostRoutingFilter。 ```properties zuul.routes.user-service.url=http://localhost:8080 zuul.routes.user-service.path=/api/users/** ``` 如果是下面的配置,配的serviceId,走的是RibbonRoutingFilter。 ```properties zuul.routes.user-service.path=/api/users/** zuul.routes.user-service.serviceId: user-service ``` 本篇暂不深入RibbonRoutingFilter,它涉及到Ribbon及Hystrix。 ## 小结 网关的地位可谓至高无上,本文仅以最简单场景加源码来学习下网关的基础知识。
本文由
cyj
创作,可自由转载、引用,但需署名作者且注明文章出处。
文章标题:
Spring Cloud 源码学习之 Zuul
文章链接:
https://chenyongjun.vip/articles/72
扫码或搜索 cyjrun 关注微信公众号, 结伴学习, 一起努力