原创: http://www.imet.me/post/2018/07-03_zuul_1.x_source/
zuul 是什么,为什么需要 zuul,zuul 实现原理?
一、zuul overview
1.1 什么是 Zuul?
zuul 作为云服务的边界服务(Edge Service),可以用来做统一授权、压力测试、金丝雀测试(Canary Testing)、动态路由、减负载以及和 netflix 其他套件一起协作
1.2 zuul as edge service
直接用官方用图:

如上图,zuul 处于内部服务的入口,起到边界服务(Edge Service)的作用
1.3 zuul 的工作原理
先不解释看官方图:

分析一下 zuul 的思路:
- ZuulServlet: zuul 的核心其实就是 ZuulServlet, 在 NIO 之前, java web 服务都是以 Servlet 作为入口提供服务的。
- ZuulFilter Runner: 通过 Runner 来串起来不同类型的 filter, 一个 http request 经过 “pre”, “route”, “post” 这三种类型的 filter。有点 servlet filter 的味道
- Filter: 可以在 Filter 里执行业务需要的逻辑。Filter 支持 java 类型的 filter 和 groovy 类型的 filter
- Filter Loader: 如果要做到动态添加或者移除 filter 的话,groovy 脚本动态编译是个不错的选择。通过监听指定目录,然后生成 filter 类,装载到 zuul runner
- Request Context: filter 如何获取 http request/response 呢。猜对了,ThreadLocal, 通过把 HttpRequest 和 HttpResposne Wrapper 起来,放到 threadlocal 变量里。
二、zuul-core + zuul-simple-webapp 源码解读之 ZuulServlet
先跑起工程再说,跑起 zuul-simple-webapp 工程,参考官方wiki
2.1 web.xml 声明
web.xml 声明完成了以下几件事情:
-
StartServer(ServletContextListener): 是 web 容器的监听器,在容器 context 初始化时做了以下几件事情:
- mock monitor
- 初始化 FilterFileManager: 去系统变量
zuul.filter.root目录下定时检查是否有新的脚本 - 初始化 JavaFilter
-
ZuulServlet: 是 Servlet 时代处理 http 请求的主要主体。我们源码会分析
-
ContextLifecycleFilter: 主要是及时清理 threadLocal 相关的变量: ZuulServlet 为了使 ZuulFilter 能获取Request相关的 Context, 把 requestContext 放到 threadlocal 中。
<listener>
<listener-class>com.netflix.zuul.StartServer</listener-class>
</listener>
<servlet>
<servlet-name>Zuul</servlet-name>
<servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Zuul</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>ContextLifecycleFilter</filter-name>
<filter-class>com.netflix.zuul.context.ContextLifecycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ContextLifecycleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.2 ZuulServlet
a.) ZuulServlet 是继承 HttpServlet 的
通过实现 Servlet 的 init 和 servic 来分别完成初始化和处理 http 逻辑。
b.) init 方法中创建 ZuulRunner 对象。
- 查看 Config param 里是否包含
buffer-requests - ZuulServlet 的实际执行都是在 ZuulRunner 去处理的
c.) service 方法干了以下几件事情
- 通过 ZuulRunner 初始化 request
.ZuulRunner.java
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
// 主要就是把 request 和 resposne 放到 requestContext 的 ThreadLocal 变量里
RequestContext ctx = RequestContext.getCurrentContext();
if (bufferRequests) {
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
ctx.setRequest(servletRequest);
}
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
- RequestContext 是以 ConcurrentHashMap 来存储 http 请求中 header,body 等各种参数的
- 同时通过 RequestContext.getCurrentContext() 来获取 threadlocal 中的 context
.RequestContext.java
public class RequestContext extends ConcurrentHashMap<String, Object> {
protected static Class<? extends RequestContext> contextClass = RequestContext.class;
private static RequestContext testContext = null;
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
// threadlocal 相关的设置
public static RequestContext getCurrentContext() {
if (testContext != null) return testContext;
RequestContext context = threadLocal.get();
return context;
}
}
- 然后 ZuulServlet 在
service()方法中完成preRoute(),route(),postRoute()还有error()的处理,当然在 finnaly,要 clean threadlocal requestContext
2.3 ZuulRunner
我们再来看 ZuulServlet 的劳工 Runner,所有的具体执行逻辑都交给 Runner 来执行
.ZuulServlet.java service() 方法调用相关
// 将 request, response 放到 requestContext hreadlocal
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}
// preRoute, route, postRoute, error, init 都是有 zuulRunner 来执行的。但是在 service 中进行了编排和异常处理
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
... route(), postRoute(), error()
再来看 ZuulRunner 中的 preRoute, route, postRoute 这三个方法都是有 FilterProcessor 来做
.ZuulRunner.java
// zuulRunner 通过 FilterProcessor 来执行相应类型的 filter
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
2.4 FilterProcessor
- preRoute(), postRoute, route() 都是由 runFilters 来执行的
- 在 runFilters 是通过 FilterLoader 来获取 filter 列表的
.FilterProcessor.java
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
// 通过 FilterLoader 来获取 filters
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;
}
- 然后挨个执行 processZuulFilter 方法,来获取 result
.FilterProcessor.java processZuulFilter()
// 主要内容就是 runFilter
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
2.5 ZuulFilter runFilter()
主要执行 某个 Filter 的 run() 方法,然后将结果放到 ZuulFilterResult 中
.ZuulFilter.java
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
2.6 filter 总结
至此,我们看到一个 filter 是如何被执行的了。
ZuulServlet.service()
-> ZuulRunner.xxRoute()
-> FilterProcessor.runFilters(byType)
-> xxZuulFilter.runFilter
-> xxZuulFilter.run()**
而其中的 request, resposne 变量传递是通过 RequestContext threadLocal
三、zuul-core 源码分析之 Filter 加载
3.1 FilterLoader 加载 filter
再看 FilterProcessor 是从 FilterLoader 获取 fitler list 的。
.FilterLoader.java 的 hashFiltersByType & filterRegistry
public class FilterLoader {
final static FilterLoader INSTANCE = new FilterLoader();
// 通过 4 个 ConcurrentHashMap 来维护 filter 信息
// 维护 xxGrovvyZuulFilter.groovy filter 文件的最近修改时间; key 为 filter 文件名(绝对路径), value 为文件最后修改时间
private final ConcurrentHashMap<String, Long> filterClassLastModified = new ConcurrentHashMap<String, Long>();
// 目测实际上没有进行相关处理操作 (目测代码很少的路径会走到这); key 为 filter 文件名, value 为 sourceCode
private final ConcurrentHashMap<String, String> filterClassCode = new ConcurrentHashMap<String, String>();
// 主要是用来检测 xxGrovvyZuulFilter 这样的 filter name 是否已经被注册 (目测代码很少的路径会走到这); key 为 filter 文件名, value 也为文件名
private final ConcurrentHashMap<String, String> filterCheck = new ConcurrentHashMap<String, String>();
// *** 这个是最主要的,根据 filter 类型: "pre", "post", "route" 等来返回对于的 filter list ***
private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();
// 而真正的 filterList 的引用都是存在 filterRegistry 里面。
// 可以看到 filterRegistry 存储 private final ConcurrentHashMap<String, ZuulFilter> filters
private FilterRegistry filterRegistry = FilterRegistry.instance();
static DynamicCodeCompiler COMPILER;
static FilterFactory FILTER_FACTORY = new DefaultFilterFactory();
}
3.2 FilterFileManager 管理加载
FilterFileManager 获取指定目录文件,FilterLoader.DynamicCodeCompiler 编译文件
- 我们找到了 filterList 的存储,再看什么时候放进来的
.FilterFileManager.java
// 由线程定时去处理文件
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 文件
void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
List<File> aFiles = getFiles();
processGroovyFiles(aFiles);
}
// 交给 FilterLoader 来去编译并注册 动态 Filter 文件
void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {
for (File file : aFiles) {
FilterLoader.getInstance().putFilter(file);
}
}
- FilterLoader putFilter 的过程
.FilterLoader.java putFilter()
public boolean putFilter(File file) throws Exception {
// 1. 获取绝对路径
String sName = file.getAbsolutePath() + file.getName();
// 2. 如果文件修改了,就从 filterRegistry map 里面删除掉
if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
LOG.debug("reloading filter " + sName);
filterRegistry.remove(sName);
}
// 3. 当再从 FilterRegistry 获取的时候,为空的话,就进行重新编译
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;
}
- StartServer 初始化的时候,初始化 groovyFilterManager
那什么时候初始化 FilterFileManager 的呢。我们可以看到在 web.xml 里面指定了 StartServer 作为 listner,然后在 listner 初始化中初始化 FilterFileManager
.StartServer.java
private void initGroovyFilterManager() {
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
String scriptRoot = System.getProperty("zuul.filter.root", "");
if (scriptRoot.length() > 0) scriptRoot = scriptRoot + File.separator;
try {
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(5, scriptRoot + "pre", scriptRoot + "route", scriptRoot + "post");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3.3 总结 FilterFile 动态加载的流程
StartServer.initGroovyFilterManager() // context 初始化
-> FilterFileManager.init() // 定时获取 filter 初始化
-> FilterLoader.putFilter() // 动态编译并加载 filter 文件到 FilterRegistry 的 ConcurrentHashMap 里
-> FilterProcessor.runFilters(byType) // 从 FilterLoader 获取 filter list
至此将 filter 工作流程串起来了。
参考
- zuul 官宣: Announcing Zuul: Edge Service in the Cloud
- zuul 2 官宣: Zuul 2 : The Netflix Journey to Asynchronous, Non-Blocking Systems
- zuul github wiki: How Zuul Works
- zuul simple webapp
本文详细介绍了Zuul作为API Gateway的角色及其工作原理。深入源码,分析了ZuulServlet如何处理HTTP请求,ZuulRunner如何串联过滤器,以及FilterLoader如何动态加载和管理过滤器。通过ZuulServlet的service方法,理解了request和response在filter之间的传递方式。
168万+

被折叠的 条评论
为什么被折叠?



