写点什么

博文干货 | Pulsar 平均负载器(AvgShedder)

作者:AscentStream
  • 2025-09-19
    上海
  • 本文字数:12438 字

    阅读完需:约 41 分钟

博文干货 | Pulsar 平均负载器(AvgShedder)

作者:冯文智,Apache Pulsar Committer,BIGO 大数据消息平台团队工程师,《Apache Pulsar 优化实战》作者,毕业于华中科技大学。 擅长领域包括 Pulsar 负载均衡和性能调优、联合 Flink 和 Pulsar 事务实现 Exactly Once 语义和 TLA 验证分布式协议等。

注:此篇 《Pulsar 平均负载器(AvgShedder)》为《Apache Pulsar 优化实战》小册 2.0 中新增的一篇。新增内容覆盖了现有 Pulsar 负载均衡的测试情况以及 Pulsar 最新平均负载器(AvgShedder)的线上实测效果。


平均负载器

- AvgShedder -

这是 BIGO 《优化 Apache Pulsar 系列》小册的第八篇文章,这篇文章我们首先会展示一系列实验数据来验证上一篇文章分析得到的结论,然后详细介绍 BIGO 内部开发的新负载均衡算法 AvgShedder,分析其是如何解决当前算法所面临的各种问题,最后展示其线上运行效果以及测试效果。

我们首先对当前的两种负载均衡算法选择进行实验,验证前面的分析:

  • 阈值卸载器方案:ThresholdShedder + LeastResourceUsageWithWeight

  • 统一卸载器方案:UniformLoadShedder + LeastLongTermMessageRate

      

注:可参考前一篇负载均衡入门

博文干货 | Meetup 小册精选 | Pulsar 负载均衡入门


阈值卸载器方案

- ThresholdShedder+LeastResourceUsageWithWeight -

环境配置

搭建一个 5 节点的 broker 集群,包含 30 个 bookie。

负载均衡相关配置如下:

loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.ThresholdShedderloadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight
复制代码

使用ThresholdShedder + LeastResourceUsageWithWeight的组合,配置基本都使用默认值,但是关闭了 bundle split、bundle 均匀分布的特性。

loadBalancerDistributeBundlesEvenlyEnabled=falseloadBalancerAutoBundleSplitEnabled=falseloadBalancerAutoUnloadSplitBundlesEnabled=false
复制代码

这里强烈建议关闭 bundle 均匀分布的特性:loadBalancerDistributeBundlesEvenlyEnabled=false

会让负载均衡的算法几乎无法工作,因为它会强行让不同 broker 之间的 bundle 个数相同,在挑选候选 broker 时会将绝大部分 broker 都过滤掉,经过它的筛选后才执行负载均衡算法,对于小集群来说,传给负载均衡算法的候选 broker 列表往往只有 1 个,此时负载均衡算法可有可无。

过度加载问题

启动三个压测任务:



启动压测任务后,集群花费了 22min 才稳定了下来,触发了 8 次 bundle unload。

为了方便 debug,添加了部分日志。第一次触发 bundle unload 时的日志如下:

2024-06-11T15:33:42,642+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder - brokerAvgResourceUsage: {XXX.83:8081=0.146445592841173, XXX.32:8081=0.1780747564543283, XXX.87:8081=0.13442747117624326, XXX.206:8081=0.28951184996156754, XXX.161:8081=0.11923764428233738}, avgUsage: 0.1735394629431299, threshold: 0.1, minThroughputThreshold: 10.0MB
复制代码

这行日志打印了所有 broker 的最终得分(即使用了历史打分算法)、平均分、阈值。

2024-06-11T15:33:42,642+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder - brokerAvgResourceUsageWithoutHistory: {XXX.83:8081=0.6200548553466797, XXX.32:8081=0.6142524337768555, XXX.87:8081=0.34531837463378906, XXX.206:8081=0.6850704193115235, XXX.161:8081=0.4193758010864258}
复制代码

这行日志打印了所有 broker 的中间分数(即当前所有 broker 的最大资源使用率,不使用历史打分算法),可以看到当前 XXX.83:8081、XXX.32:8081、XXX.206:8081 是高载 broker,另外两个 broker 是低载的。

根据前面两行日志可以看到,由于启动压测任务之前各个 broker 的负载都较低,因此此时所有 broker 的得分都跟真实负载有较大差距。只有 XXX.206:8081 的得分超过了阈值:

28.951184996156755% > 17.35394629431299% + 10.0%

因此对它执行 bundle unload,得到如下日志:

2024-06-11T15:33:42,642+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder - Attempting to shed load on XXX.206:8081, which has max resource usage above avgUsage  and threshold 28.951184996156755% > 17.35394629431299% + 10.0% -- Offloading at least 14.70925448705765 MByte/s of traffic, left throughput 208.25151973726722 MByte/s
复制代码

卸载一个 bundle,并立刻执行放置策略 LeastResourceUsageWithWeight

2024-06-11T15:33:42,663+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight - brokerAvgResourceUsageWithWeight:{XXX.83:8081=0.08251018381118773, XXX.32:8081=0.11141611766815185, XXX.87:8081=0.0459994751214981, XXX.206:8081=0.23925241661071778, XXX.161:8081=0.06012571454048156}, avgUsage:0.10786078155040742, diffThreshold:0.1, candidates:[XXX.83:8081, XXX.32:8081, XXX.87:8081, XXX.206:8081, XXX.161:8081]2024-06-11T15:33:42,663+0800 [pulsar-load-manager-1-1] WARN  org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight - Assign randomly as all 5 brokers are overloaded.
复制代码

由于任一 broker 的分数加上 10 都大于平均分 10.7%,因此候选 broker 列表为空,触发随机分配。这就是我们之前描述的 LeastResourceUsageWithWeight 的问题:候选 broker 的列表很容易为空,从而导致随机分配。

2024-06-11T15:33:42,663+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight - Selected 5 best brokers: [XXX.83:8081, XXX.32:8081, XXX.87:8081, XXX.206:8081, XXX.161:8081] from candidate brokers: [XXX.83:8081, XXX.32:8081, XXX.87:8081, XXX.206:8081, XXX.161:8081], assign bundle public/default/0x70000000_0x80000000 to broker XXX.83:80812024-06-11T15:33:42,663+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.ModularLoadManagerImpl - [ThresholdShedder] Unloading bundle: public/default/0x70000000_0x80000000 from broker XXX.206:8081 to dest broker XXX.83:8081
复制代码

可以看到,卸载下来的 bundle 分配到了高载的 XXX.83:8081!这是一次失败的负载均衡决策。

实际上,这次实验里这个问题触发的概率非常高,几乎是必现的。如下图所示,连续四次都触发了。

之所以复现的概率会如此之高,历史打分算法也要背一部分锅。这是由于候选 broker 的挑选条件是:broker 打分要比平均分小 10 分,也就是说 broker 之间的分数要拉开相当的差距。但是由于历史打分算法,所有 broker 的分数都只能从 20 左右缓慢地逼近它们的真实负载,这就导致不同 broker 之间的分数迟迟无法拉开差距,那么 LeastResourceUsageWithWeight 算法就只能进行随机分配了。

过度卸载问题

为了增加单台 broker 的负载,缩容两台 broker,观察到一次异常的负载均衡。



可以发现,主要执行了三轮负载均衡:

  • 第一轮将 bundle 从最高载的 XXX.206:8081(黄色线)卸载到 XXX.83:8081(绿色线),但是第一轮卸载了四次 bundle,导致 XXX.206:8081 的负载迅速变成最低载的 broker,遇到了过度卸载的问题。

  • 第二轮将 bundle 从最高载的 XXX.32:8081(蓝色线)卸载到同样是高载的 XXX.83:8081 和低载的 XXX.206:8081,这次卸载了 11 个 bundle,同样遇到了过度卸载的问题,而且还遇到了过度加载的问题,错误地将 bundle 分配给高载的 XXX.83:8081,XXX.32:8081 成为最低载的 broker。

  • 第三轮将 bundle 从最高载的 XXX.83:8081 卸给 XXX.32:8081,集群才进入到均衡的状态,总共耗时 30min。

broker 日志能更深入地看到上面的过程:

可以看到,第一轮 bundle unload 的 10min 里一直判定 XXX.206:8081 为高载 broker,一直在从 XXX.206:8081 身上卸载 bundle,最终导致过度卸载的问题,这是因为历史打分算法使得 XXX.206:8081 的打分只能缓慢地变化,尽管它已经卸载了 bundle,并且真实负载也对应变化了,但是得分还是很高,因此一直被判定为高载 broker,一直在卸载 bundle。

而第二轮 bundle unload 时,从最高载的 XXX.32:8081 身上卸载 bundle,但是却遇到了过度加载的问题,将 bundle 卸载到了同样高载的 XXX.83:8081 身上。

2024-06-12T10:24:02,245+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder - Attempting to shed load on XXX.32:8081, which has max resource usage above avgUsage  and threshold 78.09468007403726% > 65.79112414711298% + 10.0% -- Offloading at least 14.886936767013715 MByte/s of traffic, left throughput 188.94441491927364 MByte/s2024-06-12T10:24:02,246+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight - brokerAvgResourceUsageWithWeight:{XXX.83:8081=0.6968493632164602, XXX.32:8081=0.6564280053774565, XXX.206:8081=0.5447576150322107}, avgUsage:0.6326783278753757, diffThreshold:0.1, candidates:[XXX.83:8081, XXX.32:8081, XXX.206:8081]2024-06-12T10:24:02,246+0800 [pulsar-load-manager-1-1] WARN  org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight - Assign randomly as all 3 brokers are overloaded.2024-06-12T10:24:02,247+0800 [pulsar-load-manager-1-1] INFO  org.apache.pulsar.broker.loadbalance.impl.ModularLoadManagerImpl - [ThresholdShedder] Unloading bundle: public/default/0x30000000_0x40000000 from broker XXX.32:8081 to dest broker XXX.83:8081
复制代码

至此,我们已经用实验验证了 ThresholdShedder + LeastResourceUsageWithWeight 的两个核心缺陷:

  • 过度加载问题

  • 过度卸载问题

这两个问题因都因为历史打分算法而变得严重,过程中也可以看出ThresholdShedder + LeastResourceUsageWithWeight的负载均衡速度并不快,由于错误的负载均衡决策,往往需要反复地执行负载均衡最终才能稳定下来。

统一卸载器方案

- UniformLoadShedder+LeastLongTermMessageRate -


环境配置

搭建了一个 4 节点的 broker 集群,包含 20 个 bookie,但是有一台机器 XXX.34 是异构的,它的性能比另外三台机器强很多。

负载均衡相关配置如下:

loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.UniformLoadShedderloadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.LeastLongTermMessageRate
复制代码

使用 UniformLoadShedder + LeastLongTermMessageRate 的组合,配置基本都使用默认值,因此允许最大消息速率是最小消息速率的 1.5 倍,最大流量吞吐是最小流量吞吐的 4 倍。

同样关闭了 bundle split、bundle 均匀分布的特性。

loadBalancerDistributeBundlesEvenlyEnabled=falseloadBalancerAutoBundleSplitEnabled=falseloadBalancerAutoUnloadSplitBundlesEnabled=false
复制代码

异构环境

启动两个压测任务:


为了观察 UniformLoadShedder 算法的执行情况,增加两个 panel:

  • 进出流量吞吐的最大最小比值

    max(sum(pulsar_throughput_in+pulsar_throughput_out) by (instance))/min(sum(pulsar_throughput_in+pulsar_throughput_out) by (instance))

  • 进出消息速率的最大最小比值。

    max(sum(pulsar_rate_in+pulsar_rate_out) by (instance))/min(sum(pulsar_rate_in+pulsar_rate_out) by (instance))

可以看到,经过一轮负载均衡后这两个比值都从 2.5 下降到 1.2 左右。

📕注记:实际上默认值让流量吞吐比值的阈值比消息速率比值的阈值更大没有什么依据,用户最好根据实际场景进行微调。



从消息速率、流量吞吐的角度来看,这一轮的负载均衡非常成功,集群的 broker 之间的消息速率、流量吞吐都非常趋近,而且 5min 以内就达到了稳定状态,比 ThresholdShedder + LeastResourceUsageWithWeight 快很多。

但是观察资源使用率的指标,我们就会发现集群其实相当地不均衡,由于 XXX.34 的性能强很多,导致它的资源使用率远远比其他 broker 低!这就造成了资源浪费,如果每台 broker 均摊到的负载再高一点,那么其它 broker 就会超载,而 XXX.34 还是会处于低载的水平,这不是我们希望看到的,我们希望 XXX.34 承担更多的负载。

负载抖动

为了模拟突增突减的负载,增加一个 topic:persistent://public/default/testTxn,生产流量跟其他任务一样,但是消费流量每运行 1min 就停止,等待 1min 后再继续消费。

如下:


观察监控可以发现,负载均衡算法一直在 unload bundle,因为突增、突减的消费流量导致消息速率的最大最小比值瞬间就超过配置的阈值 1.5,触发 bundle unload。



平均卸载器方案

- AvgShedder -


为了解决前面遇到的这些问题,我们重新设计了负载均衡算法。首先,从卸载策略 LoadSheddingStrategy 开始入手。

打分算法

要确定高载 broker,肯定要对 broker 进行打分并排序,当前有两种打分依据:

  • broker 的资源使用率

  • broker 的消息速率、流量吞吐

根据前面分析可知,如果根据消息速率、流量吞吐来打分就会像 UniformLoadShedder 一样面临异构环境的问题;而如果根据资源使用率来打分,在放置 bundle 的时候会像 LeastResourceUsageWithWeight 一样面临过度加载的问题。那是否就熊掌和鱼不可兼得呢?

深入思考 LeastResourceUsageWithWeight 过度加载问题的根源,会发现问题不是因为打分依据导致的,而是因为卸载策略和放置策略是两个独立的模块,两者之间没有信息互通!

我们举个例子就明白了,使用 ThresholdShedder + LeastResourceUsageWithWeight 组合,配置使用默认值,当前 broker 打分为 20,51,52,80,80,80,则平均分为 60.5,ThresholdShedder 判定三个 80 分的 broker 为高载 broker,卸载 bundle 出来,而根据 LeastResourceUsageWithWeight 算法,只有打分 20 的 broker 被选为候选 broker,那么卸载下来的 bundle 会全部砸给这个低载 broker,很有可能就让它变成最高载的 broker 了。

但是如果让人去手动均衡负载的话,一种非常简单直观的想法是:让得分最高的 broker 和得分最低的 broker 均摊负载。比如说上面的例子,假设均摊负载能让两者得分也均摊,让其中一个 80 分的 broker 与 20 分的 broker 均摊,那么 20,51,52,80,80,80 会变成 50,50,51,52,80,80,如果觉得 50 和 80 的差距还是比较大,则可以进一步均摊,我们会引入一个阈值,当最低与最高的分数差距大于此阈值时,则触发 bundle unload。

这种均摊算法的本质是卸载 bundle 的时候就指定好接收方了,而不是先卸载完一堆 bundle,然后再去确定分配给谁。也就是说,LoadSheddingStrategy 在卸载的时候就已经明确了接下来 ModularLoadManagerStrategy 的决策结果了,因此,我们让 AvgShedder 同时实现了 LoadSheddingStrategy、ModularLoadManagerStrategy 接口,集卸载策略和放置策略于一体。

因此,AvgShedder 会根据 broker 的资源使用率进行打分,避免异构环境的问题,并通过捆绑卸载策略和放置策略的方式来避免过度加载的问题。

读者可以发现,这种均摊的做法跟 UniformLoadShedder 有点像,它也是比较最高和最低载的 broker,然后从最高载的 broker 上卸载 bundle。我们前面也提到过,这种方式一次 shedding 只处理一个高载 broker,对于大集群来说速度会比较慢,因此我们进一步做优化,一次 shedding 匹配多对高低载 broker,即对 broker 打分进行排序后,第一名和倒数第一名配对,第二名和倒数第二名配对,依次类推,当配对的两个 broker 之间的分数差距大于阈值,则会在两者之间均摊负载,这样就能解决速度慢的问题了。

我们最后细化一下打分的算法,因为内存使用率和直接内存使用率都跟具体负载关系不大,因此我们还是会引入资源权值的机制来进行打分,即复用配置 loadBalancerBandwithInResourceWeight、loadBalancerBandwithOutResourceWeight、loadBalancerCPUResourceWeight、loadBalancerDirectMemoryResourceWeight、loadBalancerMemoryResourceWeight。这样我们就可以屏蔽掉内存使用率和直接内存使用率的影响。

多次触发

那 ThresholdShedder 打分时使用的历史加权算法呢?它是用来解决负载抖动的问题的,但是前面的分析与实验都证明了它会带来严重的负面影响,因此我们不能再采用这种方式来解决负载抖动问题。

我们模仿告警触发的方式:多次触发阈值才最终触发 bundle unload。比如说,当一对 broker 之间的差距超到阈值达到 3 次,那么就触发负载均摊。

如何实现也是个值得讨论的问题,比如说有三台 broker,broker1 负载 80,broker2 负载 80,broker3 负载 20。broker1、broker2 之间的负载相近,因此可能第一次执行时 broker1 与 broker3 配对并判定超载,但是第二次执行时 broker2 与 broker3 配对并判定超载,第三次还是 broker2 与 broker3,那此时我们要触发 bundle unload 吗?

要,因为负载相近的 broker 个数很多,那么要求的触发次数就不止是 3 次了,极端情况下可能是 3*n 次,n 为相近负载的 broker。一方面这会引入无法预测的复杂行为,管理员难以单纯从监控来预测负载均衡算法的执行,另一方面这会造成触发负载均衡执行的等待时间很长,且随集群规模增长而增长。

因此实现方式是维护一个Map<String, Integer>,key 为 broker 名,value 为触发次数,当发现Pair<brokerX,brokerY>超过阈值时,则往 Map 里插入Entry<brokerX,1>、Entry<brokerY,1>;下一次 shedding 时,如果再次判定 brokerX 超载(或低载),那么更新为Entry<brokerX,2>,如果 brokerX 没判定为超载(或低载),那么删除Entry<brokerX,1>

当某一次 shedding 判定Pair<brokerX,brokerZ> 超过阈值,并更新 Map 为Entry<brokerX,3>,如果次数阈值为 3 就会触发均摊 brokerX 与 brokerZ 的负载。

还是使用上面的例子来介绍这个算法。

  • 第一次 shedding:记录Entry<broker1,1>Entry<broker3,1>

  • 第二次 shedding:记录Entry<broker2,1>Entry<broker3,2>,剔除Entry<broker1,1>

  • 第三次 shedding:则无论第一名是 broker1 还是 broker2,都会有Entry<broker3,3>,从而触发 shedding。如果第一名是 broker1,则均摊 broker1 与 broker3 的负载;如果第一名是 broker2,则均摊 broker2 与 broker3 的负载。

在集群滚动重启、broker 扩容等情况下,往往会出现不同 broker 之间负载差距较大的情况,而我们又希望较为快速地完成负载均衡,因此我们引入了两个阈值:

  • loadBalancerAvgShedderLowThreshold,默认值为 15

  • loadBalancerAvgShedderHighThreshold,默认值为 40

两个阈值分别对应两个次数要求:

  • loadBalancerAvgShedderHitCountLowThreshold,默认值为 8

  • loadBalancerAvgShedderHitCountHighThreshold,默认值为 2

当配对的两个 broker 分数差距超过 LowThreshold 达到 HitCountLowThreshold 次数,或者超过 HighThreshold 达到 HitCountHighThreshold 次数时,则触发 bundle unload。比如说,当分数差距超过 15,则需要连续触发 8 次,当分数差距超过 40,则只需要连续触发 2 次。broker 间的负载差距越大,触发 bundle unload 所需要的次数就越小,这样就能适应 broker 扩缩容等场景,而负载抖动一般不会使 broker 的负载发生如此大的抖动,从而在稳定性和响应速度上达到很好的平衡。

在计算需要卸载多少流量时,AvgShedder 同样也会同时考虑消息速率和流量吞吐,优先考虑消息速率,为了避免引入过多的配置,AvgShedder 复用了如下三个 UniformLoadShedder 的配置:

minUnloadMessage 控制卸载的消息速率的最低阈值,minUnloadMessageThroughput 控制卸载的流量吞吐的最低阈值。

maxUnloadPercentage 控制分摊的比例,虽然默认值是 0.2,但是由于我们的目标是平均分摊两个 broker 的压力,因此我们设置的值是 0.5,这样负载均衡完成后两个 broker 的消息速率(或流量吞吐)就会几乎相等。

放置策略

前面已经讲过,AvgShedder 会捆绑卸载策略和放置策略,一个 bundle 在 shedding 的时候就已经根据卸载策略确定好它的下一个 owner broker 了。但是我们不只是在执行 shedding 的时候才会用到放置策略,在集群初始化、滚动重启、broker 缩容的时候也需要使用放置策略来分配 broker,那么该如何分配这些 bundle 呢?

我们采用哈希分配的方式:哈希映射 (bundle 名+随机数) 到 broker。哈希映射大致吻合均匀分布,因此 bundle 会大致均匀地分布到所有 broker 上,但是由于不同 bundle 之间的流量不同,集群会呈现一定程度的不均衡现象,但是这个问题不大,后续靠卸载策略来完成均衡即可,而且集群初始化、滚动重启、broker 缩容这种场景频率不高,因此影响不大。

另外读者可能会疑问:为什么不直接哈希映射 bundle 名到 broker,要多加一个随机数作为入参呢?

这是因为测试过程中发现了一个 corner case:假设 Pulsar 集群有 4 个节点,broker 列表为[broker1, broker2, broker3, broker4],集群稳定后加入一个新的 broker5,然后再关闭一个 broker3,broker 列表变为[broker1, broker2, broker5, broker4],即 broker5 代替了 broker3 的位置,由于集群总 broker 数目不变,因此 broker3 身上的大部分 bundles 经过 hash 算法计算,得到的 index 还是原来的值(2),因此 broker3 身上的绝大部分 bundles 都会转移到 broker5 身上,如果该 broker 刚好是新加入的 broker,负载不高,那情况很好,但如果该 broker 是负载相对较高的 broker,则可能导致它的负载超高,只能后面通过 shedding 来均衡负载。

如下图,关闭 broker3(蓝色线),发现原来没有负载的 broker5(红色线),把 broker3 的负载都承担了,消息速率直线拉升到 broker3 的位置,负载(CPU 使用率)也跟其余三个 broker 几乎差不多,而其余三个 broker 的负载没有发生什么改变。


效果对比

后续配置如下:

loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedderloadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedderloadBalancerAvgShedderHitCountHighThreshold = 2loadBalancerAvgShedderHitCountLowThreshold = 8loadBalancerAvgShedderLowThreshold = 15loadBalancerAvgShedderHighThreshold = 40maxUnloadPercentage = 0.5minUnloadMessageThroughput = 10 * 1024 * 1024minUnloadMessage = 10000
复制代码

AvgShedder 线上效果

负载均衡算法不考虑内存使用率、直接内存使用率,因此只剩下网卡使用率和 CPU 使用率,由于我们网卡性能较好,因此性能瓶颈在 CPU,给 broker 打分的依据也是 CPU 使用率。

因此我们添加如下 panel:CPU 使用率极差,来代表集群里最高载与最低载 broker 之间的负载差距。

max(pulsar_lb_cpu_usage) - min(pulsar_lb_cpu_usage)

可以看到,虽然 CPU 使用率极差偶尔会超过 15 的阈值,但是因为有触发次数的要求,因此最近一个月内只触发了一次 bundle unload,稳定性完美达到我们的预期。

用户可以根据自己的预期来配置阈值,如果希望集群的机器之间资源使用率更加均衡,可以进一步调小 loadBalancerAvgShedderHitCountLowThreshold 到 10,同时可以进一步调大 loadBalancerAvgShedderHitCountLowThreshold 来缓解阈值降低后的 bundle unload 频率可能变大的问题。

这里也顺便展示一下最大最小流量吞吐、消息速率的比值。


AvgShedder 对比 UniformLoadShedder+LeastLongTermMessageRate

  • 异构环境

跟前面测试 UniformLoadShedder + LeastLongTermMessageRate 一样的异构环境和压力负载,机器 XXX.34 是异构的,它的性能比另外三台机器强很多。


可以观察到,机器 XXX.34 的流量吞吐、消息速率显著高于其他机器。


消息速率和流量吞吐的最大最小比值甚至达到了 11,但这是很合理的,因为观察资源使用率我们就会发现:XXX.34 这台机器的负载还是最低的!

可以看到,XXX.34 的资源使用率还是不到其他机器的一半。读者可能会希望其他机器比如说 XXX.83 的负载进一步分给 XXX.34,从而使得资源使用率进一步均衡下来,但是当前 AvgShedder 算法还没法做到这种程度,只是在异构环境下会比 UniformLoadShedder 更优秀。

  • 负载抖动

进一步部署周期抖动的消费任务:


可以看到,一次 bundle unload 都没有触发!稳定性良好。

AvgShedder 对比 ThresholdShedder + LeastResourceUsageWithWeight

接下来部署跟 ThresholdShedder + LeastResourceUsageWithWeight 一样的测试环境,即机器是同构的,从而对比效果。

  • 启动压测任务

启动三个压测任务如下:



可以看到,启动压测任务后,XXX.83(绿色线)承载了最多的流量,也是 CPU 使用率最高的 broker,XXX.161(蓝色线)承载了最少的流量,也是 CPU 使用率最低的 broker,两者之间的打分差距 63-38.5=24.5 > 15,因此连续检查 8 次(等待 8min)后触发了负载均衡,XXX.83 与 XXX.161 均摊了流量。

只需触发这一次 bundle unload,集群就进入了稳定状态,而 ThresholdShedder + LeastResourceUsageWithWeight 花费了 22min,执行了很多次 bundle unload。


执行完这唯一一次负载均衡后,集群的消息速率和流量吞吐的最大最小比值也从 2.5 下降到 1.5 了,效果良好。

另外,我们还观察到一次负载抖动,XXX.32 的 CPU 负载突然飙高到 86.5,后又迅速下降回来,但它的流量吞吐并没有变化,这可能是因为机器上部署的其他进程等原因导致的,但是不管是什么原因,负载均衡算法都不能立马触发 bundle unload,而 AvgShedder 做到了这一点,再次展示了它应对负载抖动的能力。

  • broker 缩容

将 broker XXX.161(蓝色线)下线,观察集群流量变化情况。

可以看到,XXX.161(蓝色线)卸载下来的流量分给了 XXX.83(绿色线)、XXX.87(橙色线)、XXX.32(红色线),XXX.206(黄色线)没有分到流量。从日志中也可以看到这个分配结果:

2024-06-18T15:04:32,188+0800 [pulsar-2-18] DEBUG org.apache.pulsar.broker.loadbalance.impl.AvgShedder - expected broker:XXX.161:8081 is shutdown, candidates:[XXX.83:8081, XXX.32:8081, XXX.87:8081, XXX.206:8081]2024-06-18T15:04:32,188+0800 [pulsar-2-18] DEBUG org.apache.pulsar.broker.loadbalance.impl.AvgShedder - Assignment details: brokers=[XXX.83:8081, XXX.206:8081, XXX.87:8081, XXX.32:8081], bundle=public/default/0x9c000000_0xa0000000, hashcode=1364617948, index=02024-06-18T15:04:32,204+0800 [pulsar-2-19] DEBUG org.apache.pulsar.broker.loadbalance.impl.AvgShedder - expected broker:XXX.161:8081 is shutdown, candidates:[XXX.83:8081, XXX.32:8081, XXX.87:8081, XXX.206:8081]2024-06-18T15:04:32,204+0800 [pulsar-2-19] DEBUG org.apache.pulsar.broker.loadbalance.impl.AvgShedder - Assignment details: brokers=[XXX.83:8081, XXX.206:8081, XXX.87:8081, XXX.32:8081], bundle=public/default/0x40000000_0x44000000, hashcode=425532458, index=22024-06-18T15:04:32,215+0800 [pulsar-2-11] DEBUG org.apache.pulsar.broker.loadbalance.impl.AvgShedder - expected broker:XXX.161:8081 is shutdown, candidates:[XXX.83:8081, XXX.32:8081, XXX.87:8081, XXX.206:8081]2024-06-18T15:04:32,216+0800 [pulsar-2-11] DEBUG org.apache.pulsar.broker.loadbalance.impl.AvgShedder - Assignment details: brokers=[XXX.83:8081, XXX.206:8081, XXX.87:8081, XXX.32:8081], bundle=public/default/0x98000000_0x9c000000, hashcode=3472910095, index=3
复制代码

可以看到,这个分配结果已经相当均匀了,总共 3 个 bundle,选中了 3 个 broker 各分配一个,但从监控上看可以看到,不同 broker 上涨的流量幅度是不一样的,这表明了不同 bundle 的流量吞吐大小是不一样的,要想进一步优化效果,增加集群 bundle 的数目是合适的方案,不仅可以降低 bundle 之间的流量差距,还可以让部分流量也分配给第四个 broker。


因为资源使用率差距没有持续性地超过 15,缩容后没有触发 bundle unload。

集群的消息速率和流量吞吐的最大最小比值从 1.25 上涨到 1.5,集群整体的负载均衡情况还在可以接受的状态。

  • broker 扩容

集群缩容并且稳定后,重新加入机器 XXX.87,观察集群的负载均衡情况。

可以看到,新机器加入后没多久就触发 bundle unload 了,这是因为最高载和最低载的打分差距达到了高阈值 40,因此只需要连续两次触发就可以执行 bundle unload(1min 执行一次,因此只需等待 2min,不需要 8min)。


触发 bundle unload 后,将最高载的 XXX.161(蓝色线)与新加入的 XXX.87(橙色线)均摊负载了。

总结

-  Summary -

评分

前面做的这一系列实验已经验证了 AvgShedder 的能力:

  • 对于异构环境能尽力让最高能力的 broker 承担最多的负载,不会像 UniformLoadShedder + LeastLongTermMessageRate 一样从高载 broker 卸载压力给低载 broker。

  • 对于负载抖动的场景,也能轻松应对,我们线上环境中一个月只触发了一次 bundle unload。

  • 也不会有 ThresholdShedder + LeastResourceUsageWithWeight 的过度加载、过度卸载问题,不会发出错误的负载均衡指令。

  • 负载均衡速度也很快,大部分情况下只需要一两次 bundle unload 集群就能进入稳定状态。

因此,我们得到如下的评分表格:

使用

我们已经将代码贡献给社区,从 3.0.6, 3.2.4, 3.3.1 版本开始包含了此算法:

  • PR:https://github.com/apache/pulsar/pull/22949[1]

  • 文档:https://pulsar.apache.org/docs/next/concepts-broker-load-balancing-concepts/#avgshedder[2]

注意:代码在配置名称上发生了冲突,下面 PR 已经修复了这个问题,

  • https://github.com/apache/pulsar/pull/23156[3]

用户如果使用的是 3.0.6-3.0.7, 3.2.4, 3.3.1-3.3.2 版本,则可以添加下面配置来解决新算法不生效的问题:

loadBalancerPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
复制代码

除了上面为了应对 bug 的配置变更,要启用 AvgShedder 还需要修改下面三个配置:

loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedderloadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgSheddermaxUnloadPercentage=0.5
复制代码

最后

这篇文章我们对ThresholdShedder + LeastResourceUsageWithWeight、UniformLoadShedder + LeastLongTermMessageRate这两种负载均衡算法组合做了完整的实验,验证了上一篇文章分析得到的结论,证明其所存在的问题。

然后详细介绍新负载均衡算法 AvgShedder 的设计过程,并进行了对比实验,展示其在同样的实验条件下 AvgShedder 成功解决了前面所遇到的所有问题,最终得到一个评分表格,总结了所有负载均衡算法在不同场景下的得分。

至此,我们介绍完了负载均衡算法相关的知识,相信用户对如何挑选和配置负载均衡算法有了更多的把握,就算不使用 AvgShedder,也能合适地搭配社区现有的负载均衡算法,以适合自己的场景。

参考资料

[1]

https://github.com/apache/pulsar/pull/22949: https://github.com/apache/pulsar/pull/22949

[2]

https://pulsar.apache.org/docs/next/concepts-broker-load-balancing-concepts/#avgshedder: https://pulsar.apache.org/docs/next/concepts-broker-load-balancing-concepts/#avgshedder

[3]

https://github.com/apache/pulsar/pull/23156: https://github.com/apache/pulsar/pu


用户头像

AscentStream

关注

还未添加个人签名 2017-10-19 加入

还未添加个人简介

评论

发布
暂无评论
博文干货 | Pulsar 平均负载器(AvgShedder)_消息队列_AscentStream_InfoQ写作社区