Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器,是开源的微服务网关。在 SpringCloud 体系中,Zuul 担任着网关的角色,对发送到服务端的请求进行一些预处理,比如安全验证、动态路由、负载分配等。
Zuul 的核心是 Filters,根据执行时期分为以下几类:
- PRE:这种过滤器在请求被路由之前调用
- ROUTING:这种过滤器将请求路由到微服务
- POST:这种过滤器在路由到微服务以后执行
- ERROR:在其他阶段发生错误时执行该过滤器
一、zuul 1.x
1、工作原理
- Zuul Servlet:zuul 的入口,所有的请求都从 servlet 进来,经过处理后,在从 servlet 返回;
- ZuulFilter Runner:zuul 在 runner 中对一个 http 请求执行不同类型的 filter,filter 共有三种类型:pre、route、post;
- Filter:filter 提供具体的过滤逻辑,zuul 提供 java 和 groovy 两种类型的 filter;
- Filter Loader:用来管理 filter,提供 filter 添加和移除的操作,ZuulFilter Runner 通过 Filter Loader 来获取 filter 列表;
- Request Context:context 中维护一个 ThreadLocal 的对象,该对象中存储这 http 请求的各种信息以供 filter 获取;
Zuul 的主体运行流程:
- 先通过 StartServer 初始化,启动 ZuulServlet;
- 在 ZuulServlet 中初始化 ZuulRunner,并调用 ZuulRunner 的方法来处理具体的 http 请求;
- ZuulRunner 调用 FilterProcessor 来进行具体的逻辑处理;
- FilterProcessor 通过 FilterLoader 来获取 Filter 列表并循环调用,通过 processZuulFilter 方法来调用每个 filter,processZuulFilter 中调用了每个 filter 的 runFilter 方法;
- Filter 列表中每个 Filter 都是 ZuulFilter 类型,并且实现了 run() 方法,run() 方法在 ZuulFilter 抽象类的 runFilter 方法中被调用;
2、源码
1. 代码目录
2. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<listener>
<!--1、初始化 mock moniter;2、初始化 filter file manager;3、初始化 filter -->
<listener-class>com.netflix.zuul.StartServer</listener-class>
</listener>
<servlet>
<!-- 用来处理 http 请求的核心模块,后面详细说明 -->
<servlet-name>ZuulServlet</servlet-name>
<servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ZuulServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<!-- 用来清理 context 中的变量,后面详细说明 -->
<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>
</web-app>
3. 核心逻辑:zuul-core
-
ZuulServlet
ZuulServlet 继承于 HttpServlet,重写了 HttpServlet 的 init 和 service 方法,并提供了 init、preRoute、route、postRoute、error 等处理方法;
- init ():初始化了 zuulRunner,ZuulServlet 中对 http 请求的处理逻辑均由 zuulRunner 完成;
- service ():通过对 init、preRoute、route、postRoute、error 等方法的调用,实现了处理 http 请求的具体流程;
init、preRoute、route、postRoute、error 方法均调用了 zuulRunner 的方法;
public class ZuulServlet extends HttpServlet {
private static final long serialVersionUID = -3374242278843351500L;
private ZuulRunner zuulRunner;
@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);
}
@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();
}
}
void postRoute() throws ZuulException {
zuulRunner.postRoute();
}
void route() throws ZuulException {
zuulRunner.route();
}
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
zuulRunner.error();
}
}
- ZuulRunner
ZuulRunner 为 ZuulServlet 提供处理 http 请求的具体操作,ZuulRunner 的 init 方法中将 http 请求的上下文信息存放到 RequestContext 中,preRoute、route、postRoute 方法中调用了 FilterProcessor 的逻辑;
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));
}
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();
}
- FilterProcessor
FilterProcessor 从 FilterLoader 中获取 Filter 列表,依次执行,列表类型有四种:pre、route、post、error;
public void postRoute() throws ZuulException {
try {
runFilters("post");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
}
}
public void error() {
try {
runFilters("error");
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
public void route() throws ZuulException {
try {
runFilters("route");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
}
}
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());
}
}
/**
* 根据 type 获取 filter 列表,在 for 循环中依次执行,调用 processZuulFilter 方法
*/
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;
}
调用 filter 的具体逻辑在 processZuulFilter 中实现,主要调用了 ZuulFilter 的 runFilter 方法,runFilter 方法中执行某个 filter 的 run() 方法,这个方法在自定义 filter 的时候需要用户自己实现;
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
RequestContext copy = null;
Object o = null;
Throwable t = null;
if (bDebug) {
Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
copy = ctx.copy();
}
// 这里调用 filter 的 runFilter 方法
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
switch (s) {
case FAILED:
t = result.getException();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
if (bDebug) {
Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
Debug.compareContextState(filterName, copy);
}
break;
default:
break;
}
if (t != null) throw t;
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
if (bDebug) {
Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
}
usageNotifier.notify(filter, ExecutionStatus.FAILED);
if (e instanceof ZuulException) {
throw (ZuulException) e;
} else {
ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
- ZuulFilter
ZuulFilter 是一个抽象类,实现了 IZuulFilter 接口,IZuulFilter 接口中声明了 run() 方法,所有的 filter 实例必须实现这个方法,自定义的 filter 继承于 ZuulFilter;
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;
}
二、zuul 2.x
工作目录
- Bootstrap.start() 启动 netty 工程
public class Bootstrap {
public static void main(String[] args) {
new Bootstrap().start();
}
public void start() {
System.out.println("Zuul Sample: starting up.");
long startTime = System.currentTimeMillis();
int exitCode = 0;
Server server = null;
try {
ConfigurationManager.loadCascadedPropertiesFromResources("application");
Injector injector = InjectorBuilder.fromModule(new ZuulSampleModule()).createInjector();
/**
* 获取 serverStartup 对象,demo 中实现了一个 SampleServerStartup,继承于 BaseServerStartup;
* SampleServerStartup 重写了 choosePortsAndChannels 方法,该方法返回一个 map,key 为 port,value 为 ChannelInitializer;
* 在构建 Server 对象的时候将这个 map 以参数的形式传入;
*/
BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class);
// 从 serverStartup 获取 server
server = serverStartup.server();
long startupDuration = System.currentTimeMillis() - startTime;
System.out.println("Zuul Sample: finished startup. Duration = " + startupDuration + " ms");
// 启动
server.start(true);
}
catch (Throwable t) {
t.printStackTrace();
System.err.println("###############");
System.err.println("Zuul Sample: initialization failed. Forcing shutdown now.");
System.err.println("###############");
exitCode = 1;
}
finally {
// server shutdown
if (server != null) server.stop();
System.exit(exitCode);
}
}
}
- Server通过 start 方法启动
public class Server
{
......
public Server(Map<Integer, ChannelInitializer> portsToChannelInitializers, ServerStatusManager serverStatusManager, ClientConnectionsShutdown clientConnectionsShutdown, EventLoopGroupMetrics eventLoopGroupMetrics, EventLoopConfig eventLoopConfig)
{
// 由 SampleServerStartup 创建 Server 对象时传入
this.portsToChannelInitializers = portsToChannelInitializers;
......
}
......
public void start(boolean sync)
{
serverGroup = new ServerGroup("Salamander", eventLoopConfig.acceptorCount(), eventLoopConfig.eventLoopCount(), eventLoopGroupMetrics);
serverGroup.initializeTransport();
try {
List<ChannelFuture> allBindFutures = new ArrayList<>();
// Setup each of the channel initializers on requested ports.
for (Map.Entry<Integer, ChannelInitializer> entry : portsToChannelInitializers.entrySet())
{
// setupServerBootstrap 方法将 ServerBootstrap 与 channelInitializer 绑定;
// 参数中:entry.getKey 为 port,将与 ServerBootstrap 绑定,entry.getValue 为 channelInitializer,将作为 ServerBootstrap 的 handler;
// netty 中通过 handler 处理数据,这里是 zuul 的处理逻辑;
allBindFutures.add(setupServerBootstrap(entry.getKey(), entry.getValue()));
}
// Once all server bootstraps are successfully initialized, then bind to each port.
for (ChannelFuture f: allBindFutures) {
// Wait until the server socket is closed.
ChannelFuture cf = f.channel().closeFuture();
if (sync) {
cf.sync();
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private ChannelFuture setupServerBootstrap(int port, ChannelInitializer channelInitializer)
throws InterruptedException
{
ServerBootstrap serverBootstrap = new ServerBootstrap().group(
serverGroup.clientToProxyBossPool,
serverGroup.clientToProxyWorkerPool);
// Choose socket options.
Map<ChannelOption, Object> channelOptions = new HashMap<>();
channelOptions.put(ChannelOption.SO_BACKLOG, 128);
//channelOptions.put(ChannelOption.SO_TIMEOUT, SERVER_SOCKET_TIMEOUT.get());
channelOptions.put(ChannelOption.SO_LINGER, -1);
channelOptions.put(ChannelOption.TCP_NODELAY, true);
channelOptions.put(ChannelOption.SO_KEEPALIVE, true);
// Choose EPoll or NIO.
if (USE_EPOLL.get()) {
LOG.warn("Proxy listening with TCP transport using EPOLL");
serverBootstrap = serverBootstrap.channel(EpollServerSocketChannel.class);
channelOptions.put(EpollChannelOption.TCP_DEFER_ACCEPT, Integer.valueOf(-1));
}
else {
LOG.warn("Proxy listening with TCP transport using NIO");
serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
}
// Apply socket options.
for (Map.Entry<ChannelOption, Object> optionEntry : channelOptions.entrySet()) {
serverBootstrap = serverBootstrap.option(optionEntry.getKey(), optionEntry.getValue());
}
serverBootstrap.childHandler(channelInitializer);
serverBootstrap.validate();
LOG.info("Binding to port: " + port);
// Flag status as UP just before binding to the port.
serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP);
// Bind and start to accept incoming connections.
return serverBootstrap.bind(port).sync();
}
......
}