2019 年 10 月 27 日,又拍云联合 Apache APISIX 社区举办 API 网关与高性能服务最佳实践丨Open Talk 杭州站活动,来自阿里巴巴的技术专家王发康作了题为《阿里七层流量入口负载均衡算法演变之路》的分享。本次活动,邀请了来自阿里巴巴、蚂蚁金服、Apache APISIX、PolarisTech、又拍云等企业的技术专家,分享网关和高性能服务的实战经验。html
王发康,阿里巴巴 Tengine 开源项目 maintainer,负责阿里集团 WEB 统一接入层的开发及维护。程序员
如下是分享全文:算法
你们下午好,我叫王发康,来自阿里巴巴 Tengine 团队,目前主要负责阿里七层流量入口的开发与维护。今天演讲的主题是《阿里七层流量入口负载均衡算法演变之路》,主要从四个方面介绍:小程序
从 2011 年至今,Tengine 在开源的道路上已走过第八个年头,感谢社区贡献者及广大用户的支持。下面先介绍下 Tengine 与 Nginx 的区别:后端
你们都知道 Nginx 的性能很是高,C1000K 都不成问题;同时,Nginx 的生态也比较丰富,不只能够做为 HTTP 服务器,也能够做为 TCP 和 UDP,功能强大;它还能够和 K8s、Mesh、Serverless等其余生态打通,也包括 Lua、Js 语言支持;Nginx 模块化作的很好,支持动态加载,能够很方便的将本身写模块扩展进去。安全
Tengine 是 100% 基于 Nginx 开发的,也就是说 Nginx 有的,Tengine 都有,Nginx 没有的,Tengine 也能够有。兼容并包是 Tengine 研发的重要思路, 除了 100% 继承 Nginx,也结合阿里大规模场景应用开发了众多高级特性:好比秉承软硬件结合的优化思想,经过 QAT 硬件卸载 HTTPS、Gzip;使用 AliUstack 用户态协议栈绕过内核、避免软中断、减小数据拷贝等操做来提升性能,另外包括一些动态服务发现 、后端的 RPC 通讯如 Dubbo 协议的实现。同时,咱们还有大规模的流量验证场景,即每一年双十一的洪峰流量,使得其稳定性等方面获得充分验证。性能优化
根据 W3C 数据显示,目前 Tengine 和 Nginx 在 Top 1000 到 4000大型网站中使用占比已接近 50%,这得益于其在高性能、稳定、易用性、功能强大等方面都作的比较极致。服务器
统一接入层架构
Tengine 做为阿里集团七层流量入口核心系统,支撑着阿里巴巴历年双 11 等大促活动平稳度过。基于 Tengine,阿里研发了新的产品——统一接入层。负载均衡
统一接入层,指的是设置专属一层,统一接入全部流量,包括 PC 流量、无线流量、IoT 流量。从入口进来,通过四层的 SLB,直接到达七层的 Tengine 组成一个集群,经过它进行 HTTPS 的卸载、链路追踪、单元化、灰度分流,以及一些安全清洗等。
若是没有统一接入层,以前的业务方,例如购物车、商品等都要本身维护一个网关,这就涉及到维护成本和机器成本,例如卸载 HTTPS,若是全部业务方都要申请证书,那形成的应用成本是很是高的。可若是将全部功能全放在这一层进行,好处很是明显:一方面是机器集中管理节省成本;另一方面,若是遇到新的瓶颈能够在统一接入层集中优化,如请求响应 Body 统一在这一层进行压缩减小带宽消耗,压缩会消耗 CPU,能够在这一层经过硬件加速的方式集中优化等。
Nginx SWRR 算法
前面提到,请求流量进来后须要经过负载均衡的算法进行调度,均匀地分配到后端。负载均衡算法很是多,例如一致性哈希,IP 哈希、Session、Cookie 等各类算法,今天主要讲的是 WRR 算法,即增强轮询算法。
WRR 算法是 Nginx 官方的,咱们平常用的 Upstream 中,若是不配置算法,那么默认的就是 SWRR 算法。最先的 Nginx 官方实际上是 WRR 算法,后来通过改造演变成 SWRR 算法,“S” 即 Smooth,是平滑的意思。
如上图,咱们简单看下 Nginx SWRR 算法的请求处理过程。假设有三台机器 a、b、c,权重分别是 五、一、1。请求编号指的是第一个请求、第二个请求、第三个请求。第一个请求 Nginx SWRR 算法会选择权重最大的 a 机器。选中 a 机器后,就会对其进行降权处理,这样能够下降下一次被选中的几率。而 b、c 两台机器本次没被选中,下一次就要提升它的权重。降权过程为:a 机器被选中后,当前的权重减去总权重,即 5 减 7 得负 2;下一轮选择开始时,还须要加上上一轮机器自己的权重 5,负 2 加 5 就是 3,1 加 1 变成 2,由此本来权重 五、一、1 的 a、b、c 三台机器本轮权重分别是 三、二、2。
第二个请求进来时,依然选择权重最大的机器,仍是 a。接下去的流程没必要细说,算法流程是同样的,总体过程均按照被选中机器须要减去总权重的规则,为了下降其下次被选择的几率。所有请求走完,被选中的机器顺序就是 a、a、b、c、b、a、c、a、a。
WRR 算法实现有很是多种,最多见的就是随机数算法。普通 WRR 选择的顺序就是 c、b、a、a、a、a、a。这就产生了一个问题:当请求流量进来时会一直选择权重高的机器,可能致使流量不均衡,流量大部分分散在权重比较高的机器。而 Nginx SWRR 的算法特色就是平滑、分散,至关于把 a 间隔打散在列表中。
流量调度
压测平台
请求流量进来后,在接入层按照权重作负载均衡算法,如此一来在接入层又孵化了一个新产品——流量调度,主要的应用场景是压测平台。
通常新版本发布后都须要用线上真实流量进行压测,经过压测平台调整后端某几台机器的权重,把须要进行压测的机器权重调高,接入层能够动态感知到机器权重被调高,使得更多的线上流量被引向那些机器,以此达到线上压测的效果。
异常检测平台
咱们还有个异常检测平台,经过实时监测各应用机器以及服务状态,按照必定的算法下降异常机器的权重,从而规避一些异常问题。咱们会检测后端的每台机器,主要考量三个层面:
若是某一台机器负载比较高,检测平台经过服务发现后会下降权重,接入层动态感知到后能下降该台机器的流量。这种作法的好处在于若是线上有异常机器,无需人工介入,能够直接智能化感知并自动摘除。
须要注意的是在一些特殊的场景里,异常检测平台也会出现问题,例如不少台机器都出现了故障,系统把这些机器权重所有下降,假设 100 台机器下降了 50 台,可能会引发系统雪崩。所以这部分也须要把控,设置在必定的范围内容许调整机器权重的数量。
接入层 VNSWRR 算法改造背景
调低权重,机器的流量会减小,但若是调高权重呢?你们都知道 Nginx 是多进程、单线程模式,例如 CPU 是 32 核,它就会起 32 个worker,每个 worker 都是独立的 SWRR 算法。
如上图所示,是采用原生的 Nginx 官方算法进行线上压测的真实案例,压测平台把某台机器的权重从 1 调整为 2,机器流量瞬间冲高 50 倍,基本上单个集群的流量所有引向该台机器了,这是原生的 Nginx 官方算法。而在接入层中,后端机器权重是动态感知的,实时性很是高,因此会出现上图中的问题。
这里有一点值得注意:权重从 1 调到 2,为何冲高的流量不是预期的 2 倍,而是冲高了 50 倍?若是想解决这个问题,咱们通常会从运维的角度或者开发的角度出发,不过既然已经有监控数据,就先不看代码,直接从运维的角度分析数据监控图,如下是调高权重后对应机器的 QPS 变化特征:
第 4 点中的 K 是应用的 QPS,能够当作速度,即每秒能进来多少许,而用接入层的总 worker 数(Nginx 是多进程,单线程模式,32 核的 CPU 就是 32 个 Worker)除以速度(K),取一个中间时间,算出来大概是 7.68 秒左右,和 7 秒基本上是接近的。
接入层 VNSWRR 算法演进历程
接入层 VNSWRR 算法(V1)
Nginx SWRR 算法有一个缺陷:第一个请求进来时,必然选择权重高的机器。这是由于接入层是无状态的,每台机器 worker 的算法都是独立的,请求分到任意一台机器的 worker 上,初始状态都是选择权重最高的。找到缘由后就须要解决这个问题,那有什么办法能够不让它选择权重最高的呢?方法很简单,咱们当初只用四五行代码就解决了这个问题。
咱们抽象一个算法模型,让它预调整机器权重,调整的范围是多少?如左闭右开一个区间 [0,min(N,16)),N 表明应用的机器数,从 1 到 16 去取一个最小值。这意味着最多能够预丢 16 次,最少能够不丢。“不丢”指的是请求来了直接送到后端权重被调高的机器。改造完后,看一下效果如何。
一样的场景,咱们把机器权重从 1 调整为 2,流量瞬间冲高了 3 倍。这里又出现了同样的问题,在改造前,流量是冲高了 50 倍,如今只冲高了 3 倍,因此问题仍是没有解决,只是缓和了。同时出现了一个新的问题,机器权重调到 2后,按照最原始的算法,其流量基本上 7 秒事后就能恢复到预期的 2 倍,可是如今须要 15 分钟才能恢复到预期的 2 倍,这确定是不能容忍的。
任何一个问题背后都是由于代码的改动或者逻辑设计的不合理致使的。先思考流量瞬间冲高 3 倍是怎么来的,这和前文提到的“丢”有关。“丢”指的是第一个请求进来时,并不直接把它送到后端,而是先伪造一次,动态调整权重,调整事后在把请求转发到后端。这个请求就至关于后面会从新触发 SWRR 算法,如此一来能够避免第一个请求都被选中到权重调高的机器上。
再看设置的区间 [0,min(N,16)),线上应用机器确定超过 16 台,而区间范围是 0 到 16。0 表明请求来了直接送到后端,这就有 1/16 的几率是不丢的,意味着有 1/16 的几率会遇到 Nginx SWRR 算法自己缺陷形成的的问题。开始冲高 50 倍,1/16 基本上就是 3 倍,这是几率问题。
那为何持续了 15 分钟才达到预期的 2 倍呢?这是由于 1/16 的几率不丢,还有其它的可能丢 1 次,丢 2 次,丢 3 次……丢 15 次。但一个机器被选中后,它的权重会减去当前全部机器的权重总和,这样作的目标是为了下降下次被选中的几率。因此当请求再次进来时选择权重被调高的那台机器几率也很低。根据此前的算法能够算出须要持续 15 分钟才能恢复。
接入层 VNSWRR 算法(V2)
基于前文流量冲高 3 倍及持续时间较长的问题,咱们又进一步演进算法,经过引入虚拟节点的新思路解决问题。
假设有三台机器 A、B、C,权重分别是 一、二、3。须要考虑两个问题:
解决上述两个问题有两个关键点:第一个关键点是机器列表如何初始化,如何打散机器,不让权重高的机器集中在一块儿。另外一个关键点是如何初始化虚拟列表,一次性初始化会发生 CPU 作密集型计算的问题。基于这些,咱们引入一种新思路——初始化虚拟节点列表的顺序彻底和 SWRR 的选取一致,严格按照数学模型的算法初始化。另外,庞大的虚拟节点列表若是按照 Nginx 官网的权重初始化算法,是很是消耗 CPU 的,因此咱们决定在运行时分批初始化。
举个例子,一个应用有 3 台机器,权重分配是 一、二、3,那么它共有 6 个的虚拟节点,而真实节点只有 3 个。则第一批先初始化3个虚拟节点(即真实机器数),当第一批虚拟节点轮训使用完后则进行初始化下一批虚拟节点,同时虚拟列表中机器节点的顺序严格按照 SWRR 算法顺序填充进去。
如上图,当第一个请求进来时,就从虚拟机器列表中的一个随机位置开始轮询。如 Step 1,抽一个随机数,有多是从 C 开始去轮询,也有可能从 B 开始轮询。经过这种方式使得 Tengine 的每一个 worker 以及每台机器的 Tengine 均可以被打散,有的机器从 C 开始,有的机器从 B 开始。这样就能够避免全部流量都选择权重最大的那台机器,经过随机数打散流量被分配到各台机器。而当运行时轮询到 A 机器后,则需初始化第二批虚拟节点列表(如 Step2中橙色部分),当虚拟节点所有填充好后(如 Step3 中状态),后续不在作初始化,直接轮训列表就好。
演进后的接入层 VNSWRR 算法上线后效果很是明显,如上图所示,机器权重从 1 调整为 2 时,流量基本上是平稳地上升到 2 倍左右。
接入层 VNSWRR 算法演进效果对比
图1 是 Nginx 原生的 SWRR 算法,图 2 是改造 V1 版本的效果。图 3 是终版改造的算法,从这个版本开始,咱们引入虚拟节点,使得流量在分钟级别内平稳的达到 2 倍。这不只解决了 Nginx 加权轮询算法在权重调高时流量所有集中在一台机器上的问题,而且在引用虚拟节点事后,咱们的算法时间复杂度基本上变成 O(1),而 Nginx 官方的算法如今目前仍是 O(n)。
以前咱们有发过一篇文章,在纯压测负载均衡算法的场景下,改造事后的终版算法相较于SWRR 算法有 60% 的性能提高。
刚开始遇到问题时,既想把问题解决掉,又想把性能优化到极致。但事实上仍是在解决问题的过程当中逐步演进到最优状态。
譬如看到权重调高后机器的 QPS 变化趋势图,每个特殊峰值点以及变化趋势会给挖掘问题的本质带来很大的帮助。
简单并不表明 low,代码入侵越小,出问题就越少,同时也容易发现。
咱们作任何一个方案,大到一个系统的设计,小到每一行代码,都须要考虑到小程序、大流量场景。若是程序员能作到这一步,相信必定会有很大的成长。
以上就是王发康老师在 Open Talk 杭州站现场分享整理,舒适提示 12 月 14 日 API 网关与高性能服务最佳实践·广州站活动正在报名中 http://hdxu.cn/jB9KO
演讲视频观看及演讲 PPT 下载: