Zuul 2.1.5 设计分析

前言

https://github.com/Netflix/zuul

zuul 是 SpringCloud 家族老兵,使用 Java 微服务大部分都在使用 zuul 作为网关。既然他如此重要,那么我们就来分析一下。本文分析的是 zuul 2.1.5版本。

调用链路

首先,我们知道,zuul 基于 Netty,Netty 是异步网络框架。我们从调用链路出发,分析下 Zuul 的调用链路是如何串起来的。

首先看官方介绍:

image.png

既然是基于 Netty 的,我们首先看 ChannelInboundHandler,Zuul 的 ChannelInboundHandler 名为 ClientRequestReceiver,用于接收 Http 请求。

然后,ClientRequestReceiver 会将请求交给 Zuul 的过滤器链,也就是我们经常编写的 Zuul 业务逻辑。Zuul 将整个过滤器链编织成一个 ZuulFilterChainHandler,作为 ClientRequestReceiver 的下一个 Handler。最后,当过滤器处理好逻辑并得到后端返回值,将 Response 交给 ClientResponseWriter,该类会将 Response 写回给客户端。代码如下:

image.png

好,我们知道了 Zuul 基于 Netty 是如何控制调用的,那么,我们再来看看,Zuul 是如何编织过滤器链的。代码就在 com.netflix.zuul.netty.server.BaseZuulChannelInitializer#addZuulFilterChainHandler 方法中。

image.png

从上面的截图,可以看到,Zuul 先是构造了 Response Filter Chain,然后再构造 Endpoint Chain,最后构造 Request Chain,形成了一张链表,并将起封装,添加到 Netty 的 pipeline 中。

Request Chain 作为头,Response Chain 作为尾,当 Response Chain 执行完之后,则会调用 ClientResponseWriter,将返回值写回 Http Client。

这里需要提一下 Endpoint Chain 概念,首先, Zuul 作为网关,核心职责是转发请求,而 endpoint 就是目标服务器,zuul 会将请求先经过 Request Chain,然后交给 Endpoint Chain,Endpoint Chain 则会请求后端服务器,拿到返回值,然后将返回值交给 Response Chain。

那么 Zuul 是如何请求后端服务的呢?

我们看 ZuulEndPointRunner 这个类,他是 Endpoint Chain 的标准实现。filter 方法是关键。

image.png

我们看到,这里有2个红框,第一个是获取过滤器,然后,执行过滤器逻辑,得到结果。从 getEndpoint 方法看,通常我们会得到 ProxyEndpoint 过滤器,他继承自 SyncZuulFilter。分支逻辑暂时不讲。

当得到 ProxyEndpoint 后,则会执行 apply 方法。

image.png

proxyRequestToOrigin 方法,背后是 Netty 客户端请求后端服务。该方法会先连接后端服务,然后在回调中,将 Request 内容发送给后端服务。注意,发送之前,会将 此客户端的异步接收类 OriginResponseReceiver 添加到 pipeline 中,用于处理后端返回值。OriginResponseReceiver 得到返回值后,则会调用 ProxyEndpoint 的 responseFromOrigin 方法,该方法,则会将返回值交给 Response Chain。Response Chain 处理完之后,再交给 ClientResponseWriter,完成一次完整的调用。

整个调用图如下:

image.png

上图展示了一次请求的整个过程。

我们继续讨论下一个问题:Zuul 如何识别请求,并将其正确的转发的目标服务器?

假设,zuul ip port 为172.3.3.3:8080,后端服务为 172.2.2.2:8888,服务名为 userService,zuul

如何将请求转发到 172.2.2.2:8888?

首先需要定义规则,我们将服务名作为标识,通过服务名去 Eureka 找到服务地址,然后再进行调用。标识放在哪里?我们可以放在 URL 里面,比如 172.3.3.3:8080/userService/getUser 这个 url ,实际是请求的 172.2.2.2:8888/getUser 这个服务。当然,也可以将服务名放在 header 里。看自己的实现。

当 Zuul 拿到标识,则会请求 Eureka,拿到 ip 地址,构造一个 Netty 客户端,即 ProxyEndpoint 的 NettyOrigin 属性。当调用 connectToOrigin 方法后,会返回一个 Netty 的 promise,ProxyEndpoint 会将自己的调用逻辑放在回调中,即,NettyOrigin connect 成功后,ProxyEndpoint 会发送数据给后端服务器。

我们查看 com.netflix.zuul.netty.connectionpool.DefaultClientChannelManager#acquire 方法,该方法,会先调用 Server chosenServer = loadBalancer.chooseServer(key), 得到一个服务器ip port。然后得到一个 PerServerConnectionPool,

该类的 tryMakingNewConnection 方法,会构造一个 Netty 客户端。

image.png

至此,我们已经知道了 Zuul 整个的调用链路是如何实现的。

异步分析

Zuul 是如何基于 Netty 实现异步的呢?

首先入口 Handler 是 ClientRequestReceiver,当 ClientRequestReceiver 拿到请求后,会将 Request 交过过滤器链,而 Zuul 的过滤器是支持异步的。基于 RxJava 加入了 applyAsync 方法。

ProxyEndpoint 在请求后端服务时,也是异步的,ProxyEndpoint 的 apply 方法里,将自己的业务逻辑,放到了 zuul 客户端的回调里,这是基于 Netty 实现的。

流程图如下:

image.png

可以看到,Netty IO 线程,基本没有等待,从流量进来之后,要么在执行 Request chain 逻辑,要么在构建 Netty Client,没有任何等待 IO 的过程,当 Endpoint 在请求后端服务时,IO 线程是空闲的。而当后端返回时,会触发 Netty 客户端的回调,进而触发 Zuul Response 过滤器链,最后写回 ClientResponseWriter。

有个问题,异步返回时,是如何找到 ClientResponseWriter 的呢?Zuul 的实现方式是,将 ChannelHandlerContext 保存到 Session 里(_netty_server_channel_handler_context),当异步返回时,则会继续调用这个 ChannelHandlerContext 后面的 Handler 的 channelRead 方法。

Zuul 的 worker EventLoop grop 名为 Salamander-ClientToZuulWorker;

在构造请求 后端服务的 Netty 客户端时,也是使用的该 EventLoop。如下图:

image.png

可以看到,处理请求 和 处理返回值的,是同一个线程。为什么不为每个 Netty Client 设计一个 Eventloop group 呢?我的理解是代价太大了,网关会 连接成千上万个后端,如果每个 Netty Client 都配置一个 Group,那服务器估计要爆炸。

参考

开源 Zuul 2(Netflix 技术博客

https://netflixtechblog.com/open-sourcing-zuul-2-82ea476cb2b3

Zuul 2:Netflix 的异步、非阻塞系统之旅(Netflix 技术博客

https://netflixtechblog.com/zuul-2-the-netflix-journey-to-asynchronous-non-blocking-systems-45947377fb5c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容