写于:2019-07-09 22:52:37 参考资料: Spring Cloud 官网 Zuul wiki

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

一、zuul 使用案例

《zuul Getting Started》

二、源码追踪入口

2.1、@EnableZuulProxy 入口

@EnableZuulProxy 该注解开启了 Zuul 功能

2.1.1、@EnableZuulProxy 相关代码如下

  1. @EnableCircuitBreaker
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Import(ZuulProxyMarkerConfiguration.class)
  5. public @interface EnableZuulProxy {
  6. }

@EnableZuulProxy 引入了配置类 ZuulProxyMarkerConfiguration

聚焦 ZuulProxyMarkerConfiguration 相关代码如下:

  1. @Configuration
  2. public class ZuulProxyMarkerConfiguration {
  3. @Bean
  4. public Marker zuulProxyMarkerBean() {
  5. return new Marker();
  6. }
  7. class Marker {
  8. }
  9. }

小贴士 借助 @Conditionxxx 注解,结合 ZuulProxyMarkerConfiguration.Marker.class 实现 zuul 自动配置的触发开关。

通过 ZuulProxyMarkerConfiguration.Marker 追踪到 zuul的两个自动配置类:ZuulServerAutoConfigurationZuulProxyAutoConfiguration

二、zuul 工作流程

Zuul 工作流程图如下:
01.jpg

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

根据上述流程图进行主线执行流程分析

2.1、ZuulController

2.1.1、 ZuulServerAutoConfiguration 中对 ZuulController 进行配置,代码如下:

  1. public class ZuulServerAutoConfiguration {
  2. @Bean
  3. public ZuulController zuulController() {
  4. return new ZuulController();
  5. }
  6. }

2.1.2、ZuulController 功能作用,直接看代码分析

  1. public class ZuulController extends ServletWrappingController {
  2. public ZuulController() {
  3. setServletClass(ZuulServlet.class);
  4. setServletName("zuul");
  5. setSupportedMethods((String[]) null); // Allow all
  6. }
  7. @Override
  8. public ModelAndView handleRequest(HttpServletRequest request,
  9. HttpServletResponse response) throws Exception {
  10. try {
  11. // We don't care about the other features of the base class, just want to
  12. // handle the request
  13. // 调用的 ZuulServlet#service
  14. return super.handleRequestInternal(request, response);
  15. }
  16. finally {
  17. // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
  18. // 清除 ThreadLocal 上下文信息
  19. RequestContext.getCurrentContext().unset();
  20. }
  21. }
  22. }

通过上述代码得到几个结论:

  • ZuulController 继承自 ServletWrappingController。

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

  • ZuulController 代理了 zuulServlet 的所有请求。所有请求最终都调用的 ZuulServlet#service

    猜测:使用 ZuulController 代理 ZuulServlet 所有请求的原因,是为了最终 ThreadLocal 上下文的清除的实现。

2.3、ZuulServlet

2.3.1、ZuulServlet 在 ZuulController 中通过反射的方式实例化

  1. public class ServletWrappingController extends AbstractController
  2. implements BeanNameAware, InitializingBean, DisposableBean {
  3. @Override
  4. public void afterPropertiesSet() throws Exception {
  5. ......
  6. // 通过反射的方式实例化 ZuulServlet 对象
  7. this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
  8. this.servletInstance.init(new DelegatingServletConfig());
  9. }
  10. }

2.3.2、ZuulServlet 主要功能作用

ZuulServlet 本身是一个 Servlet ,通过 service() 方法进行分析,代码如下:

  1. public class ZuulServlet extends HttpServlet {
  2. @Override
  3. public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
  4. try {
  5. // 通过该方法,能够知道 ZuulRunner 的生命周期为:一次请求
  6. init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
  7. // Marks this request as having passed through the "Zuul engine", as opposed to servlets
  8. // explicitly bound in web.xml, for which requests will not have the same data attached
  9. RequestContext context = RequestContext.getCurrentContext();
  10. // 标记请求为 zuul 请求
  11. context.setZuulEngineRan();
  12. // 进行 pre、route、post route 过滤处理
  13. try {
  14. preRoute();
  15. } catch (ZuulException e) {
  16. error(e);
  17. postRoute();
  18. return;
  19. }
  20. try {
  21. route();
  22. } catch (ZuulException e) {
  23. error(e);
  24. postRoute();
  25. return;
  26. }
  27. try {
  28. postRoute();
  29. } catch (ZuulException e) {
  30. error(e);
  31. return;
  32. }
  33. } catch (Throwable e) {
  34. error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
  35. } finally {
  36. RequestContext.getCurrentContext().unset();
  37. }
  38. }
  39. }

实现功能

  • a、标注请求为 zuul 请求
  • b、进行 pre、route、post route 过滤处理,而真正执行 过滤处理功能的是 ZuulRunner

2.4、ZuulRunner

2.4.1、ZuulRunner 功能作用

  1. public class ZuulRunner {
  2. public void postRoute() throws ZuulException {
  3. FilterProcessor.getInstance().postRoute();
  4. }
  5. public void route() throws ZuulException {
  6. FilterProcessor.getInstance().route();
  7. }
  8. public void preRoute() throws ZuulException {
  9. FilterProcessor.getInstance().preRoute();
  10. }
  11. public void error() {
  12. FilterProcessor.getInstance().error();
  13. }
  14. }

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

2.5、FilterProcessor

2.5.1、FilterProcessor 功能

  1. public class FilterProcessor {
  2. public void postRoute() throws ZuulException {
  3. runFilters("post");
  4. }
  5. public void error() {
  6. runFilters("error");
  7. }
  8. public void route() throws ZuulException {
  9. runFilters("route");
  10. }
  11. public void preRoute() throws ZuulException {
  12. runFilters("pre");
  13. }
  14. }

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

聚焦FilterProcessor#runFilters

  1. public class FilterProcessor {
  2. public Object runFilters(String sType) throws Throwable {
  3. if (RequestContext.getCurrentContext().debugRouting()) {
  4. Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
  5. }
  6. boolean bResult = false;
  7. List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
  8. if (list != null) {
  9. for (int i = 0; i < list.size(); i++) {
  10. ZuulFilter zuulFilter = list.get(i);
  11. Object result = processZuulFilter(zuulFilter);
  12. if (result != null && result instanceof Boolean) {
  13. bResult |= ((Boolean) result);
  14. }
  15. }
  16. }
  17. return bResult;
  18. }
  19. }

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

  • FilterLoader.getInstance().getFiltersByType
    根据 post、error、route、pre 类型,从 FilterLoader 中获取所有该类型的 ZuulFilter 过滤操作
  • FilterProcessor#processZuulFilter
    遍历查找到的 ZuulFilter ,并调用 ZuulFilter#runFilter 进行处理,获取成功或者失败的操作。

2.6、总结

zuul 由 servlet + filter 完成请求的处理。

请求交由 ZuulServlet 处理, ZuulServlet 根据 pre、post、route、error 过滤链进行请求过滤处理,最后得到处理结果。

三、扩展:ZuulFilter 过滤器如何被加载

3.1、ZuulFilter 存放容器

小贴士:FilterLoader 是一个饿汉模式的单例

FilterLoader 是存放 ZuulFilter 的容器,并提供有相关的 增删改查方法,其简单结构图如下:
03.png
ZuulFilter 真实存放在 FilterRegistry 中的 ConcurrentHashMap 中,

FilterLoader 有点装饰者模式的味道(类似于 Mybatis 的 二级 Cache),FilterLoader 在 FilterRegistry 的基础上提供了更丰富操作的增删改查。

3.1.1、FilterLoader#getFiltersByType ZuulFilter 查询方法

代码如下:

  1. public class FilterLoader {
  2. private FilterRegistry filterRegistry = FilterRegistry.instance();
  3. private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();
  4. public List<ZuulFilter> getFiltersByType(String filterType) {
  5. // 先从 FilterLoader 缓存中获取相关过滤类型的过滤链数据
  6. List<ZuulFilter> list = hashFiltersByType.get(filterType);
  7. if (list != null) return list;
  8. list = new ArrayList<ZuulFilter>();
  9. // 如果缓存中没有,从 FilterRegistry 中获取。
  10. Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
  11. for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
  12. ZuulFilter filter = iterator.next();
  13. if (filter.filterType().equals(filterType)) {
  14. list.add(filter);
  15. }
  16. }
  17. Collections.sort(list); // sort by priority
  18. hashFiltersByType.putIfAbsent(filterType, list);
  19. return list;
  20. }
  21. }

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

小贴士:上述代码存在代码:Collections.sort(list);。由此能够猜测 ZuulFilter 是存在执行顺序的。通过设定不同的优先级,来指定特定的 ZuulFilter 执行顺序。

3.1.2、FilterLoader#getInstance()#putFilter File 类型的 ZuulFilter 存储

  1. public class FilterLoader {
  2. public boolean putFilter(File file) throws Exception {
  3. String sName = file.getAbsolutePath() + file.getName();
  4. if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
  5. LOG.debug("reloading filter " + sName);
  6. filterRegistry.remove(sName);
  7. }
  8. ZuulFilter filter = filterRegistry.get(sName);
  9. if (filter == null) {
  10. Class clazz = COMPILER.compile(file);
  11. if (!Modifier.isAbstract(clazz.getModifiers())) {
  12. filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
  13. List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
  14. if (list != null) {
  15. hashFiltersByType.remove(filter.filterType()); //rebuild this list
  16. }
  17. filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
  18. filterClassLastModified.put(sName, file.lastModified());
  19. return true;
  20. }
  21. }
  22. return false;
  23. }
  24. }

主线逻辑:

  • 1、判定指定名称的 ZuulFilter 如果被修改过,清除 FilterRegistry 中对应的缓存数据
  • 2、实例化 指定目录中加载的 ZuulFilter (groovy 文件 ) 并存入 FilterRegistry 缓存。

在 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 未被修改,不处理。

3.2、加载 ZuulFilter

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

  • Groovy File
  • Bean config

3.2.1、ZuulFilter 获取渠道:Groovy File

Groovy Filter 能够动态加载 ZuulFilter。

执行 Groovy File 加载的类为 FilterFileManager

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

聚焦 FilterFileManager 相关代码

  1. public class FilterFileManager {
  2. static FilterFileManager INSTANCE;
  3. // 初始化方法
  4. public static void init(......){
  5. // 开启线程
  6. INSTANCE.startPoller();
  7. }
  8. // 以守护进程的方式启动了一个线程,线程通过 while 循环,定时加载 GroovyFile 文件
  9. void startPoller() {
  10. poller = new Thread("GroovyFilterFileManagerPoller") {
  11. public void run() {
  12. while (bRunning) {
  13. try {
  14. sleep(pollingIntervalSeconds * 1000);
  15. manageFiles();
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. };
  22. poller.setDaemon(true);
  23. poller.start();
  24. }
  25. // 加载 Groovy File 文件
  26. void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
  27. List<File> aFiles = getFiles();
  28. processGroovyFiles(aFiles);
  29. }
  30. // 调用 FilterLoader#putFile 将 Groovy File 文件加载到容器 FilterLoader(FilterRegistry)中
  31. void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {
  32. for (File file : aFiles) {
  33. FilterLoader.getInstance().putFilter(file);
  34. }
  35. }
  36. }

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

  • FilterFileManager#getFiles
    获取指定目录文件下的所有 ZuulFilter groovy 文件
  • FilterFileManager#processGroovyFiles
    调用 FilterLoader.getInstance().putFilter(file); 将 ZuulFilter 加载到 FilterRegistry 缓存中

且在上述代码中启动了一个守护线程,通过 死循环 + sleep 的方式,实现轮询某个文件路径实现,动态加载 ZuulFilter 的功能。

3.2.2、ZuulFilter 获取渠道:Bean config

通过 Spring Bean 的方式加载 ZuulFilter 。

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

  1. public class ZuulServerAutoConfiguration {
  2. // pre filters
  3. @Bean
  4. public ServletDetectionFilter servletDetectionFilter() {
  5. return new ServletDetectionFilter();
  6. }
  7. ......
  8. // post filters
  9. @Bean
  10. public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
  11. return new SendResponseFilter(zuulProperties);
  12. }
  13. ......
  14. }

而我们在开发中能够通过 继承 ZuulFilter 重写相关方 进行自定义 ZuulFilter(操作应用这里不进行展开

  1. @Component
  2. public class AccessFilter extends ZuulFilter {
  3. ......
  4. }

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

聚焦 ZuulFilterConfiguration

  1. public class ZuulServerAutoConfiguration {
  2. @Configuration
  3. protected static class ZuulFilterConfiguration {
  4. @Autowired
  5. private Map<String, ZuulFilter> filters;
  6. @Bean
  7. public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
  8. TracerFactory tracerFactory) {
  9. FilterLoader filterLoader = FilterLoader.getInstance();
  10. FilterRegistry filterRegistry = FilterRegistry.instance();
  11. return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
  12. filterLoader, filterRegistry);
  13. }
  14. }
  15. }

通过查看代码,这里拿到了全局的 FilterLoader ,全局的 FilterRegistry 以及所有的 ZuulFilter 。将这些作为 ZuulFilterInitializer 的构造参数。
猜测 ZuulFilter 存入 FilterRegistry 的操作在 ZuulFilterInitializer 中执行。

聚焦 ZuulFilterInitializer

  1. public class ZuulFilterInitializer {
  2. // 构造方法
  3. public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
  4. CounterFactory counterFactory, TracerFactory tracerFactory,
  5. FilterLoader filterLoader, FilterRegistry filterRegistry) {
  6. this.filters = filters;
  7. this.counterFactory = counterFactory;
  8. this.tracerFactory = tracerFactory;
  9. this.filterLoader = filterLoader;
  10. this.filterRegistry = filterRegistry;
  11. }
  12. @PostConstruct
  13. public void contextInitialized() {
  14. log.info("Starting filter initializer");
  15. TracerFactory.initialize(tracerFactory);
  16. CounterFactory.initialize(counterFactory);
  17. for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
  18. filterRegistry.put(entry.getKey(), entry.getValue());
  19. }
  20. }
  21. }

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

四、总结

02.png
Zuul 通过 Servlet 和 Filter 完成了网关的功能。请求以 ZuulServlet 为入口,经由各种 ZuulFilter 过滤处理,最后响应处理结果。