提升集群吞吐量与稳定性的秘诀: Dubbo 自适应负载均衡与限流策略实现解析

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 提升集群吞吐量与稳定性的秘诀: Dubbo 自适应负载均衡与限流策略实现解析

作者:刘泉禄

整体介绍


本文所说的“柔性服务”主要是指 consumer 端的负载均衡和 provider 端的限流两个功能。在之前的 Dubbo 版本中,负载均衡部分更多的考虑的是公平性原则,即 consumer 端尽可能平等的从 provider 中作出选择,在某些情况下表现并不够理想。而限流部分只提供了静态的限流方案,需要用户对 provider 端设置静态的最大并发值,然而该值的合理选取对用户来讲并不容易。我们针对这些存在的问题进行了改进。


负载均衡


在原本的 Dubbo 版本中,有五种负载均衡的方案供选择,他们分别是 "Random" , "ShortestResponse" , "RoundRobin","LeastActive" 和 "ConsistentHash"。


其中除 "ShortestResponse" 和 "LeastActive" 外,其他的几种方案主要是考虑选择时的公平性和稳定性。对于 "ShortestResponse" 来说,其设计目的是从所有备选的 provider 中选择 response 时间最短的以提高系统整体的吞吐量。然而存在两个问题:


1. 在大多数的场景下,不同 provider 的 response 时长没有非常明显的区别,此时该算法会退化为随机选择。

2. response 的时间长短有时也并不能代表机器的吞吐能力。对于 "LeastActive" 来说,其认为应该将流量尽可能分配到当前并发处理任务较少的机器上。但是其同样存在和 "ShortestResponse" 类似的问题,即这并不能单独代表机器的吞吐能力。


基于以上分析,我们提出了两种新的负载均衡算法。一种是同样基于公平性考虑的单纯 "P2C" 算法,另一种是基于自适应的方法 "adaptive",其试图自适应的衡量 provider 端机器的吞吐能力,然后将流量尽可能分配到吞吐能力高的机器上,以提高系统整体的性能。


效果介绍

对于负载均衡部分的有效性实验在两个不同的情况下进行的,分别是提供端机器配置比较均衡和提供端机器配置差距较大的情况。

image.pngimage.png

使用方法

使用方法与原本的负载均衡方法相同。只需要在 consumer 端将 "loadbalance" 设置为 "p2c" 或者 "adaptive" 即可。


代码结构

负载均衡部分的算法实现只需要在原本负载均衡框架内继承 LoadBalance 接口即可。


原理介绍

P2C 算法

Power of Two Choice 算法简单但是经典,主要思路如下:

1. 对于每次调用,从可用的 provider 列表中做两次随机选择,选出两个节点 providerA 和 providerB。

2. 比较 providerA 和 providerB 两个节点,选择其“当前正在处理的连接数”较小的那个节点。


adaptive 算法

代码的 github 地址[1]

相关指标

1. cpuLoad

cpuLoad = cpu一分钟平均负载 * 100 / 可用cpu数量。该指标在 provider 端机器获得,并通过 invocation 的 attachment 传递给 consumer 端。

2. rt

rt 为一次 rpc 调用所用的时间,单位为毫秒。

3. timeout

timeout 为本次 rpc 调用超时剩余的时间,单位为毫秒。

4. weight

weight 是设置的服务权重。

5. currentProviderTime

provider 端在计算 cpuLoad 时的时间,单位是毫秒

6. currentTime

currentTime 为最后一次计算 load 时的时间,初始化为 currentProviderTime,单位是毫秒。

7. multiple

multiple=(当前时间 - currentTime)/timeout + 1

8. lastLatency

image.png

9. beta

平滑参数,默认为0.5

10. ewma

lastLatency 的平滑值

lastLatency=beta*lastLatency+(1 - beta)*lastLatency

11. inflight

inflight 为 consumer 端还未返回的请求的数量。

inflight=consumerReq - consumerSuccess - errorReq

12. load

对于备选后端机器x来说,若距离上次被调用的时间大于 2*timeout,则其 load 值为 0。

否则

load=CpuLoad*(sqrt(ewma) + 1)*(inflight + 1)/(((consumerSuccess / (consumerReq +1) )*weight)+1)


算法实现

依然是基于 P2C 算法。

1. 从备选列表中做两次随机选择,得到 providerA 和 providerB

2. 比较 providerA 和 providerB 的 load 值,选择较小的那个。


自适应限流


与负载均衡运行在 consumer 端不同的是,限流功能运行在 provider 端。其作用是限制 provider 端处理并发任务时的最大数量。从理论上讲,服务端机器的处理能力是存在上限的,对于一台服务端机器,当短时间内出现大量的请求调用时,会导致处理不及时的请求积压,使机器过载。在这种情况下可能导致两个问题:


1.由于请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务瘫痪。

2.服务端机器长时间的过载可能有宕机的风险。因此,在可能存在过载风险时,拒绝掉一部分请求反而是更好的选择。在之前的 Dubbo 版本中,限流是通过在 provider 端设置静态的最大并发值实现的。但是在服务数量多,拓扑复杂且处理能力会动态变化的局面下,该值难以通过计算静态设置。


基于以上原因,我们需要一种自适应的算法,其可以动态调整服务端机器的最大并发值,使其可以在保证机器不过载的前提下,尽可能多的处理接收到的请求。


因此,我们参考部分业界方案实现基础上,在 Dubbo 的框架内实现了两种自适应限流算法,分别是基于启发式平滑的 "HeuristicSmoothingFlowControl" 和基于窗口的 "AutoConcurrencyLimier"。

代码的 github 地址[2]


效果介绍

自适应限流部分的有效性实验我们在提供端机器配置尽可能大的情况下进行,并且为了凸显效果,在实验中我们将单次请求的复杂度提高,将超时时间尽可能设置的大,并且开启消费端的重试功能。

image.png

使用方法

要确保服务端存在多个节点,并且消费端开启重试策略的前提下,限流功能才能更好的发挥作用。设置方法与静态的最大并发值设置类似,只需在 provider 端将 "flowcontrol" 设置为 "autoConcurrencyLimier" 或者 "heuristicSmoothingFlowControl" 即可。


代码结构

1. FlowControlFilter:在 provider 端的 filter 负责根据限流算法的结果来对 provider 端进行限流功能。

2. FlowControl:根据 Dubbo 的 spi 实现的限流算法的接口。限流的具体实现算法需要继承自该接口并可以通过 Dubbo 的 spi 方式使用。

3. CpuUsage:周期性获取 cpu 的相关指标

4. HardwareMetricsCollector:获取硬件指标的相关方法

5. ServerMetricsCollector:基于滑动窗口的获取限流需要的指标的相关方法。比如 qps 等。

6. AutoConcurrencyLimier:自适应限流的具体实现算法。

7. HeuristicSmoothingFlowControl:自适应限流的具体实现方法。


原理介绍

HeuristicSmoothingFlowControl

相关指标

1. alpha

alpha 为可接受的延时的上升幅度,默认为 0.3

2. minLatency

在一个时间窗口内的最小的 Latency 值。

3. noLoadLatency

noLoadLatency 是单纯处理任务的延时,不包括排队时间。这是服务端机器的固有属性,但是并不是一成不变的。在 HeuristicSmoothingFlowControl 算法中,我们根据机器CPU的使用率来确定机器当前的 noLoadLatency。当机器的 CPU 使用率较低时,我们认为 minLatency 便是 noLoadLatency。当 CPU 使用率适中时,我们平滑的用 minLatency 来更新 noLoadLatency 的值。当 CPU 使用率较高时,noLoadLatency 的值不再改变。

4. maxQPS

一个时间窗口周期内的 QPS 的最大值。

5. avgLatency

一个时间窗口周期内的 Latency 的平均值,单位为毫秒。

6. maxConcurrency

计算得到的当前服务提供端的最大并发值。

maxConcurrency=ceil(maxQPS*((2 + alpha)*noLoadLatency - avgLatency))

算法实现

当服务端收到一个请求时,首先判断 CPU 的使用率是否超过 50%。如果没有超过 50%,则接受这个请求进行处理。如果超过 50%,说明当前的负载较高,便从 HeuristicSmoothingFlowControl 算法中获得当前的 maxConcurrency 值。如果当前正在处理的请求数量超过了 maxConcurrency,则拒绝该请求。


AutoConcurrencyLimier

相关指标

1. MaxExploreRatio

默认设置为 0.3

2. MinExploreRatio

默认设置为 0.06

3. SampleWindowSizeMs

采样窗口的时长。默认为 1000 毫秒。

4. MinSampleCount

采样窗口的最小请求数量。默认为 40。

5. MaxSampleCount

采样窗口的最大请求数量。默认为 500。

6. emaFactor

平滑处理参数。默认为 0.1。

7. exploreRatio

探索率。初始设置为 MaxExploreRatio。若 avgLatency<=noLoadLatency*(1.0 + MinExploreRatio) 或者 qps>=maxQPS*(1.0 + MinExploreRatio)则 exploreRatio=min(MaxExploreRatio,exploreRatio+0.02)

否则

exploreRatio=max(MinExploreRatio,exploreRatio-0.02)

8. maxQPS

窗口周期内 QPS 的最大值。

image.png

9. noLoadLatency

image.png

10. halfSampleIntervalMs

半采样区间。默认为 25000 毫秒。

11. resetLatencyUs

下一次重置所有值的时间戳,这里的重置包括窗口内值和 noLoadLatency。单位是微秒。初始为 0.

image.png

12. remeasureStartUs

下一次重置窗口的开始时间。

image.png

13. startSampleTimeUs

开始采样的时间。单位为微秒。

14. sampleCount

当前采样窗口内请求的数量。

15. totalSampleUs

采样窗口内所有请求的 latency 的和。单位为微秒。

16. totalReqCount

采样窗口时间内所有请求的数量和。注意区别 sampleCount。

17. samplingTimeUs

采样当前请求的时间戳。单位为微秒。

18. latency

当前请求的 latency。

19. qps

在该时间窗口内的 qps 值。

image.png

20. avgLatency

窗口内的平均 latency。

image.png

21. maxConcurrency

上一个窗口计算得到当前周期的最大并发值。

22. nextMaxConcurrency

当前窗口计算出的下一个周期的最大并发值。

image.png

Little's Law

当服务处于稳定状态时:concurrency=latency*qps。这是自适应限流理论的基础。当请求没有导致机器超载时,latency 基本稳定,qps 和 concurrency 处于线性关系。当短时间内请求数量过多,导致服务超载的时候,concurrency 会和latency一起上升,qps则会趋于稳定。


算法实现

AutoConcurrencyLimier 的算法使用过程和 HeuristicSmoothingFlowControl 类似。


实现与 HeuristicSmoothingFlowControl 的最大区别是 AutoConcurrencyLimier 是基于窗口的。每当窗口内积累了一定量的采样数据时,才利用窗口内的数据来更新得到 maxConcurrency。


其次,利用 exploreRatio 来对剩余的容量进行探索。


另外,每隔一段时间都会自动缩小 max_concurrency 并持续一段时间,以处理 noLoadLatency 上涨的情况。因为估计 noLoadLatency 时必须先让服务处于低负载的状态,因此对 maxConcurrency 的缩小是难以避免的。


由于 max_concurrency<concurrency 时,服务会拒绝掉所有的请求,限流算法将"排空所有的经历过排队的等待请求的时间"设置为 2*latency,以确保 minLatency 的样本绝大部分时没有经过排队等待的。


Dubbo 于上周上线了新版官网与文档,涵盖 Dubbo3 核心功能及特性,关于自适应负载均衡、自适应限流及更多方案的详细讲解,请访问:https://dubbo.apache.org


相关链接

[1] 代码的 github 地址

https://github.com/apache/dubbo/pull/10745

[2] 代码的 github 地址

https://github.com/apache/dubbo/pull/10642

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
30天前
|
域名解析 移动开发 负载均衡
阿里云DNS常见问题之DNS负载均衡调加权模式失败如何解决
阿里云DNS(Domain Name System)服务是一个高可用和可扩展的云端DNS服务,用于将域名转换为IP地址,从而让用户能够通过域名访问云端资源。以下是一些关于阿里云DNS服务的常见问题合集:
|
1月前
|
人工智能 搜索推荐 安全
打造精准营销!营销电子邮件以客户为中心策略解析!
营销电子邮件是数字营销的核心,用于建立客户关系、推广产品和服务,提高品牌忠诚度和转化率。它们在客户旅程中扮演关键接触点角色,如欢迎邮件、购物车提醒和个性化推荐。电子邮件营销能提升品牌知名度,细分营销可带来760%的收入增长。然而,大量邮件可能导致邮件过载,缺乏个性化可能引起收件人反感,甚至网络安全问题。收件人和IT团队可通过过滤、优化设置、启用2FA等措施改善体验。营销团队则需克服管理、个性化和法规遵从等挑战,采用先进技术同时确保隐私和安全,以同理心驱动的策略建立客户连接,实现业务成功。
21 1
打造精准营销!营销电子邮件以客户为中心策略解析!
|
1月前
|
缓存 监控 NoSQL
解析Redis缓存雪崩及应对策略
解析Redis缓存雪崩及应对策略
|
1月前
|
存储 JSON 安全
【C++ 泛型编程 综合篇】泛型编程深度解析:C++中的五种类型泛型策略综合对比
【C++ 泛型编程 综合篇】泛型编程深度解析:C++中的五种类型泛型策略综合对比
65 1
|
6天前
|
存储 安全 网络安全
解析企业邮箱迁移:从技术到策略的完全指南
公司邮箱迁移是业务连续性和数据安全的关键步骤。涉及数据加密、安全存储和密钥管理,确保转移过程中的完整性与机密性。迁移应尽量减少对业务影响,通过IMAP/POP协议实现无缝转移。以Zoho Mail为例,需开启服务,获取授权码,设置转移,选择内容,填写原邮箱信息,最后验证数据。迁移前后注意备份和问题解决,确保顺利进行。
9 0
|
22天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
1月前
|
算法 IDE Linux
【CMake 小知识】CMake中的库目标命名和查找策略解析
【CMake 小知识】CMake中的库目标命名和查找策略解析
99 1
|
1月前
|
Java 程序员 API
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
77 0
|
1月前
|
SQL 安全 网络安全
构筑数字堡垒:网络安全漏洞解析与防御策略
在数字化时代,网络安全已成为维护信息完整性、保障用户隐私和确保商业连续性的关键。本文将深入探讨网络安全领域的核心议题—安全漏洞及其防御机制。通过分析常见网络攻击手段,如SQL注入、跨站脚本攻击(XSS)及拒绝服务(DoS)攻击,揭示其背后的原理与潜在危害。同时,文章将重点介绍加密技术的种类和应用场景,以及如何通过强化安全意识,构建多层次的防御体系来有效预防和应对网络安全威胁。本研究旨在为读者提供一份系统性的网络安全防护指南,帮助个人和组织在不断演变的威胁面前保持警惕,并采取适当的安全措施。
21 2
|
1月前
|
Kubernetes Linux Docker
深度解析:Kubernetes 1.28.2集群安装过程中的关键步骤
本文旨在为读者提供一份详尽的Kubernetes 1.28.2集群安装指南,帮助您从零开始构建稳定、高效的Kubernetes集群。我们将从环境准备、软件安装、集群初始化到节点添加等各个环节进行逐步讲解,确保您能够顺利完成集群的搭建。

推荐镜像

更多