华企号 后端开发 Zuul网关原理

Zuul网关原理

零丶概述#

Zuul是netflix旗下开源网关,作为微服务系统的网关组件,是微服务请求的前门。微服务网关的作用有点类似于AOP,将负载均衡,限流熔断等”横切关注点”都集中于此,避免每一个服务都需要写重复的功能(解决不了的问题,我们就加一层doge)

且微服务网关是统一入口,系统有多个服务,不能每个服务一个对外地址,用户需要一个统一的系统入口进行操作,故zuul网关是系统的统一入口。

为微服务云平台提供统一的入口是API网关最主要的用途,除此之外,网关还可承担认证授权、访问控制、路由、负载均衡、缓存、日志、限流限额、转换、映射、过滤、熔断、注册、服务编排、API管理、监控、统计分析等等非业务性的功能。

下面是zuul的架构图,结合图我们开始Zuul源码之旅。

img

一丶ZuulHandlerMapping 处理器映射器#

zuul和spring的结合,请求到达服务的时候,tomcat会根据请求路径调用到指定的servlet,这里会调用到DispatcherServlet,DispatcherServlet会使用HandlerMapping处理器映射器找到当前请求的处理器Handler,这里便会找到ZuulHandlerMapping

image-20221227195556263

ZuulHandlerMapping是一个HandlerMapping,HandlerMapping称为处理器映射器,这里的处理器表示处理http请求,映射器表示根据请求中的一些特征(一般是url,请求头等)映射到一个处理器。当一个请求交由DispatcherServlet处理的时候,会首先通过处理器映射器找到合适的处理器

1.DispatcherServlet根据请求找到合适的处理器#

image-20221227200144492

DispatcherServlet会遍历spring容器中的所有HandlerMapping的实现调用其getHandler方法找到处理器,然后和HandlerInterceptor一起包装成HandlerExecutionChain(后续会先调用HandlerInterceptor的preHandle前置处理,postHandle后置处理,请求处理完毕后调用afterCompletion)

image-20221227200338202

2.ZuulHandlerMapping 发光发热#

当一个请求调用到我们的zuul网关的时候,DispatcherServlet根据请求找到合适的处理器的这一步,会调用到zuul自动配置注入到spring容器中的ZuulHandlerMapping,那么ZuulHandlerMapping 做了写什么呢

2.1利用RouteLocator注册Handler#

image-20221227201250577

在ZuulHandlerMapping中,根据请求找合适的处理器最终交给lookupHandler方法,此方法调用registerHandlers注册请求处理器

image-20221227201516717

这里出现一个新的类RouteLocator路由定位器,顾名思义就是用来获取路由的方法

image-20221227201949804

这里的路由定位器是CompositeRouteLocator,其内部使用一个集合保存其他的RouteLocator,一般zuul自动配置会为我们注入一个DiscoveryClientRouteLocator(父类是SimpleRouteLocator,会读取配置文件中的路由配置)其基于服务发现(DiscoveryClient#getServices方法)并且将微服务的serviceId如A注册成路由/A/**,意味着A开头的请求后续都将路由到微服务A

最终会将这些路由的url注册到一个map当中,map的value是ZuulController

2.2.路径匹配获取请求处理器ZuulController#

ZuulHandlerMapping是一个AbstractUrlHandlerMapping,顾名思义会根据url找对应的请求处理器,逻辑如下

image-20221227203723420

这里找到的请求处理器,必然是ZuulController(如果路径和配置,或者服务发现中serviceId匹配上的话)

二丶ZuulController处理请求#

1.将zuulController组装成HandlerExecutionChain#

HandlerExecutionChain内部使用数组保存拦截器HandlerInterceptor,在ZuulHandlerMapping找到对应的ZuulController(其实是一个单例对象,都是同一个),会从容器中拿到HandlerInterceptor类型的bean,和ZuulController包装到一起生成HandlerExecutionChain

image-20221227204820967

2.获取ZuulController匹配的HandlerAdapter#

想调用ZuulController?还需要其对应的HandlerAdapter,由于SpringMVC定义了多种处理器(比如RequestMappingHandlerAdapter(我们最常用的,基于@RequestMapping注解,反射调用Controller方法的处理器对应的适配器)SimpleServletHandlerAdapter(适配Servlet),SimpleControllerHandlerAdapter(适配Controller接口对象)),为了屏蔽这种差异springmvc又定义了一个HandlerAdapter——处理器适配器,使用适配器模式,如同一个多功能转接头来适配多种处理器

image-20221227205634830

找到合适处理器适配器的代码如下

image-20221227205838428

这里自然找到的是SimpleControllerHandlerAdapter

image-20221227205931456

随后会执行拦截器HandlerInterceptor#preHandle方法

3.适配器调用ZuulController处理请求#

在拦截器的preHandler执行完成后,接下来会调用ZuulController#handleRequest,这一步是使用SimpleControllerHandlerAdapter进行的方法调用,我们看下ZuulController是如何处理请求的

image-20221227210442318

ZuulController是一个ServletWrappingController(内部包装servlet),对请求的处理最终交给ZuulServlet

兜兜转转最终还是来到了ZuulServlet,其本质是一个Servlet对象,看来Zuul处理Spring框架下可以用,普通的Servlet应用也可以使用呀

三丶ZuulServlet处理请求#

1.ZuulServlet初始化ZuulRunner#

在zuulServlet真正处理器请求之前,会初始化一个zuulRunner,ZuulRunner的init方法会被调用,zuul在这里面初始化了一个基于ThreadLocal的请求上下文,ZuulRunner提供了preRoute,postRoute,routeerror方法,顾名思义分别在路由前,路由后,路由,以及发生错误的时候进行调用

2.ZuulServlet处理请求#

image-20221227211939471

可以看到ZuulServlet存在三个阶段,preRoute,route,postRoute,如果出现异常那么将调用errorRoute

这些方法的调用都委托给ZuulRunner进行,ZuulRunner会调用FilterProcessor.getInstance()对应的方法

image-20221227212017626

image-20221227212210313

image-20230103191703344

最终是FilterLoader.getInstance()负责找到合适的Filter并且排序得到先后顺序之后依次调用。

image-20230103193524720

具体存在哪些Filter做了什么事情,我们在ZuulFilter章节细说

3.ZuulFilter#

ZuulFilter实现了Comparable 和 IZuulFilter

  • 实现Comparable ,并且定义了方法filterOrder,在比较方法中,起始是调用filterOrder得到顺序然后进行比较

    image-20230103192352365

  • IZuulFilter 是Zuul定义的过滤器接口(注意不是Servlet规范中的过滤器)

    image-20230103192509167

    但是据我看到的源码,FilterLoader拿到的过滤器都是实现了ZuulFilter的

3.1 preRoute#

上面源码我们说到了,对过滤器的调用,最终是委托给FilterLoader,在FilterRegistry中找到对应类型的过滤器,并且进行排序,然后依次调用。下面我们看下pre类型的过滤器有哪些,都做了什么事情

image-20230103191603568

3.1.1 ServletDetectionFilter#

这个过滤器,负责调用ServletRequest#getAttribute方法判断,请求是否通过DispatcherServlet处理,然后来到ZuulController,接着调用ZuulServlet来到此,判断的依据就是经过DispatcherServlet处理的请求,会被调用DispatcherServlet#setAttribute(常量字符串,WebApplicationContext web环境下spring上下文)

image-20230103194135813

3.1.2 Servlet30WrapperFilter#

负责将请求适配成Servlet3.0所规范的样子

image-20230103200812434

使用Servlet30RequestWrapper包装原有的请求来实现,有点适配器,又有点装饰器设计模式的意思

3.1.3 FormBodyWrapperFilter#

如果请求的content-type中带有application/x-www-form-urlencodedmultipart/form-data

  • application/x-www-form-urlencoded:表示数据发送到服务器之前,所有字符都会进行编码。属于比较常用的编码方式。
  • multipart/form-data:指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。

那么FormBodyWrapperFilter 会生效,将对原本请求使用FormBodyRequestWrapper进行包装,并提取原始请求的contentData、contentLength、contentType赋值到FormBodyRequestWrapper的属性上

image-20230103201647826

image-20230103201621168

3.1.4 DebugFilter#

如果请求中zuul.debug.parameter参数对应的值是true 那么,将调用RequestContext#setDebugRouting(true)RequestContext#setDebugRequest(true)

image-20230103201954369

这两个设置的作用应该是让zuul打印出一些帮助调试的日志信息

3.1.5 PreDecorationFilter#

根据提供的RouteLocator确定路由的位置和方式。还为下游请求设置各种与代理相关的标头。此类运行逻辑分支很多

3.2 route#

route类型网关负责,将请求路由到对应的服务,其中最为关键的便是RibbonRoutingFilter,它基于Ribbon负载均衡

image-20230104152546785

3.2.1 RibbonRoutingFilter#

基于Ribbon负载均衡,实现服务调用的过滤器。

image-20230109191956645

  • 构建RibbonCommandContext,就是对下游微服务的请求的上下文数据的封装,会记录目标服务的id,请求方式,请求资源uri,请求头,请求参数,请求体等等(配置中通过sensitiveHeaders指定哪些请求头不透传)
  • forward是根据serviceId,和微服务中注册的应用,依据负载均衡的策略,挑选一个健康的实例发送请求

    image-20230109193414959

    • RibbonCommandFactory#Create

    image-20230109193608270

    其中FallbackProvider在调用失败后,将调用fallbackResponse来进行服务降级

    RibbonLoadBalancingHttpClient 使用ribbon实现负载均衡的Http客户端

    • RibbonCommand#execute

    其会根据ILoadBalance调用到IRule(ribbon负载均衡策略)选择一个健康的实例,然后使用HttpClient发送请求,包装结果然后返回

  • setResponse就是在上下文中记录下,forward调用得到的请求结果。

    image-20230109194716104

3.2.2 SendForwardFilter#

使用RequestDispatcher转发请求的Route ZuulFilter。一般是在配置文件中配置 location:forward.to或者配置url:forward:xxxx的时候会生效。会使用RequestDispatcher#forward来进行转发

image-20230109194946099

3.3 post#

3.3.1 SendResponseFilter#

负责将路由请求结果写入到HttpServletResponse

image-20230109200504374

  • 写请求头 调用HttpServletResponse#addHeader将请求头加到HttpServletResponse对象中
  • 写请求内容,就是使用流拷贝route阶段的请求结果到HttpServletResponse

3.4 error#

3.4.1 SendErrorFilter#

如果前面阶段的过滤器出现错误,将调用此过滤器,它负责使用RequestDispatcher将请求路由到/error

image-20230109202843472

至此我们看完了zuul转发请求,写请求内容到`HttpServletResponse`的流程
接下来将回到 DispatchServlet 中

四丶DispatchServlet收尾工作#

接下来便是调用HandlerInterceptor#postHandle然后调用HandlerInterceptor#afterCompletion,并推送一个ServletRequestHandledEvent事件其中记录了请求信息和处理时间等等信息

最后DispatchServlet处理结束,tomcat负责将请求结果返回给调用方。

五丶扩展自己的ZuulFilter#

实现ZuulFilter,并将自己的ZuulFilter注册到Spring容器中,可以实现一些自定义操作。

ZuulFilter需要实现filterType(过滤器类型,字符串,可以选择pre route post)filterOrder(决定Filter的顺序,值越大顺序越后)

在公司我见过使用自定义的zuulFilter将Tracer注入到请求中,实现分布式链路日志

作者: 华企网通李铁牛程序员

我是程序员李铁牛,热爱互联网软件开发和设计,专注于大数据、数据分析、数据库、php、java、python、scala、k8s、docker等知识总结。15889726201 我的座右铭:"业精于勤荒于嬉,行成于思毁于随"
上一篇
下一篇

发表回复

联系我们

联系我们

028-84868647

在线咨询: QQ交谈

邮箱: tech@68v8.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部