zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。

Zuul可以通过加载动态过滤机制,从而实现以下各项功能:

  • 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
  • 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
  • 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
  • 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
  • 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
  • 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。

网关zuul从1.0到2.0 经历了较大的变化,先从架构上看看吧

 zuul 1.0的架构

从上图看,

1.ZuulServlet负责接收请求,对filter进行处理

/**
* Core Zuul servlet which intializes and orchestrates zuulFilter execution
*
* @author Mikey Cohen
* Date: 12/23/11
* Time: 10:44 AM
*/
@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();
}
}

其中

FilterProcessor处理核心类

前置filter

runFilters("pre"); //前置filter类型

跳转filter

runFilters("route");

后置filter

runFilters("post");

2. zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。工作原理如下图所示

Zuul可以对Groovy过滤器进行动态的加载,编译,运行。FilterFileManager.java

/**
* This class manages the directory polling for changes and new Groovy filters.
* Polling interval and directories are specified in the initialization of the class, and a poller will check
* for changes and additions.
*
* @author Mikey Cohen
* Date: 12/7/11
* Time: 12:09 PM
*/
void processGroovyFiles(List<File> aFiles) throws Exception { List<Callable<Boolean>> tasks = new ArrayList<>();
for (File file : aFiles) {
tasks.add(() -> {
try {
return filterLoader.putFilter(file);
}
catch(Exception e) {
LOG.error("Error loading groovy filter from disk! file = " + String.valueOf(file), e);
return false;
}
});
}
processFilesService.invokeAll(tasks, FILE_PROCESSOR_TASKS_TIMEOUT_SECS.get(), TimeUnit.SECONDS);
}

3.对groovy文件的动态操作管理类FilterScriptManagerServlet

/**
* Servlet for uploading/downloading/managing scripts.
* <p/>
* <ul>
* <li>Upload scripts to the registry for a given endpoint.</li>
* <li>Download scripts from the registry</li>
* <li>List all revisions of scripts for a given endpoint.</li>
* <li>Mark a particular script revision as active for production.</li>
* </ul>
*/
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (!adminEnabled.get()) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Filter admin is disabled. See the zuul.filters.admin.enabled FastProperty.");
return;
} // retrieve arguments and validate
String action = request.getParameter("action");
/* validate the action and method */
if (!isValidAction(request, response)) {
return;
} // perform action
if ("UPLOAD".equals(action)) {
handleUploadAction(request, response);
} else if ("ACTIVATE".equals(action)) {
handleActivateAction(request, response);
} else if ("CANARY".equals(action)) {
handleCanaryAction(request, response);
} else if ("DEACTIVATE".equals(action)) {
handledeActivateAction(request, response);
} }

zuul 2.0架构

从上图可以看到:

1.Zuul引入了Netty和RxJava,正如之前的 ZuulFilter 分为了 Pre,Post,Route,Error,Zuul2的Filter分为三种类型

  • Inbound Filters: 在路由之前执行
  • Endpoint Filters: 路由操作
  • Outbound Filters: 得到相应数据之后执行

使用RxJava重写了Pre,Post,Route ZuulFilter的结构如下

ZuulServerChannelInitializer.java

    @Override
protected void initChannel(Channel ch) throws Exception
{
// Configure our pipeline of ChannelHandlerS.
ChannelPipeline pipeline = ch.pipeline(); storeChannel(ch);
addTimeoutHandlers(pipeline);
addPassportHandler(pipeline);
addTcpRelatedHandlers(pipeline);
addHttp1Handlers(pipeline);
addHttpRelatedHandlers(pipeline);
addZuulHandlers(pipeline);
}

其父类实现了addZuulHandlers方法

   protected void addZuulHandlers(final ChannelPipeline pipeline)
{
pipeline.addLast("logger", nettyLogger);
pipeline.addLast(new ClientRequestReceiver(sessionContextDecorator));
pipeline.addLast(passportLoggingHandler);
addZuulFilterChainHandler(pipeline);
pipeline.addLast(new ClientResponseWriter(requestCompleteHandler, registry));
} protected void addZuulFilterChainHandler(final ChannelPipeline pipeline) {
final ZuulFilter<HttpResponseMessage, HttpResponseMessage>[] responseFilters = getFilters( //1
new OutboundPassportStampingFilter(FILTERS_OUTBOUND_START),
new OutboundPassportStampingFilter(FILTERS_OUTBOUND_END)); // response filter chain
final ZuulFilterChainRunner<HttpResponseMessage> responseFilterChain = getFilterChainRunner(responseFilters,
filterUsageNotifier); // endpoint | response filter chain
final FilterRunner<HttpRequestMessage, HttpResponseMessage> endPoint = getEndpointRunner(responseFilterChain, //2
filterUsageNotifier, filterLoader); final ZuulFilter<HttpRequestMessage, HttpRequestMessage>[] requestFilters = getFilters( //3
new InboundPassportStampingFilter(FILTERS_INBOUND_START),
new InboundPassportStampingFilter(FILTERS_INBOUND_END)); // request filter chain | end point | response filter chain
final ZuulFilterChainRunner<HttpRequestMessage> requestFilterChain = getFilterChainRunner(requestFilters,
filterUsageNotifier, endPoint); pipeline.addLast(new ZuulFilterChainHandler(requestFilterChain, responseFilterChain));
}

调用Handler处理

    @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequestMessage) {
zuulRequest = (HttpRequestMessage)msg; //Replace NETTY_SERVER_CHANNEL_HANDLER_CONTEXT in SessionContext
final SessionContext zuulCtx = zuulRequest.getContext();
zuulCtx.put(NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, ctx);
zuulCtx.put(ZUUL_FILTER_CHAIN, requestFilterChain); requestFilterChain.filter(zuulRequest);
}
else if ((msg instanceof HttpContent)&&(zuulRequest != null)) {
requestFilterChain.filter(zuulRequest, (HttpContent) msg);
}
else {
LOG.debug("Received unrecognized message type. " + msg.getClass().getName());
ReferenceCountUtil.release(msg);
}
}

调用ZuulFilterChainRunner的filter方法

 @Override
public void filter(T inMesg, HttpContent chunk) {
String filterName = "-";
try {
Preconditions.checkNotNull(inMesg, "input message"); final AtomicInteger runningFilterIdx = getRunningFilterIndex(inMesg);
final int limit = runningFilterIdx.get();
for (int i = 0; i < limit; i++) {
final ZuulFilter<T, T> filter = filters[i];
filterName = filter.filterName();
if ((! filter.isDisabled()) && (! shouldSkipFilter(inMesg, filter))) {
final HttpContent newChunk = filter.processContentChunk(inMesg, chunk);
if (newChunk == null) {
//Filter wants to break the chain and stop propagating this chunk any further
return;
}
//deallocate original chunk if necessary
if ((newChunk != chunk) && (chunk.refCnt() > 0)) {
chunk.release(chunk.refCnt());
}
chunk = newChunk;
}
} if (limit >= filters.length) {
//Filter chain has run to end, pass down the channel pipeline
invokeNextStage(inMesg, chunk);
} else {
inMesg.bufferBodyContents(chunk); boolean isAwaitingBody = isFilterAwaitingBody(inMesg); // Record passport states for start and end of buffering bodies.
if (isAwaitingBody) {
CurrentPassport passport = CurrentPassport.fromSessionContext(inMesg.getContext());
if (inMesg.hasCompleteBody()) {
if (inMesg instanceof HttpRequestMessage) {
passport.addIfNotAlready(PassportState.FILTERS_INBOUND_BUF_END);
} else if (inMesg instanceof HttpResponseMessage) {
passport.addIfNotAlready(PassportState.FILTERS_OUTBOUND_BUF_END);
}
}
else {
if (inMesg instanceof HttpRequestMessage) {
passport.addIfNotAlready(PassportState.FILTERS_INBOUND_BUF_START);
} else if (inMesg instanceof HttpResponseMessage) {
passport.addIfNotAlready(PassportState.FILTERS_OUTBOUND_BUF_START);
}
}
} if (isAwaitingBody && inMesg.hasCompleteBody()) {
//whole body has arrived, resume filter chain
runFilters(inMesg, runningFilterIdx);
}
}
}
catch (Exception ex) {
handleException(inMesg, filterName, ex);
}
}

2.NettyClient

            if (filter.getSyncType() == FilterSyncType.SYNC) {
final SyncZuulFilter<I, O> syncFilter = (SyncZuulFilter) filter;
final O outMesg = syncFilter.apply(inMesg);
recordFilterCompletion(SUCCESS, filter, startTime, inMesg, snapshot);
return (outMesg != null) ? outMesg : filter.getDefaultOutput(inMesg);
} // async filter
filter.incrementConcurrency();
resumer = new FilterChainResumer(inMesg, filter, snapshot, startTime);
filter.applyAsync(inMesg)
.observeOn(Schedulers.from(getChannelHandlerContext(inMesg).executor()))
.doOnUnsubscribe(resumer::decrementConcurrency)
.subscribe(resumer);

ProxyEndpoint.java

    @Override
public HttpResponseMessage apply(final HttpRequestMessage input) {
// If no Origin has been selected, then just return a 404 static response.
// handle any exception here
try { if (origin == null) {
handleNoOriginSelected();
return null;
} origin.getProxyTiming(zuulRequest).start(); // To act the same as Ribbon, we must do this before starting execution (as well as before each attempt).
IClientConfig requestConfig = origin.getExecutionContext(zuulRequest).getRequestConfig();
originalReadTimeout = requestConfig.getProperty(ReadTimeout, null);
setReadTimeoutOnContext(requestConfig, 1); origin.onRequestExecutionStart(zuulRequest);
proxyRequestToOrigin(); //Doesn't return origin response to caller, calls invokeNext() internally in response filter chain
return null;
} catch (Exception ex) {
handleError(ex);
return null;
}
}

将请求转发至远端

    private void proxyRequestToOrigin() {
Promise<PooledConnection> promise = null;
try {
attemptNum += 1;
requestStat = createRequestStat();
origin.preRequestChecks(zuulRequest);
concurrentReqCount++; // update RPS trackers
updateOriginRpsTrackers(origin, attemptNum); // We pass this AtomicReference<Server> here and the origin impl will assign the chosen server to it.
promise = origin.connectToOrigin(zuulRequest, channelCtx.channel().eventLoop(), attemptNum, passport, chosenServer, chosenHostAddr); storeAndLogOriginRequestInfo();
currentRequestAttempt = origin.newRequestAttempt(chosenServer.get(), context, attemptNum);
requestAttempts.add(currentRequestAttempt);
passport.add(PassportState.ORIGIN_CONN_ACQUIRE_START); if (promise.isDone()) {
operationComplete(promise);
} else {
promise.addListener(this);
}
}
catch (Exception ex) {
LOG.error("Error while connecting to origin, UUID {} " + context.getUUID(), ex);
storeAndLogOriginRequestInfo();
if (promise != null && ! promise.isDone()) {
promise.setFailure(ex);
} else {
errorFromOrigin(ex);
}
}
}

调用BasicNettyOrigin

    @Override
public Promise<PooledConnection> connectToOrigin(HttpRequestMessage zuulReq, EventLoop eventLoop, int attemptNumber,
CurrentPassport passport, AtomicReference<Server> chosenServer,
AtomicReference<String> chosenHostAddr) {
return clientChannelManager.acquire(eventLoop, null, zuulReq.getMethod().toUpperCase(),
zuulReq.getPath(), attemptNumber, passport, chosenServer, chosenHostAddr);
}

3.小结

>> zuul2通过启动BaseServerStartup的实现类,启动一个netty server

>> netty server将ZuulFilter (InboundOutboundEndPoint)包裹成ChainRunner组合成netty的一个handler:ZuulFilterChainHandler

>> ZuulFilterChainHandler将请求包装成SyncZuulFilter封装成NettyClient

4.zuul1和zuul2的选择

性能对比

Zuul 1 (阻塞)的应用场景

cpu密集型任务

简单操作的需求

开发简单的需求

实时请求高的

zuul2(非阻塞)的应用场景

io密集的任务

大请求或者大文件

队列的流式数据

超大量的连接

参考文献

【1】https://www.cnblogs.com/lexiaofei/p/7080257.html

【2】https://blog.csdn.net/lengyue309/article/details/82192118

【】https://github.com/strangeloop/StrangeLoop2017/blob/master/slides/ArthurGonigberg-ZuulsJourneyToNonBlocking.pdf

Netflix网关zuul(1.x和2.x)全解析的更多相关文章

  1. 微服务之路由网关—zuul

    Zuul 简介Zuul 是 Netflix 公司开发的一个开源 APIGateway,其本质上是一个 WebServlet 应用.Zuul 的核心是一系列的 Filter. 为什么要使用 Zuul微服 ...

  2. 玩转SpringCloud(F版本) 四.路由网关(zuul)

    本篇文章基于: 01)玩转SpringCloud 一.服务的注册与发现(Eureka) 02) 玩转SpringCloud 二.服务消费者(1)ribbon+restTemplate 03) 玩转Sp ...

  3. 【Dalston】【第五章】API服务网关(Zuul) 上

    微服务场景下,每一个微服务对外暴露了一组细粒度的服务.客户端的请求可能会涉及到一串的服务调用,如果将这些微服务都暴露给客户端,那么客户端需要多次请求不同的微服务才能完成一次业务处理,增加客户端的代码复 ...

  4. 服务网关zuul之七:zuul中的动态刷新路由配置

    <spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后做些事情> <服务网关zu ...

  5. SpringCloud-微服务网关ZUUL(六)

    前言:前面说过,由于微服务过多,可能某一个小业务就需要调各种微服务的接口,不可避免的就会需要负载均衡和反向代理了,以确保ui不直接与所有的微服务接口接触,所以我们需要使用一个组件来做分发,跨域等各种请 ...

  6. 八、springcloud之服务网关zuul(一)

    一.Zuul简介 zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用. Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器. ...

  7. Spring Cloud 服务网关Zuul

    Spring Cloud 服务网关Zuul 服务网关是分布式架构中不可缺少的组成部分,是外部网络和内部服务之间的屏障,例如权限控制之类的逻辑应该在这里实现,而不是放在每个服务单元. Spring Cl ...

  8. Spring Cloud(十):服务网关 Zuul(路由)【Finchley 版】

    Spring Cloud(十):服务网关 Zuul(路由)[Finchley 版]  发表于 2018-04-23 |  更新于 2018-05-09 |  通过之前几篇 Spring Cloud 中 ...

  9. Spring Cloud(十一):服务网关 Zuul(过滤器)【Finchley 版】

    Spring Cloud(十一):服务网关 Zuul(过滤器)[Finchley 版]  发表于 2018-04-23 |  更新于 2018-05-07 |  在上篇文章中我们了解了 Spring ...

随机推荐

  1. python 编译源文件

    背景 近期项目到了部署的阶段.由于项目后台和算法都是用Python "撸的",但是又不希望将源代码直接 "release" 到 “客户”哪里.于是开始思考... ...

  2. quillJS 富文本编辑器源码分析系列1

    quillJS 富文本编辑器目前是一款很火富文本编辑器,使用广泛,github 上面的 star 有 22,492,虽然说不以 star 论英雄,不过这可以说明它还是比较受欢迎的: 它的特点是:轻量, ...

  3. Boosting(提升方法)之GBDT

    一.GBDT的通俗理解 提升方法采用的是加法模型和前向分步算法来解决分类和回归问题,而以决策树作为基函数的提升方法称为提升树(boosting tree).GBDT(Gradient Boosting ...

  4. 从git仓库导入idea的gradle项目无法添加依赖包问题

    引言 之前将项目导入到git仓库,后来同事从git仓库将项目导入到本地,发现无法导入依赖包. 解决方法 这个勾idea是默认勾上的,意思是idea将默认使用本地离线工作模式,使用的是本地仓库,每次依赖 ...

  5. Spring Boot 2.x整合Redis

    最近在学习Spring Boot 2.x整合Redis,在这里和大家分享一下,希望对大家有帮助. Redis是什么 Redis 是开源免费高性能的key-value数据库.有以下的优势(源于Redis ...

  6. Observer观察者模式与OCP开放-封闭原则

    目录 场景引入 在联网坦克项目中使用观察者模式 总结 在学习Observer观察者模式时发现它符合敏捷开发中的OCP开放-封闭原则, 本文通过一个场景从差的设计开始, 逐步向Observer模式迈进, ...

  7. 设计模式之面向切面编程AOP

    动态的将代码切入到指定的方法.指定位置上的编程思想就是面向切面的编程. 代码只有两种,一种是逻辑代码.另一种是非逻辑代码.逻辑代码就是实现功能的核心代码,非逻辑代码就是处理琐碎事务的代码,比如说获取连 ...

  8. C# 23种设计模式

    目录 0).简单工厂模式 1).工厂方法模式 2).抽象工厂模式 3).单例模式 4).构建者模式 5).原型模式 6).适配器模式 7).修饰者模式 8).代理模式 9).外观模式 10).桥接模式 ...

  9. SuperMap iObject入门开发系列之三管线系统标注

    本文是一位好友“托马斯”授权给我来发表的,介绍都是他的研究成果,在此,非常感谢. 管线系统会涉及到一些坐标标注,属性标注,提供给用户查询获取其需要的信息,这期的文章介绍的是基于超图iObject开发的 ...

  10. 第八周LINUX学习笔记

    vsftpd丶NFS丶SAMBA nfs基于rpcsamba基于cifs(smb)  DRBD: ftp:File Transfer protocol 文件传输协议 两个连接:       tcp:命 ...