写于: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

# 源码追踪入口

# @EnableZuulProxy 入口

@EnableZuulProxy 该注解开启了 Zuul 功能

# @EnableZuulProxy 相关代码如下

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

@EnableZuulProxy 引入了配置类 ZuulProxyMarkerConfiguration

聚焦 ZuulProxyMarkerConfiguration 相关代码如下:

@Configuration
public class ZuulProxyMarkerConfiguration {
	@Bean
	public Marker zuulProxyMarkerBean() {
		return new Marker();
	}

	class Marker {

	}
}

小贴士

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

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

# zuul 工作流程

Zuul 工作流程图如下:

Zuul工作原理_总结流程图

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

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

# ZuulController

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

public class ZuulServerAutoConfiguration {
	@Bean
	public ZuulController zuulController() {
		return new ZuulController();
	}
}

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

public class ZuulController extends ServletWrappingController {

	public ZuulController() {
		setServletClass(ZuulServlet.class);
		setServletName("zuul");
		setSupportedMethods((String[]) null); // Allow all
	}

	@Override
	public ModelAndView handleRequest(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		try {
			// We don't care about the other features of the base class, just want to
			// handle the request
            // 调用的 ZuulServlet#service
			return super.handleRequestInternal(request, response);
		}
		finally {
			// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
            // 清除 ThreadLocal 上下文信息
			RequestContext.getCurrentContext().unset();
		}
	}

}

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

  • ZuulController 继承自 ServletWrappingController。

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

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

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

# ZuulServlet

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

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

}

# 2、ZuulServlet 主要功能作用

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

public class ZuulServlet extends HttpServlet {
	@Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            // 通过该方法,能够知道 ZuulRunner 的生命周期为:一次请求
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            // 标记请求为 zuul 请求
            context.setZuulEngineRan();
            // 进行 pre、route、post route 过滤处理
            try {
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
}

实现功能

  • a、标注请求为 zuul 请求

  • b、进行 pre、route、post route 过滤处理,而真正执行 过滤处理功能的是 ZuulRunner

# ZuulRunner

# ZuulRunner 功能作用

public class ZuulRunner {

	public void postRoute() throws ZuulException {
        FilterProcessor.getInstance().postRoute();
    }

    public void route() throws ZuulException {
        FilterProcessor.getInstance().route();
    }

	public void preRoute() throws ZuulException {
        FilterProcessor.getInstance().preRoute();
    }

    public void error() {
        FilterProcessor.getInstance().error();
    }
}

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

# FilterProcessor

# FilterProcessor 功能

public class FilterProcessor {
	public void postRoute() throws ZuulException {
    	runFilters("post");
    }
    public void error() {
        runFilters("error");
    }
    public void route() throws ZuulException {
        runFilters("route");
    }
    public void preRoute() throws ZuulException {
        runFilters("pre");
    }
}

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

聚焦FilterProcessor#runFilters

public class FilterProcessor {
	public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }
}

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

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

# 总结

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

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

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

# ZuulFilter 存放容器

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

FilterLoader 是存放 ZuulFilter 的容器,并提供有相关的 增删改查方法,其简单结构图如下:

ZuulFilter 真实存放在 FilterRegistry 中的 ConcurrentHashMap 中,

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

# FilterLoader#getFiltersByType ZuulFilter 查询方法

代码如下:

public class FilterLoader {
	private FilterRegistry filterRegistry = FilterRegistry.instance();

	private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();

	public List<ZuulFilter> getFiltersByType(String filterType) {

		// 先从 FilterLoader 缓存中获取相关过滤类型的过滤链数据
        List<ZuulFilter> list = hashFiltersByType.get(filterType);
        if (list != null) return list;

        list = new ArrayList<ZuulFilter>();

        // 如果缓存中没有,从 FilterRegistry 中获取。
        Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
        for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
            ZuulFilter filter = iterator.next();
            if (filter.filterType().equals(filterType)) {
                list.add(filter);
            }
        }
        Collections.sort(list); // sort by priority
        hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }
}

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

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

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

public class FilterLoader {
    public boolean putFilter(File file) throws Exception {
        String sName = file.getAbsolutePath() + file.getName();
        if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
            LOG.debug("reloading filter " + sName);
            filterRegistry.remove(sName);
        }
        ZuulFilter filter = filterRegistry.get(sName);
        if (filter == null) {
            Class clazz = COMPILER.compile(file);
            if (!Modifier.isAbstract(clazz.getModifiers())) {
                filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
                List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
                if (list != null) {
                    hashFiltersByType.remove(filter.filterType()); //rebuild this list
                }
                filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
                filterClassLastModified.put(sName, file.lastModified());
                return true;
            }
        }

        return false;
    }
}

主线逻辑:

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

# 加载 ZuulFilter

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

  • Groovy File
  • Bean config

# ZuulFilter 获取渠道:Groovy File

Groovy Filter 能够动态加载 ZuulFilter。

执行 Groovy File 加载的类为 FilterFileManager

FilterFileManager 是实现动态加载 ZuulFilter 管理类。

Zuul 默认没有开启动态加载 ZuulFilter 的方式,需要自己实例化 FilterFileManager 并修改 FilterLoader DynamicCodeCompiler 的 Compile 为 GroovyCompiler。

更多相关配置参考 Hystrix 官方提供的 StartServer.java

聚焦 FilterFileManager 相关代码

public class FilterFileManager {
    static FilterFileManager INSTANCE;
    // 初始化方法
    public static void init(......){
        // 开启线程
        INSTANCE.startPoller();
    }
    // 以守护进程的方式启动了一个线程,线程通过 while 循环,定时加载 GroovyFile 文件
    void startPoller() {
        poller = new Thread("GroovyFilterFileManagerPoller") {
            public void run() {
                while (bRunning) {
                    try {
                        sleep(pollingIntervalSeconds * 1000);
                        manageFiles();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        poller.setDaemon(true);
        poller.start();
    }
    // 加载 Groovy File 文件
	void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
        List<File> aFiles = getFiles();
        processGroovyFiles(aFiles);
    }

    // 调用 FilterLoader#putFile 将 Groovy File 文件加载到容器 FilterLoader(FilterRegistry)中
    void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {

        for (File file : aFiles) {
            FilterLoader.getInstance().putFilter(file);
        }
    }
}

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

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

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

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

# ZuulFilter 获取渠道:Bean config

通过 Spring Bean 的方式加载 ZuulFilter 。

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

public class ZuulServerAutoConfiguration {
	// pre filters
	@Bean
	public ServletDetectionFilter servletDetectionFilter() {
		return new ServletDetectionFilter();
	}
    ......
	// post filters

	@Bean
	public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
		return new SendResponseFilter(zuulProperties);
	}
	......
}

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

@Component
public class AccessFilter extends ZuulFilter {
	......
}

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

聚焦 ZuulFilterConfiguration

public class ZuulServerAutoConfiguration {
	@Configuration
	protected static class ZuulFilterConfiguration {

		@Autowired
		private Map<String, ZuulFilter> filters;

		@Bean
		public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
				TracerFactory tracerFactory) {
			FilterLoader filterLoader = FilterLoader.getInstance();
			FilterRegistry filterRegistry = FilterRegistry.instance();
			return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
					filterLoader, filterRegistry);
		}

	}
}

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

聚焦 ZuulFilterInitializer

public class ZuulFilterInitializer {

	// 构造方法
	public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
			CounterFactory counterFactory, TracerFactory tracerFactory,
			FilterLoader filterLoader, FilterRegistry filterRegistry) {
		this.filters = filters;
		this.counterFactory = counterFactory;
		this.tracerFactory = tracerFactory;
		this.filterLoader = filterLoader;
		this.filterRegistry = filterRegistry;
	}

	@PostConstruct
	public void contextInitialized() {
		log.info("Starting filter initializer");

		TracerFactory.initialize(tracerFactory);
		CounterFactory.initialize(counterFactory);

		for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
			filterRegistry.put(entry.getKey(), entry.getValue());
		}
	}
}

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

# 总结

Zuul工作原理_总结流程图

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

精彩内容推送,请关注公众号!
最近更新时间: 4/23/2020, 10:40:44 PM