暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring Cloud Zuul【源码篇】揭秘 Zuul

花好夜猿 2019-07-04
462

参考资料:
Spring Cloud 官网
https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#_router_and_filter_zuul

Zuul wiki
https://github.com/Netflix/zuul/wiki/Getting-Started

相关版本:zuul 1.3.1,spring boot 2.1.4 ,spring cloud Greenwich.SR1

一个简单的 zuul 案例

  • step1、引入依赖
    // zuul 依赖
    spring-cloud-starter-netflix-zuul
    // eureka 依赖
    spring-cloud-starter-netflix-eureka-client

  • step2、开启 zull 功能
    @EnableZuulProxy

  • step3、application.properties 相关配置


通过上面三个步骤配置,便完成了一个 API 网关。


zuul 工作流程

zuul 功能开启

追踪注解 @EnableZuulProxy

@EnableZuulProxy 源码:引入了配置类 ZuulProxyMarkerConfiguration

聚焦 ZuulProxyMarkerConfiguration

可以看到 ZuulProxyMarkerConfiguration 中实例化了一个空的对象 Marker。

这个空对象实例 ZuulProxyMarkerConfiguration .Marker 有啥用?

通过查看 zuul的两个自动配置类:ZuulServerAutoConfigurationZuulProxyAutoConfiguration 类上的注解

可以得出结论:ZuulProxyMarkerConfiguration.Marker.class 是 zuul 实现自动配置的触发开关。

换句话说,@EnableZuulProxy 是触发 zuul 自动配置的开关,相当于 zuul 功能开启开关。

zuul 工作流程图

上图是 Zuul 处理请求的全过程。

zuul 工作流程分解

ZuulController

1、如何被实例化加载?
在 ZuulServerAutoConfiguration 自动配置类中进行实例化。实例托管 Spring 。

2、ZuulController 功能作用

ZuulController 继承自 ServletWrappingController。

ServletWrappingController 作用:将应用中的某个 Servlet 封装成 Controller 用来处理 Servlet 中的所有请求。

  • ZuulController 是 ZuulServlet Controller 方式的封装。

  • ZuulController 拦截了  zuulServlet 处理的请求,

  • 请求调用ZuulController#handleRequest 中的 super.handleRequestInternal(request, response); 最终调用 ZuulServlet#service

ZuulServlet

1、如何被实例化加载?
在 ZuulServerAutoConfiguration 自动配置类中实例化并注册为一个 servlet ,实例托管 Spring 。

2、ZuulServlet 主要功能作用

主线逻辑:

  • a、标注请求为 zuul 请求

  • b、进行 pre、route、post route 过滤处理

通过查看代码能够知道 pre、route、post route 的所有操作都委托给了 zuulRunner 执行


ZuulRunner

1、如何被加载实例化?
ZuulRunner 的生命周期为一次请求,每一次请求都会生成一个 zuulRunner 对象

2、ZuulRunner 功能作用

通过代码能够知道 ZuulRunner 啥都没做,直接就把 pre、route、post route 的所有操作直接交给 FilterProcessor 来进行处理。

FilterProcessor

1、FilterProcessor 是一个单例。在类初始化时,FilterProcessor 就被加载并实例化。

2、FilterProcessor 功能

所有从 ZuulRunner 中过来过滤操作会被分为:post、error、route、pre 这四种类型。然后通过 FilterProcessor#runFilters 进行处理。

聚焦FilterProcessor#runFilters

代码中,主要的两个逻辑方法

  • FilterLoader.getInstance().getFiltersByType
    根据 post、error、route、pre 类型,从 FilterLoader 中获取所有该类型的 ZuulFilter 过滤操作

  • FilterProcessor#processZuulFilter
    遍历查找到的 ZuulFilter ,并调用  ZuulFilter#runFilter 进行处理,获取成功或者失败的操作。

总结

zuul 由 servlet + filter 完成请求的处理。
请求交由 ZuulServlet 处理, ZuulServlet 根据 pre、post、route、error 过滤链进行请求过滤处理,最后得到处理结果。

问:FilterLoader 如何加载 ZuulFilter 过滤链

聚焦 FilterLoader

FilterLoader 类进行 ZuulFilter 加载。

FilterLoader是一个单例,在类初始化时就直接实例化。

zuul 请求处理调用在方法 FilterProcessor#runFilters 中,通过查看该方法,能够知道 请求真实处理是交由 ZuulFilter 来执行的,而ZuulFilter 的获取是 通过  FilterLoader#getInstance()#getFiltersByType 进行获取。

通过代码能够知道, FilterLoader 会根据过滤链类型进行过滤连的缓存,在进行调用获取过滤链时,先从缓存中获取,如果没有再从 FilterRegistry 中获取所有的过滤链,然后根据类型遍历获取对应类型的过滤链。

知识扩展:通过代码能够看到  Collections.sort(list); // sort by priority ,能够知道 ZuulFilter 是存在执行顺序的。通过设定不同的优先级,来指定特定的 ZuulFilter 执行顺序。

聚焦 FilterRegistry 

根据 FilterLoader#getFiltersByType 中的 FilterRegistry#getAllFilters 作为入口进行查看

FilterRegistry 是一个单例,在类初始化时进行实例化操作

通过 FilterRegistry#getAllFilters 方法发现 ZuulFilter 集合是从缓存中直接获取的。

ZuulFilter 是如何被加载到 FilterRegistry 缓存 filters 中的 ?

通过查看 FilterRegistry 找不到相关操作,但是有一个方法值得关注, FilterRegistry#put 方法。该方法执行将单个 zuulFIlter 放入 FilterRegistry 缓存 filters 中。

以 FilterRegistry#put 为入口追溯如何别调用

通过源码追踪, ZuulFilter 有两种加载渠道:

  • Groovy File

  • Bean config

ZuulFilter 获取渠道:Groovy File

Groovy Filter 实现动态加载 ZuulFilter。


通过 FilterRegistry#put 追溯到 Groovy Filter 加载 ZuulFilter 的管理类 FilterFileManager

FilterFileManager 是实现动态加载 ZuulFilter 管理类。
Zuul 默认没有开启动态加载 ZuulFilter 的方式,需要自己实例化 FilterFileManager 并修改 FilterLoader DynamicCodeCompiler 的 Compile 为 GroovyCompiler。
更多相关配置参考 Hystrix 官方提供的 StartServer.java

聚焦 FilterFileManager 

FilterFileManager 如何加载 ZuulFilter 文件

FilterFileManager 加载 ZuulFilter 文件的两个主要方法:

  • FilterFileManager#getFiles
    获取指定目录文件下的所有 ZuulFilter groovy 文件

  • FilterFileManager#processGroovyFiles
    调用 FilterLoader.getInstance().putFilter(file); 将 ZuulFilter 加载到 FilterRegistry 缓存中

聚焦FilterLoader#getInstance#putFilter

主线逻辑:

  • 1、判定指定名称的 ZuulFilter 如果被修改过,清除 FilterRegistry 中对应的缓存数据

  • 2、实例化 指定目录中加载的 ZuulFilter (groovy 文件 ) 并存入 FilterRegistry 缓存。

那么问题来了,FilterFileManager 又是如何实现动态加载 groovy 文件,动态更新 ZuulFilter 到缓存中?

1、动态加载 ZuulFilter (groovy 文件)

再来看看 FilterFileManager

代码一目了然,FilterFileManager 通过死循环+ sleep 的方式,实现 FilterFileManager#manageFiles 方法的轮询调用。(FilterFileManager#manageFiles 就是上面提到的实现将 ZuulFilter 存入 FilterRegistry 缓存的方法调用

2、维护 ZuulFilter 修改时间

在 FilterLoader 中维护了一个列表 filterClassLastModified ,存放了 ZuulFilter 名称 和 文件最后修改时间的关联关系,通过维护一个修改时间来判定每次轮训加载的 ZuulFilter 是否是最新的,是否修改过,来对 ZuulFilter 缓存进行增删改查,依次来实现动态加载,更新 ZuulFilter 的功能。

在 FilterLoader#putFilter 执行逻辑:

  • 逻辑1、根据关联关系 filterClassLastModified
    1、加载的 ZuulFilter 如果之前未加载,则不处理
    2、加载的 ZuulFilter 如果之前加载,判定指定 zuulFilter 是否修改过,如果修改过从 FilterRegistry 缓存中移除。

  • 逻辑2、加载 ZuulFilter 到 FilterRegistry 中
    1、加载的 ZuulFilter 如果之前未加载,加载到 FilterRegistry 缓存中
    2、加载的 ZuulFilter 之前被加载过

    • ZuulFilter 被修改了,重新加载到 FilterRegistry 缓存中

    • ZuulFilter 未被修改,不处理。

ZuulFilter 获取渠道:Bean config

通过 Spring Bean 的方式加载 ZuulFilter 。

查看 ZuulServerAutoConfiguration 自动配置类,看看 zuul 默认提供的部分 zuulFilter 的加载

这些在 Spring Bean 中的 ZuulFilter 如何被加载到 FilterRegistry 中。

聚焦 ZuulFilterConfiguration

通过查看代码,这里拿到了全局的 FilterLoader ,全局的 FilterRegistry 以及所有的 ZuulFilter 。将这些作为 ZuulFilterInitializer 的构造参数。


猜测 ZuulFilter 存入 FilterRegistry 的操作在 ZuulFilterInitializer 中执行。

聚焦 ZuulFilterInitializer

ZuulFilterInitializer#contextInitialized 能够看到 @PostConstruct 。所以在 ZuulFilterInitializer 构造函数调用时该方法会被加载。
该方法会遍历所有的 filter ,并将 filter 放入 filterRegistry 的缓存 map 中。

总结

ZuulFilter 有两种加载方式

  • 通过 Groovy File 的方式实现动态的 ZuulFitler 加载

  • 通过 Spring Bean 的方式,在程序启动过程中,通过构造方法加载到缓存中,方便你调用。

最后总结

Zuul 本质由 Servlet 和 Filter 组成。请求以 ZuulServlet 为入口,经由各种 ZuulFilter 过滤处理,最后响应处理结果。

-- END --

       长按二维码      

关注 [ WTF名字好难取 ] 公众号


往期回顾


Spring Cloud Feign【源码篇】Feign 如何进行服务间请求调用

Spring Cloud Feign【源码篇】Feign Hystrix Support

Spring Cloud Feign【源码篇】Feign 如何调用Ribbon进行客户端负载均衡

Spring Cloud Hystrix【理论篇】

Spring Cloud Hystrix【应用篇】

Spring Cloud Hystrix【应用篇】 Hystrix Dashboard

Redis【理论篇】主从复制-快速扩容式读高并发 Redis 架构

文章转载自花好夜猿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论