博文干货 | 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.ThresholdShedder
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.LeastResourceUsageWithWeight
使用ThresholdShedder + LeastResourceUsageWithWeight
的组合,配置基本都使用默认值,但是关闭了bundle split、bundle均匀分布的特性。
loadBalancerDistributeBundlesEvenlyEnabled=false
loadBalancerAutoBundleSplitEnabled=false
loadBalancerAutoUnloadSplitBundlesEnabled=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:8081
2024-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/s
2024-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.UniformLoadShedder
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.LeastLongTermMessageRate
使用UniformLoadShedder + LeastLongTermMessageRate的组合,配置基本都使用默认值,因此允许最大消息速率是最小消息速率的1.5倍,最大流量吞吐是最小流量吞吐的4倍。
同样关闭了bundle split、bundle均匀分布的特性。
loadBalancerDistributeBundlesEvenlyEnabled=false
loadBalancerAutoBundleSplitEnabled=false
loadBalancerAutoUnloadSplitBundlesEnabled=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.AvgShedder
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
loadBalancerAvgShedderHitCountHighThreshold = 2
loadBalancerAvgShedderHitCountLowThreshold = 8
loadBalancerAvgShedderLowThreshold = 15
loadBalancerAvgShedderHighThreshold = 40
maxUnloadPercentage = 0.5
minUnloadMessageThroughput = 10 * 1024 * 1024
minUnloadMessage = 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=0
2024-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=2
2024-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集群就能进入稳定状态。
因此,我们得到如下的评分表格:
策略 | 适应异构环境 | 适应负载抖动 | 过度加载问题 | 过度卸载问题 | 负载均衡速度 |
---|---|---|---|---|---|
ThresholdShedder+ LeastResourceUsageWithWeight | 一般 | 良 | 差 | 差 | 一般 |
UniformLoadShedder + LeastLongTermMessageRate | 差 | 差 | 良 | 良 | 一般 |
AvgShedder | 一般 | 良 | 良 | 良 | 良 |
使用
我们已经将代码贡献给社区,从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.AvgShedder
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
maxUnloadPercentage=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