Spring Cloud源码学习之Zuul

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取 2000+ 道 Java 面试题

Zuul 由Netflix公司开发,是基于JVM的路由器和服务端负载均衡器。事实上,网关承载着路由、负载均衡、安全认证、流量控制、灰度发布、熔断等很多重要功能。

本文将简要学习Zuul的理论和源码,看看其核心功能 Router 和 Filter。

从看电影说起

先以看电影场景来类比下网关。上映2部3D电影,A电影在1、2号厅放映,B电影在3号厅放映

20191123100170\_1.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 是Spring Cloud手册上的标题,Zuul两个核心功能就是:路由过滤。我们可以基于这两个核心功能做大量的事情。

路由 是根据规则做流量转发,犹如婚礼落座、交警指挥交通等。路由用来转发数据流量,无特别之处。

过滤 是在请求处理前后做些事情,想玩转请求都可通过Filter来实现。你就是剪刀手,想把请求剪成什么模样全看你心情。

源码学习

本文源码基于spring-cloud-netflix-core:1.3.0.RELEASE,下面所有示例代码中仅为必要代码,并非全部源码,文章以适合阅读为宜。

现在Servlet虽然退居幕后,但指挥棒还是在Servlet这位指挥官手中,下面看看源码。

魔法入口

用注解 @EnableZuulProxy 标记Spring Boot应用,便获得Zuul的所有能力。

     
  1. @Configuration
  2. public class ZuulProxyConfiguration extends ZuulConfiguration {}

注解Import了 ZuulProxyConfiguration,配置中注入RibbonRoutingFilterSimpleHostRoutingFilter两个用于路由的过滤器。

     
  1. @Configuration
  2. public class ZuulProxyConfiguration extends ZuulConfiguration {
  3. @Bean
  4. public RibbonRoutingFilter ribbonRoutingFilter(...,
  5. RibbonCommandFactory<?> ribbonCommandFactory) {
  6. }
  7. @Bean
  8. public SimpleHostRoutingFilter simpleHostRoutingFilter(...) {
  9. return new SimpleHostRoutingFilter(helper, zuulProperties);
  10. }
  11. }

请求通过网关到达具体服务的整个过程,都是通过Filter来搞定。pre 类型Filter负责事前处理,route 类型Filter负责路由,postRoute 类型Filter负责善后,error 类型负责擦屁股(处理异常)。

其他Filter各司其职,这里不做介绍。

指挥官战略

ZuulProxyConfiguration继承ZuulConfiguration,一个是标准Zuul配置,一个是具备proxy功能的配置。

ZuulConfiguration核心功能之一就是创建zuulServlet,zuulServlet才是请求的实际处理者。而Zuul的所有强大功能,都得益于这位Boss的顶层设计。

     
  1. @Configuration
  2. public class ZuulConfiguration {
  3. @Bean
  4. @ConditionalOnMissingBean(name = "zuulServlet")
  5. public ServletRegistrationBean zuulServlet() {
  6. ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
  7. this.zuulProperties.getServletPattern());
  8. return servlet;
  9. }
  10. }

现在开始复习大学课程,学习Servlet的 init()、service() 方法。熟知的DispatcherServlet也不过只是HttpServlet的 “重孙类” 而已。

     
  1. public class ZuulServlet extends HttpServlet {
  2. @Override
  3. public void init(ServletConfig config) throws ServletException {
  4. super.init(config);
  5. zuulRunner = new ZuulRunner(bufferReqs);
  6. }
  7. @Override
  8. public void service(servletRequest, servletResponse){
  9. try {
  10. init(...);
  11. // 执行所有pre类型Filter
  12. try { preRoute();} catch (ZuulException e) {
  13. error(e); postRoute(); return;}
  14. // 执行所有route类型Filter
  15. try { route(); } catch (ZuulException e) {
  16. error(e); postRoute(); return; }
  17. // 执行所有postRoute类型Filter
  18. try { postRoute(); } catch (ZuulException e) {
  19. error(e); return;
  20. }
  21. }
  22. }
  23. }

整个Zuul的设计如此简单。按 路由前、路由中、路由后 顺序执行各类Filter,出错就catch住并进行error处理。

指挥官实操

在 preRoute()、route()、postRoute()、error(e)四个函数中,做的事情都是一样的,就是运行对应的Filter。以route()为例:

     
  1. void route() throws ZuulException {
  2. zuulRunner.route();
  3. }

ZuulRunner 这位跑腿大叔来代为执行,跑腿大叔果然只负责跑腿,转身就丢给路由专家 FilterProcessor

     
  1. public class FilterProcessor {
  2. public void route() throws ZuulException {
  3. runFilters("route");
  4. }
  5. }

FilterProcessor不愧是专家,提供了个 runFilters() 这个通用函数,不管什么类型,来者不拒。runFilters(“route”)、runFilters(“error”)、runFilters(“post”)、runFilters(“pre”) 各种过滤器通通可以帮忙执行。

     
  1. public Object runFilters(String sType) throws Throwable {
  2. List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
  3. if (list != null) {
  4. for (int i = 0; i < list.size(); i++) {
  5. ZuulFilter zuulFilter = list.get(i);
  6. Object result = processZuulFilter(zuulFilter);
  7. }
  8. }
  9. }

runFilters() 就是从 FilterRegistry 中取出对应类型的Filters,以for循环逐一执行各Filter。

另,现在XXRegistry、XXHub特别多,像Docker Registry、Dockerhub、Github等等,各种同类交友集中地。FilterRegistry也就是各位Filter的宿舍,各Filter创建之后就进入了Registry。

路由怎么实现

一路上修修补补请求倒不是什么难事,可路由毕竟是个体力活,要把请求从网关转发到背后真正的服务。

像Nginx、Apache、Haproxy等都有着数据转发的功能,那Zuul又如何实现?

入口处特意提到 SimpleHostRoutingFilterRibbonRoutingFilter,这两位就是在两种场景下负责路由的哥们。先看看SimpleHostRoutingFilter。

     
  1. public class SimpleHostRoutingFilter extends ZuulFilter {
  2. private CloseableHttpClient httpClient;
  3. @Override
  4. public boolean shouldFilter() {
  5. return RequestContext.getCurrentContext().getRouteHost() != null && ...;
  6. }
  7. @Override
  8. public Object run() {
  9. CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
  10. headers, params, requestEntity);
  11. }
  12. }

CloseableHttpClient 是apache httpclient 中的类。SimpleHostRoutingFilter中两个重要方法,一是shouldFilter()即什么情况下会进行路由;而是run()即路由处理逻辑。

继续跟一下run()中forward的实际处理,其实就是 用HttpClient发起了个HTTP请求

     
  1. httpclient.execute(httpHost, httpRequest);

至于是走SimpleHostRoutingFilter还是RibbonRoutingFilter,下面简单说明下。

如果是下面这种配置,RouteHost有值,走的就是SimpleHostRoutingFilter。

     
  1. zuul.routes.user-service.url=http://localhost:8080
  2. zuul.routes.user-service.path=/api/users/**

如果是下面的配置,配的serviceId,走的是RibbonRoutingFilter。

     
  1. zuul.routes.user-service.path=/api/users/**
  2. zuul.routes.user-service.serviceId: user-service

本篇暂不深入RibbonRoutingFilter,它涉及到Ribbon及Hystrix。


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Cloud源码学习之Zuul

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏