一、简要介绍
Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.
这里有几个关键词
- front door
我们这里可以理解为请求网关,在底层服务和客户端/网页之间的通讯桥梁。我个人对zuul的理解更偏向于业务总线,服务编排,整合底层的各种基础服务。 - dynamic routing
动态路由 - resiliency
可伸缩 - security
安全性
wiki里面提到的3个使用场景 - Surgical Routing
- Stress Testing
- Multi-Region Resiliency
其实我个人认为有点夸大的成分,我理解这些都是动态路由的应用场景而已,而zuul的动态路由也只是说把拦截器的逻辑用groovy来写,通过这种方式来实现热部署,并不是什么稀奇的东西。
二、源码下载&启动
- 源码下载并编译
- 修改配置文件
application.properties
zuul.filters.root=zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters
filter的根目录修改一下
- 进程启动配置jvm的系统参数
-DTZ=GMT -Darchaius.deployment.environment=test -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Deureka.validateInstanceId=false -Deureka.mt.num_retries=1
- 运行com.netflix.zuul.sample.Bootstrap启动类
Zuul Sample: starting up.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Zuul Sample: finished startup. Duration = 26856 ms
2019-02-15 00:38:59,487 WARN io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'SO_KEEPALIVE' for channel '[id: 0x283bb17c]'
2019-02-15 00:38:59,487 WARN io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'SO_LINGER' for channel '[id: 0x283bb17c]'
2019-02-15 00:38:59,487 WARN io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'TCP_NODELAY' for channel '[id: 0x283bb17c]'
三、核心组件分析
这里对zuul分析的版本是v1.1.0。为什么选择zuul1的版本来分析,主要是考虑到第一版的特性会比较少,方便我了解zuul的核心功能。另外zuul1用的是BIO,代码看起来会简单不少。zuul2增加了很多新的特性和优化,后面会抽时间慢慢看完。
接下来分析下zuul-simple-webapp
web.xml configures a few things:
-
StartServer as a
ServletContextListener
that initializes the app. - ZuulServlet is a servlet that matches all requests. It performs the core Zuul Filter flow of executing pre, routing, and post Filters.
-
ContextLifecycleFilter is a servlet filter matching all requests. It cleans up the
RequestContext
after each request, ensuring isolation.
<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>
1. 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);
}
}
这里定义了groovy的编译器和Zuul的Filter的加载路径。
2. ZuulFilter的定义
zuulFilter即为zuul的过滤器实现。我们后续需要实现的各种过滤器都必须基于这个类来实现。接口比较简单,注释也写得很好,这里就不展开来讲。
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final DynamicBooleanProperty filterDisabled =
DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*
* @return A String representing that type
*/
abstract public String filterType();
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
*
* @return the int order of a filter
*/
abstract public int filterOrder();
/**
* By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
*
* @return true by default
*/
public boolean isStaticFilter() {
return true;
}
/**
* The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable
*
* @return
*/
public String disablePropertyName() {
return "zuul." + this.getClass().getSimpleName() + "." + filterType() + ".disable";
}
/**
* If true, the filter has been disabled by archaius and will not be run
*
* @return
*/
public boolean isFilterDisabled() {
return filterDisabled.get();
}
3. ZuulServlet.java
接下来看ZuulServlet,这部分是zuul最核心的逻辑。
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
zuulRunner = new ZuulRunner(bufferReqs);
}
首先会初始化ZuulRunner,其中bufferReqs这个属性其实就是控制Request是否使用bufferReader,允许多次从request里面读取内容。
ZuulRunner.java
/**
* sets HttpServlet request and HttpResponse
*
* @param servletRequest
* @param servletResponse
*/
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
RequestContext ctx = RequestContext.getCurrentContext();
if (bufferRequests) {
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
ctx.setRequest(servletRequest);
}
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
接下里我们看zuulSerlvet的核心逻辑。
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
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();
context.setZuulEngineRan();
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();
}
}
- preRoute和postRoute不管发生任何异常的情况下都会执行。如果在执行
preRoute的时候就发生异常,会跳过route的逻辑,直接到postRoute。 - 不管在哪一步发生异常都会执行error的过滤器。
另外RequestContext的作用为了保存请求和执行结果的上下文,方便在过滤器中传递。包括过滤器之间如果需要做传输传递的话也是依赖RequestContext来实现。
5.FilterProcessor.java
最后我们再看下执行过滤器的逻辑,实时上就是根据过滤器的类型拿到过滤器的链表,遍历执行
/**
* runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
*
* @param sType the filterType.
* @return
* @throws Throwable throws up an arbitrary exception
*/
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;
}
三、总结
zuul的代码不多,整体的解决思路有点类似spring的拦截器实现,只是spring拦截器一般面向的是方法,zuul建议面向的是服务(当然这个看个人的使用方式)。另外zuul使用允许过滤器使用groovy进行动态编译注入,不需要发版。我认为在解决问题的思路上并不是一个新的思路,只是说在分布式的场景下的一个应用场景罢了。当然zuul2增加了很多新的特性,这个是需要我这边去深入了解的。