zuul 1.x 源码解析 (结合本人过去2年工作经历整理,超详细)

1 启动配置项

1.1 @EnableZuulProxy

在这里插入图片描述
作为zuul网关,需要配置@EnableZuulProxy注解,这个注解表明,嵌入该注解的类将自动成为 Zuul 服务器的入口。当然这个注解还有着其他的非常强大的功能,我们可以基于此注解来使用 Zuul 中的各种内置过滤器实现复杂的服务路由。此注解,由
@Import({ZuulProxyMarkerConfiguration.class})修饰。

1.2 @Import({ZuulProxyMarkerConfiguration.class})

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以发现,ZuulProxyAutoConfiguration
是通过ZuulProxyMarkerConfiguration 中 Marker 类是否存在,当做是否进行自动装配的开关。

1.3 ZuulProxyAutoConfiguration

ZuulProxyAutoConfiguration生效原理:
SpringBoot自动装配,启动的时候要加载大量的spring.factories文件,并扫描获得EnableAutoConfiguration的值,其中就包含ZuulServerAutoConfiguration
以及ZuulProxyAutoConfiguration。ZuulProxyAutoConfiguration继承自ZuulServerAutoConfiguration。这两个配置都有共同点,使用@ConditionalOnBean({Marker.class})注解修饰。前者ZuulProxyMarkerConfiguration.Marker的创建来源于@EnableZuulProxy注解, 后者ZuulServerMarkerConfiguration.Marker的创建来源于@EnableZuulServer注解。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3.1(延伸)@EnableZuulServer、@EnableZuulProxy两个注解的区别

@EnableZuulServer - 普通Zuul Server,只支持基本的route与filter功能.
@EnableZuulProxy - 普通Zuul Server+服务发现与熔断等功能的增强版,具有反向代理功能.
@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当Zuul与Eureka、Ribbon等组件配合使用时,我们使用@EnableZuulProxy。

2 ZuulServlet创建

2.1 ZuulServlet作用

1.ZuulServlet - 处理请求(调度不同阶段的filters,处理异常等)

ZuulServlet类似SpringMvc的DispatcherServlet,所有的Request都要经过ZuulServlet的处理。三个核心的方法preRoute(),route(), postRoute(),zuul对request处理逻辑都在这三个方法里。

2.作为激活ZuulServerAutoConfiguration创建的前提条件
在这里插入图片描述
3.作为ZuulController创建的前提条件
在这里插入图片描述
在这里插入图片描述

2.2 ZuulServlet初始化

在项目启动阶段,会完成ZuulServlet的初始化,并调用init方法,此方法中,包含了对ZuulRunner的初始化。zuul对request处理会交给ZuulRunner去执行。由于ZuulServlet是单例,因此ZuulRunner也仅有一个实例。

在这里插入图片描述

3.Zuul filter的初始化

3.1:filterRegistry的初始化

在ZuulServerAutoConfiguration配置类内部,包含了
ZuulFilter的配置项,该配置项,内部注入了map类型的filters,
业务自己定义的filter只要交给spring托管, 就可以加载进来,并成为map容器的元素,key为ZuulFilter的bean的名字,值为对应的ZuulFilter。该配置项,在内部定义了ZuulFilterInitializer这个bean,在启动的时候,会完成FilterRegistry、FilterLoader以及ZuulFilterInitializer的初始化。

在这里插入图片描述

3.2:Zuul filter初始化到内存

在ZuulFilterInitializer类的内部,有@PostConstruct注解修饰的contextInitialized方法,该方法会在上述private Map<String, ZuulFilter> filters 依赖注入完成后调用。方法内部,遍历了所有的ZuulFilter,并将其放在了单例FilterRegistry内部维护的
ConcurrentHashMap容器中。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.2.1(延伸)Zuul组件实现动态过滤

上述Zuul filter初始化到内存的方式,是常规的初始化方式,Zuul设计的初衷是ZuulFilter是存放在Groovy文件中,可以实现基于最后修改时间进行热加载。即可以让服务在不停机的情况下,对过滤的条件进行变更。即动态过滤功能。
。使用示例如下:
1.将过滤器的java文件,放置到指定的一个文件夹:(C:\Users\Lenovo\Desktop\custom-zuul-filter)
2.由于动态路由是基于文件扫描的机制,所以需要手动去启用。如下是基于服务启动后去做的这个事情。

在这里插入图片描述
3.若要更新文件内容,可以直接对指定文件进行修改,然后丢回到指定目录(C:\Users\Lenovo\Desktop\custom-zuul-filter)即可。添加过滤器也是采用类似的方式。

3.2.2 Zuul组件实现动态过滤原理概述

1.总的来说是通过通过FilterFileManager来对过滤器进行扫描以及加载,如下是FilterFileManager的结构,在调用init方法时,会调用manageFiles方法。

在这里插入图片描述
2.manageFiles方法会做如下处理,

在这里插入图片描述

在这里插入图片描述
3.putFilter方法,依次对文件进行处理。通过类加载器去加载过滤器对应的java文件,然后生成字节码,最后通过反射生成对象。然后进行注册(添加到注册表中),以及添加修改记录。

在这里插入图片描述
4.可以看到的是,更新的时候,FilterLoader的hashFiltersByType会被清空。但是没关系,因为获取为空的时候会重新生成。

在这里插入图片描述
5.编译器如何生成字节码:

在这里插入图片描述
6.Zuul实现动态过滤的原理图:

在这里插入图片描述
7.FilterFileManager在调用init方法时,内部也调用了startPoller方法,内部定义了一个线程,运行机制比较简单,死循环,休眠,执行manageFiles方法,循环往复。manageFiles方法内部执行逻辑上面已详细讲述。

在这里插入图片描述

3.2.3 phoenix Zuul 网关在动态修改过滤器方面的实现

1.phoenix网关的管理后台,可以通过拖拽控件的方式,改变过滤器顺序,在改变顺序的同时,会通过zk修改路径节点值,来触发网关同步修改内存中的zkFilter的顺序:

在这里插入图片描述
2.网关清空hashFiltersByType后, 后续请求执行逻辑:

在这里插入图片描述

4 请求处理

4.1 RequestContext的初始化

1.整个请求的过程是这样的,首先将请求给zuulservlet处理,
并调用zuulservlet的service方法

在这里插入图片描述
2.service方法内部,调用了zuulRunner的初始化方法。

在这里插入图片描述
3.zuulRunner的初始化方法init,内部完成了
初始化RequestContext。RequestContext的作用:作为存储整个请求的一些数据,并被所有的 zuulfilter共享。RequestContext是存放在threadLocal中的。

在这里插入图片描述

在这里插入图片描述

4.2 FilterProcessor处理请求

1.在zuulservlet中,调用完zuulRunner的初始化方法init后,会执行pre,route,post过滤器的相关方法。
在这里插入图片描述
在这里插入图片描述
2.ZuulRunner直接将执行逻辑交由FilterProcessor处理,FilterProcessor也是单例。

在这里插入图片描述
在这里插入图片描述
4.FilterProcessor作为执行所有的zuulfilter的管理器。其功能就是依据filterType执行filter的处理逻辑。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载。以pre过滤器为例,FilterProcessor内部会执行preRoute()方法,该方法内部会调用runFilters方法。

在这里插入图片描述
runFilters方法内部,会根据过滤器类型:pre, 将所有pre类型的过滤器筛选出来 ,并按照优先级进行排序。其背后调用了FilterRegistry这个类,这个类维护了一个ConcurrentHashMap<String, ZuulFilter> filters 容器,用于存放所有的过滤器ZuulFilter。
在这里插入图片描述
在这里插入图片描述
得到过滤出来的过滤器列表后,依次调用processZuulFilter方法。

在这里插入图片描述
6.processZuulFilter方法内部,调用了过滤器本身的runFilter方法

在这里插入图片描述

7.ZuulFilter在执行runFilter的时候,会执行该过滤器子类里面的run方法。

在这里插入图片描述

在这里插入图片描述
8.由此,在我们自定义过滤器的时候,可以在run方法中,自定义一些自己的业务逻辑。
在这里插入图片描述

5 过滤器链执行逻辑

5.1执行逻辑

1.在请求发起调用后,会执行ZuulServlet的service方法,该方法内部,会依次执行preRoute,route以及postRoute方法,以preRoute
为例:
在这里插入图片描述

在这里插入图片描述
2.最终会调用FilterProcessor的runFilters方法
在这里插入图片描述
在这里插入图片描述
3.在FilterProcessor的runFilters方法内部,调用了FilterLoader的getFiltersByType方法,此方法在上文中已介绍过,不再赘述。获得对应类型的ZuulFilter列表后,调用processZuulFilter依次对其进行处理。
在这里插入图片描述
4.在processZuulFilter方法内部,调用了ZuulFilter的runFilter方法。
在这里插入图片描述
5.runFilter方法内部,会依次执行过滤器的run()方法,我们可以在自定义过滤器中的run方法内部,定义自己的逻辑。
在这里插入图片描述
在这里插入图片描述

5.2过滤器介绍

5.2.1前置过滤器

前置过滤器执行顺序:依次执行ServletDetectionFilter、Servlet30WrapperFilter、FormBodyWrapperFilter、DebugFilter、PreDecorationFilter

5.2.1.1 ServletDetectionFilter

1.本过滤器的作用是判断请求的来源,判断请求是从dispatcherServlet来的还是 从zuulServlet来的。并将判断结果存放到RequestContext中。键为:isDispatcherServletRequest 值为:true或者false。通过RequestUtils.isDispatcherServletRequest()或者RequestUtils.isZuulServletRequest()判断请求的源头,以实现后续不同的处理机制。实际应用:在Servlet30WrapperFilter、PreDecorationFilter有用到。
2.一般情况下,发送到api网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/路径访问的请求会绕过DispatcherServlet(比如之前我们说的大文件上传),被ZuulServlet处理,主要用来应对大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/,我们可以通过zuul.servletPath参数进行修改。
在这里插入图片描述

5.2.1.2 Servlet30WrapperFilter

Servle30WrapperFilter主要是将原始请求进行包装,将原始的HttpServletRequest请求包装成Servlet30RequestWrapper类型的,扩展其功能。包装后的效果:等同于HttpServletRequestWrapper包装。经过包装之后可以修改请求的一些参数,以及contentData等内容。
在这里插入图片描述

5.2.1.3 FormBodyWrapperFilter

该过滤器仅对两种类请求生效,第一类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。
在这里插入图片描述
在这里插入图片描述

5.2.1.4 DebugFilter

该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。而它的具体操作内容则是将当前的请求上下文中的debugRouting和debugRequest参数设置为true。由于在同一个请求的不同生命周期中,都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题。另外,对于请求参数中的debug参数,我们也可以通过zuul.debug.parameter来进行自定义。
在这里插入图片描述

5.2.1.5 PreDecorationFilter

1.PreDecorationFilter是pre阶段最后被执行的过滤器。该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的)。
在这里插入图片描述
2.而它的具体操作内容就是为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息比如PROXY_KEY、SERVICE_ID_KEY等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些信息。
在这里插入图片描述
上图中的Route信息,是根据请求path来匹配的,相关的配置,以phoenix网关为例,存放在Gateway-Routing这个namespace下面。API-GATEWAY为路由信息id。当访问地址符合/gateway/interface规则的时候,会被自动定位到APIGATEWAY服务上去。只不过网关进行了全局拦截处理,将SERVICE_ID_KEY,替换成了要转发的下游服务id
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.另外,我们还可以在该实现中找到一些对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如:X-Forwarded-Host、X-Forwarded-Port。另外,对于这些头域的记录是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值为true,所以Zuul在请求跳转时默认地会为请求增加X-Forwarded-*头域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded-    Proto。我们也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。
在这里插入图片描述
在这里插入图片描述

5.2.2路由过滤器

路由过滤器执行顺序:执行顺序依次是RibbonRoutingFilter、SimpleHostRoutingFilter、SendForwardFilter。当满足某个过滤器执行条件时,其他两个过滤器不走。

5.2.2.1 RibbonRoutingFilter

1.该过滤器只对请求上下文中不存在routeHost,但存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。
在这里插入图片描述
此处的SERVICE_ID_KEY与GwAuthChecker类中的SERVICE_ID_KEY是同一个。
在这里插入图片描述
2.若上述shouldFilter方法返回true,将走run方法。
在这里插入图片描述
2.1 buildCommandContext方法,对将要发送的请求进行一个封装。
在这里插入图片描述
此处的请求路径,是提取的上下文信息里面的REQUEST_URI_KEY。此值在phoenix网关中,是在GwAuthChecker类中,提炼了请求体里面的参数,然后赋值上去的。
在这里插入图片描述
在这里插入图片描述
2.2 forward方法:this.ribbonCommandFactory.create(context)会返回一个具体的RibbonCommand的实现。真正调用服务的是execute方法。
在这里插入图片描述
执行execute方法后,依次调用如下方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终会调用LoadBalancerCommand类,submit方法内部的selectServer方法:该方法的作用是筛选出调用的实例
在这里插入图片描述
在这里插入图片描述
getServerFromLoadBalancer方法:
在这里插入图片描述
ILoadBalancer有多个实现类,最终将调用BaseLoadBalancer的chooseServer方法:
在这里插入图片描述
chooseServer方法内部,调用了IRule的choose方法,用于筛选实例。
在这里插入图片描述
以phoenix网关为例,由于网关自定义了MetaDataCanaryRule用于实现IRule,所以最终请求将走MetaDataCanaryRule内部的choose方法,用于实现灰度发布功能。
在这里插入图片描述
在这里插入图片描述
2.3 forward方法执行完后,将获得其调用的返回结果,并调用setResponse方法,对结果进行处理。
在这里插入图片描述
将返回结果,存放在zuul的上下文信息里面的zuulResponse中。
在这里插入图片描述
以phoenix网关为例,在加密过滤器中,将调用(ClientHttpResponse) ctx.get(“zuulResponse”),获取调用下游服务后的返回结果。
在这里插入图片描述
3.在调用下游服务失败的时候,会执行handleException方法处理异常:
在这里插入图片描述
在这里插入图片描述
以phoenix网关为例,最终会调gatewayErrorController里的/error接口进行处理。
在这里插入图片描述

5.2.2.2 SimpleHostRoutingFilter

是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。
在这里插入图片描述
而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。配置类似zuul.routes.user-service.url=http://localhost:8080/这样的底层都是通过httpclient直接发送请求的。
在这里插入图片描述
发起调用后,会将响应结果,放到zuul的上下文信息里面的zuulResponse中。
在这里插入图片描述

5.2.2.3 SendForwardFilter

是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置。
在这里插入图片描述

5.2.3 error过滤器

5.2.3.1 SendErrorFilter

1.是error阶段第一个执行的过滤器。该过滤器仅在请求上下文中包含throwable参数(由之前执行的过滤器设置)并且还没有被该过滤器处理过(sendErrorFilter.ran 参数值为false)的时候执行。处理过后,会将请求上下文中sendErrorFilter.ran 参数值设置为true。
在这里插入图片描述
在这里插入图片描述
2.而该过滤器的具体逻辑就是利用请求上下文中的错误信息来组织成一个forward到API网关/error错误端点的请求来产生错误响应。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.以phoenix网关为例,请求是不会走SendErrorFilter的,因为虽然FilterProcessor的runFilters方法查出了2个error过滤器,GwErrorFilter和SendErrorFilter, 但后者因为在配置文件中,设置了zuul.SendErrorFilter.error.disable=true
属性,所以在ZuulFilter内部,依次遍历执行过滤器的时候,将跳过SendErrorFilter的执行。只会执行GwErrorFilter。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.2.4 后置过滤器

5.2.4.1 SendResponseFilter

1.是post阶段最后执行的过滤器
2.若当前请求,出现异常,走了SendErrorFilter,并forward到API网关/error错误端点,来产生错误响应。将不会再走此过滤器。

3.网关请求执行完毕后,将向zuul的上下文信息里面,存放responseBody 内容
在这里插入图片描述
4. 该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含它们其中一个的时候就会执行处理逻辑。
在这里插入图片描述
5. 响应头里面,添加信息:
在这里插入图片描述
6. 下游服务返回数据,写入到输出流:
在这里插入图片描述

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值