Zuul 2.1.5 设计分析
前言
https://github.com/Netflix/zuul
zuul 是 SpringCloud 家族老兵,使用 Java 微服务大部分都在使用 zuul 作为网关。既然他如此重要,那么我们就来分析一下。本文分析的是 zuul 2.1.5版本。
调用链路
首先,我们知道,zuul 基于 Netty,Netty 是异步网络框架。我们从调用链路出发,分析下 Zuul 的调用链路是如何串起来的。
首先看官方介绍:

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

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

从上面的截图,可以看到,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 方法是关键。

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

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

上图展示了一次请求的整个过程。
我们继续讨论下一个问题: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 客户端。

至此,我们已经知道了 Zuul 整个的调用链路是如何实现的。
异步分析
Zuul 是如何基于 Netty 实现异步的呢?
首先入口 Handler 是 ClientRequestReceiver,当 ClientRequestReceiver 拿到请求后,会将 Request 交过过滤器链,而 Zuul 的过滤器是支持异步的。基于 RxJava 加入了 applyAsync 方法。
ProxyEndpoint 在请求后端服务时,也是异步的,ProxyEndpoint 的 apply 方法里,将自己的业务逻辑,放到了 zuul 客户端的回调里,这是基于 Netty 实现的。
流程图如下:

可以看到,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。如下图:

可以看到,处理请求 和 处理返回值的,是同一个线程。为什么不为每个 Netty Client 设计一个 Eventloop group 呢?我的理解是代价太大了,网关会 连接成千上万个后端,如果每个 Netty Client 都配置一个 Group,那服务器估计要爆炸。
参考
开源 Zuul 2(Netflix 技术博客)
https://netflixtechblog.com/open-sourcing-zuul-2-82ea476cb2b3
Zuul 2:Netflix 的异步、非阻塞系统之旅(Netflix 技术博客)
Zuul 2.1.5 设计分析的更多相关文章
- C#小程序呢飞行棋设计分析
C#小程序飞行棋,程序效果图 1.设计分析 这个程序界面大致分为四部分: ① 最上面游戏名字界面 ②信息提示区 ③游戏界面区 ④游戏操作提示区 2.分区设计实现 一.游戏界面显示区,由于只需要显示出图 ...
- Web API应用架构设计分析(2)
在上篇随笔<Web API应用架构设计分析(1)>,我对Web API的各种应用架构进行了概括性的分析和设计,Web API 是一种应用接口框架,它能够构建HTTP服务以支撑更广泛的客户端 ...
- 脊柱外科病人资料管理系统的界面设计分析(2)--JOA评分记录的实现
在上篇随笔<脊柱外科病人资料管理系统的界面设计分析>中介绍了一些常用的界面设计方面的内容,本篇继续上一篇,介绍脊柱外科病人管理系统的JOA评分记录模块的界面设计以及实现方面的内容. JOA ...
- Magento架构分析,Magento MVC 设计分析
Magento架构分析,Magento MVC 设计分析 分类:Magento 标签:Magento MVC.Magento架构 669人浏览 Magento 采用类似 JAVA的架构,其扩展与稳定性 ...
- Netflix Zuul 了解
Zuul 是提供动态路由,监控,弹性,安全等的边缘服务.Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门.Zuul 可以适当的对多个 Amazon Auto Scal ...
- netflix zuul 学习
netflix zuul 是netflix开发的一个EDGE SERVICE. 主要是作为一个API Gateway 服务器,可以实现安全,流量控制等功能. 我看的是1.x的版本,Zuul1.x的实现 ...
- SpringCloud网关ZUUL集成consul
最近一直在搞基于springcloud的微服务开发,为了不限定微服务开发语言,服务发现决定采用consul不多说上代码 pom文件 <project xmlns="http://mav ...
- springcloud(十):服务网关zuul
前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,似乎一个 ...
- XCOM2中敌对生物设计分析(ADVENT篇)
最近,在制作游戏Demo--DroneAssmble的过程中,对于敌对生物的设计,参考了幽浮系列的相关设定,因此着手对幽浮2中的主要敌人进行分析. 我们知道, XCOM2中的敌对生物主要由" ...
- XCOM2中敌对生物设计分析(Aliens篇)
Aliens Aliens作为游戏设定中入侵的外星人,有各式外貌及奇特的战斗方式,掌握一些高能科技或利用精神力量进行攻击 Sectoid 使用灵能战斗的外星人,并无高级版本,初级便会使用精神控制,生命 ...
随机推荐
- 2023-06-26:在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于 关闭 状态 给你一个由灯的位置组成的二维数组 lamps 其中 lamps[i] = [rowi,
2023-06-26:在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于 关闭 状态 给你一个由灯的位置组成的二维数组 lamps 其中 lamps[i] = [rowi, ...
- Day01_Java作业
A:选择题 1:下列标识符哪个是合法的(a) A.class B.$abc C.1234 D.Car.taxi B:填空题 1: java源程序的扩展名是( .java ) 2: java程序经编译后 ...
- C补充
C语言之unsigned 与signed 无符号与有符号数据的操作区别在于当最高位,当最高位为0时都一样: 在C中,默认的基础数据类型均为signed,现在我们以char为例,说明(signed) c ...
- DLL注入-Windows消息钩取
0x01 钩子 钩子,英文Hook,泛指钓取所需东西而使用的一切工具.后来延伸为"偷看或截取信息时所用的手段或工具". 挂钩:为了偷看或截取来往信息而在中间设置岗哨的行为 钩取:偷 ...
- MyBatis 常用工具类
SQL 类 MyBatis 提供了一个 SQL 工具类,使用这个工具类,我们可以很方便在 Java 代码动态构建 SQL 语句 String newSql = new SQL() ({ SELECT( ...
- linux 查看进程使用的内存大小
你可以使用 ps 命令结合 grep 命令来查看进程使用的内存大小.以下是示例代码: ps aux | grep <进程名> 这个命令会列出所有匹配 <进程名> 的进程,并显示 ...
- python-gitlab 一个简单demo
背景 需要收集git仓库信息到数据库供前端展示 包括:仓库信息.仓库所有者.成员列表.提交信息.活跃情况等 需要定时启动.灵活触发 实现简介 使用gitlab v4 restful 接口 使用pyth ...
- Unity的IFilterBuildAssemblies:深入解析与实用案例
Unity IFilterBuildAssemblies Unity IFilterBuildAssemblies是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目时自定义哪些程序集需要 ...
- Redis从入门到放弃(8):哨兵模式
在前面的文章中介绍了Redis的主从复制,但主从复制存在一定的缺陷.如果Master节点宕机,因为不具备自动恢复功能,需要人工干预,那么在这个干预过程中Redis将不可用. 为了解决这一问题,Redi ...
- Linux下apt与dpkg的详解
apt是一个包管理工具,用于管理Debian和Ubuntu等基于Debian的Linux发行版中的软件包.它是"Advanced Packaging Tool"的缩写,允许用户在系 ...