Spring Cloud源码学习之Zuul

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

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

从看电影说起

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

简易流程如下(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,配置中注入RibbonRoutingFilter 和 SimpleHostRoutingFilter两个用于路由的过滤器。

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

请求通过网关到达具体服务的整个过程,都是通过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.  
  8. @Override
  9. public void service(servletRequest, servletResponse){
  10. try {
  11. init(...);
  12. // 执行所有pre类型Filter
  13. try { preRoute();} catch (ZuulException e) {
  14. error(e); postRoute(); return;}
  15. // 执行所有route类型Filter
  16. try { route(); } catch (ZuulException e) {
  17. error(e); postRoute(); return; }
  18. // 执行所有postRoute类型Filter
  19. try { postRoute(); } catch (ZuulException e) {
  20. error(e); return;
  21. }
  22. }
  23. }
  24. }

整个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又如何实现?

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

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

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。

猜你喜欢

转载自blog.csdn.net/qq_42950313/article/details/82500105
今日推荐