PowerData

编者荐语:

一篇非常非常干货的技术文章,从代码层面剖析Pulsar是如何基于存算分离的基础之上,进行动态的负载均衡

以下文章来源于ApachePulsar ,作者冯文智

[

ApachePulsar .

Apache 软件基金会顶级项目,下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性。

](#)

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

注:此篇 《Pulsar 负载均衡入门》为《Apache Pulsar 优化实战》小册 2.0 中新增的一篇。新增内容覆盖了 Pulsar 负载均衡问题的介绍和调优实战,将优先放发给 PulsarMeetup 北京 2024 线下参会的同学(后附报名链接),之后会线上发布,还请大家踊跃报名线下活动领取。

Pulsar 负载均衡入门

-   Pulsar LB -

这是 BIGO 优化 Apache Pulsar 系列的第七篇文章,这篇文章我们会详细介绍 Pulsar 的负载均衡机制。负载均衡机制是 Pulsar 支持动态扩缩容的一个重要基础组件,扩容新的机器或者缩容机器时都能够自动地将负载分摊到各个 broker 机器上,极大地降低了运维负担。

当然,Pulsar 能做到动态负载均衡还是得益于它存算分离的架构,这是 Kafka 注定无法做到的,因为 Kafka 存储和计算耦合,将负载切换到其他节点必须要把数据也拷贝过去,这个成本是无法接受的,因此 Kafka 的扩缩容步骤也会很麻烦,必须要运维侧来接入,往往需要耗费很长时间来拷贝数据才能将负载缓慢切换到新节点上,但是像扩容这种操作常常会在紧急的场景下遇到,这就非常地尴尬了,想快快不了,只能干等。

而 Pulsar 只要加入了新 bookie、broker 节点,负载就能迅速且自动地分摊到新节点,这是从 Kafka 迁移到 Pulsar 的一个重要收益。Pulsar 负载均衡操作的基本单位是 Bundle,我们在 《BIGO 优化 Apache Pulsar 系列(四)-- Zookeeper 压力优化 broker 篇》  已经详细介绍了 Bundle,这里就不再赘述了,还没看过的读者有必要回过头去看一下。

负载策略接口

Pulsar主要有两个策略接口:

  • LoadSheddingStrategy 是卸载策略,负责识别高负载的broker,并将其承载的部分bundle unload下来,以降低负载。
  • ModularLoadManagerStrategy 是放置策略,负责给bundle分配broker。

接下来会重点介绍这两个接口。另外,Pulsar还支持多种负载均衡器,默认使用的是

org.apache.pulsar.broker.loadbalance.impl.ModularLoadManagerImpl

这篇文章也基于使用ModularLoadManagerImpl负载均衡器来展开。

配置loadBalancerEnabled用来控制是否启用负载均衡器,要想使用负载均衡功能,必须把它打开。默认配置为true,用户使用默认配置即可。

LoadSheddingStrategy

首先介绍一些配置,这些配置都使用默认的即可,这里只是为了介绍。

  • loadBalancerSheddingEnabled

    要想启用LoadSheddingStrategy策略,必须设置loadBalancerSheddingEnabled为true。如果把它关闭了,则无法主动识别高负载broker了,只有在broker重启、broker扩缩容的时候才会执行bundle unload,然后借由ModularLoadManagerStrategy来分配broker,因此也基本失去动态负载均衡的能力了。

  • loadBalancerSheddingIntervalMinutes

    LoadSheddingStrategy策略的执行时间间隔由配置loadBalancerSheddingIntervalMinutes决定,默认为1min一次。

    具体源码可以查看方法:org.apache.pulsar.broker.loadbalance.LoadSheddingTask#start。

  • loadBalancerSheddingGracePeriodMinutes

    在bundle unload的时候,该bundle包含的所有topic都会暂时不可用,直到bundle加载到新的broker上。因此bundle unload也是有代价的,虽然不高。为了避免同一个bundle频繁被unload,导致这些topic/分区的流量频繁抖动,LoadSheddingStrategy策略在挑选候选卸载bundle集合的时候,会将刚卸载“没多久”的bundle先过滤掉,“没多久”由配置loadBalancerSheddingGracePeriodMinutes来决定,默认为30min。

  • loadBalancerLoadSheddingStrategy

    配置loadBalancerLoadSheddingStrategy决定了使用哪个Shedding算法,从2.10版本开始默认算法为ThresholdShedder。有三种可用算法:ThresholdShedder、OverloadShedder、UniformLoadShedder。DeviationShedder虽然也实现了LoadSheddingStrategy接口,但其实是一个抽象类,不可用,最新版本已经删除了。

ThresholdShedder

ThresholdShedder 首先使用如下公式计算出每个Broker的最大资源使用率,资源包括CPU、直接内存、网卡入带宽、网卡出带宽。

org.apache.pulsar.policies.data.loadbalancer.LocalBrokerData#getMaxResourceUsageWithWeight(double, double, double, double)

资源使用率的计算还可以配置一个权值,对应loadBalancerBandwithInResourceWeightloadBalancerBandwithOutResourceWeightloadBalancerCPUResourceWeightloadBalancerDirectMemoryResourceWeight

其实2.11之前还会考虑内存的资源使用率,但是实践过程中发现broker的负载跟内存使用率没有关联性,所以下面PR就去掉了它。

实际上,直接内存使用率跟broker的负载也没有明显关联性,因此如下PR把loadBalancerDirectMemoryResourceWeight的默认值修改为0。

其实负载均衡算法里考虑的监控指标种类数不是越多越好的,种类多了反而容易出现各种预期外的动作,排查起来也会麻烦很多。目前我们只考虑CPU、网卡带宽,这就足够了。

计算出来每个Broker的最大资源使用率后,还会执行一个历史权值算法来得到最终的打分。

org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder#updateAvgResourceUsage

historyUsage = historyUsage == null ? resourceUsage : historyUsage *

historyPercentage由配置loadBalancerHistoryResourcePercentage决定,默认值是0.9,即上一次计算得到的分数占90%,当前计算得到分数只占10%。

引入这个历史权值算法是为了避免短时间的异常负载升高导致bundle切换,但其实这个算法会引入一些问题,这个后面在LeastResourceUsageWithWeight小节会详细介绍。

接下来计算出来整个集群所有broker的平均分数:avgUsage = totalUsage / totalBrokers,当任一broker的分数超过avgUsage一定阈值时,就判定该broker超载。阈值由配置loadBalancerBrokerThresholdShedderPercentage决定,默认值为10。

但是一个broker被判定为超载后,不一定会从它身上卸载流量。比如说,如果该broker只服务了一个bundle,如果卸载了该bundle会导致该broker没有承载任何流量,因此会直接跳过bundle unload。

因此,实践过程中我们要避免超大bundle的出现,比如说单单一个bundle就让该broker超载了,那么这种bundle放到哪个broker上面大概率都会超载。要避免超大bundle的出现,一个是配置合理的bundle个数,另一个是要避免超大分区的出现。一般一个分区流量吞吐达到20MB/s我们就会主动帮用户提出扩分区了。当然也可以使用bundle split特性来避免超大bundle的出现,但是为了避免高峰期触发bundle split造成业务流量抖动,我们没有开启功能。

那如果要卸载流量,会如何挑选bundle进行unload呢?

首先计算出来要卸载多少流量,然后卸载至少这么多流量的bundle。这里讲的流量包含进流量和出流量,比如说一个bundle进流量10MB,出流量20MB,那么就算作10+20=30MB;如果一个broker的进流量1GB,出流量2GB,那么就算作1+2=3GB。

算法如下:

首先计算得到要卸载的流量占broker流量的比例

percentOfTrafficToOffload = currentUsage - avgUsage - threshold + ADDITIONAL_THRESHOLD_PERCENT_MARGIN;

currentUsage为当前broker的最大资源使用率,avgUsage为前面得到的平均broker资源使用率,threshold即配置loadBalancerBrokerThresholdShedderPercentage,ADDITIONAL\_THRESHOLD\_PERCENT\_MARGIN为一个常量0.05。

然后需要卸载的流量大小为

minimumThroughputToOffload = brokerCurrentThroughput * percentOfTrafficToOffload;

举个例子,假设一个broker的最大资源使用率为80%,avgUsage为60%,threshold为10,该broker的流量吞吐为10GB,则需要卸载(0.8-0.6-0.1+0.05)*10=1.5GB的bundle。

另外,这里还有一个配置loadBalancerBundleUnloadMinThroughputThreshold,用来指定最小的卸载流量阈值,默认值为10MB,应该是觉得卸载太少流量没什么效果,反而造成流量中断不划算吧。

计算出来要卸载多少流量的bundle之后,就要开始挑选bundle了,挑选原则很简单,从大到小开始挑选,因此小流量吞吐的bundle几乎不可能在负载均衡过程中被挑选到。这一点在  《BIGO优化Apache Pulsar系列(四) -- Zookeeper压力优化broker篇》  中也提到过,并且作为优化Zookeeper压力的一个依据。

但是这个算法有一个缺陷,它只考虑了高载的broker,没有考虑低载broker的情况。比如说,有11个broker,其中10个负载为80%,1个负载为0%,那么平均负载为80*10/11=72.73,卸载阈值为72.73+10=82.73。由于80<82.73,将不会触发卸载,那么会永远有一个空闲Broker的负载为0%。这个case其实不难触发,因为broker滚动重启就容易遇到,新启动的broker负载很低。

这个PR修复了这个缺陷:https://github.com/apache/pul...[3]。打了一个补丁,当存在broker的最大资源使用率current usage < average usage - threshold,则从最高载的broker上卸载minimumThroughputToOffload = brokerCurrentThroughput * threshold * LOWER_BOUNDARY_THRESHOLD_MARGIN 大小的流量。

但是注意:这个特性由配置lowerBoundarySheddingEnabled来控制开关,默认为关闭状态,需要的用户需要主动设置为true。

OverloadShedder

接下来介绍OverloadShedder,在理解不同Shedder的时候,只需要抓住两个点:

  • 什么样的broker判定为超载?
  • 判定为超载后,挑选哪些bundles进行unload?

OverloadShedder跟ThresholdShedder在判定broker超载时会不一样,但是计算出来需要卸载的流量吞吐大小后,挑选bundle的算法是相同的,都是从大到小开始挑选bundle。

下面详细介绍一下OverloadShedder是如何判定broker超载的。首先,同样会给每个broker进行打分。

org.apache.pulsar.policies.data.loadbalancer.LocalBrokerData#getMaxResourceUsage

但是注意:这里并没有使用loadBalancerCPUResourceWeight等配置进行加权计算,而是简单地使用了资源使用率,但是正如前面所描述的,直接内存跟真实负载并没有明显关系,因此这里很有可能会被直接内存使用率所干扰,导致误判broker的真实负载。因此,最好让loadBalancerCPUResourceWeight等配置也对OverloadShedder生效,提供一个手段来避免直接内存使用率的干扰。如下PR:

https://github.com/apache/pul...[4]

得到每个broker的负载打分后,跟loadBalancerBrokerOverloadedThresholdPercentage比较,如果超过该阈值则判定为超载,默认值为85%。

一个broker判定为超载后,就要计算需要卸载多少流量,算法如下:

举同样的例子,假设一个broker的最大资源使用率为90%,threshold为85,该broker的流量吞吐为10GB,则需要卸载(0.9-0.85+0.05)*10 GB = 1GB

后面的bundle挑选算法跟ThresholdShedder是完全一致的,这里就不赘述了。

这个算法比较简单,但是有很多严重的corner case,使用率应该不高。这里介绍两个case:

  • 当集群每个broker负载都达到阈值时,会一直执行bundle unload,但是只会从一个高载broker切换到另一个高载broker,这是毫无意义的。
  • 如果集群没有broker负载达到阈值时,加入新的broker并不会将流量均衡到新broker上面。

这两点的影响都是比较严重的,所以不建议采用OverloadShedder。

UniformLoadShedder

根据前面分析可见,ThresholdShedder跟OverloadShedder都无法处理低载broker资源浪费的问题,因此UniformLoadShedder诞生了。

注记:虽然ThresholdShedder后面加入了lowerBoundaryShedding特性来解决低载broker的问题,但这是在加入UniformLoadShedder之后的事情了。

下面详细介绍一下UniformLoadShedder的算法。它首先会统计出最大、最小消息速率和最大、最小流量吞吐以及对应的broker,如下:

然后计算最大最小的差距,有两个阈值,分别对应消息速率和吞吐大小。

注记:这两个配置值的类型都是double。

  • loadBalancerMsgRateDifferenceShedderThreshold

负载最高和负载最低的broker之间的消息速率百分比阈值,默认值为50,因此当最大消息速率是最小消息速率的1.5倍时,即可触发bundle unload。例如:具有 50K msgRate 的broker1 和具有 30K msgRate 的broker2 将具有 (50-30) / 30 = 66% msgRate 差异,那么负载均衡器可以将bundle从broker-1 卸载到broker-2。

注记:虽然配置描述这是一个百分比值,但是其实可以允许超过100的值,从而允许最大最小消息速率之间更大的差距。设最大、最小消息速率为X、Y,阈值为P,则触发条件为:

当P为100时,则允许最大两倍的差距。

  • loadBalancerMsgThroughputMultiplierDifferenceShedderThreshold

负载最高和负载最低的broker之间的消息吞吐量倍数阈值,默认值为4,因此当最大流量吞吐是最小流量吞吐的4倍时,即可触发bundle unload。例如:broker1 的 msgRate 为 450MB,broker2 的 msgRate 为 100MB,msgThroughout 的差异为 450 / 100 = 4.5 倍,那么负载均衡器可以将bundle从 Broker-1 卸载到 Broker-2。

如果阈值配置为不大于0的值,则会禁用根据消息速率或者流量吞吐来shedding;如果两个阈值都达到了,则会优先根据消息速率来执行shedding。

接下来计算需要卸载多少消息速率/吞吐对应的bundle,算法为:最大最小的差值 乘以 maxUnloadPercentage,默认为0.2。

如果根据消息速率来执行shedding,则做一些检查,比如说超载broker的bundle个数要大于1,待卸载的消息速率大小要达到阈值minUnloadMessage,默认配置为1000。

通过检查后,则开始挑选bundle,同样是从大到小挑选,但是还多了一个限制,unload的bundle总个数不能超过maxUnloadBundleNumPerShedding,默认无限制。

·如果根据吞吐来执行shedding,也有类似的检查,卸载的流量大小起码要达到minUnloadMessageThroughput,默认为1MB。

挑选bundle的逻辑基本类似,这里就不赘述了。

介绍完UniformLoadShedder的算法,我们显然可以得到如下信息:

  • UniformLoadShedder没有低载broker资源浪费的问题,算法考虑了低载broker的情况,这也是它提出来的动机。
  • UniformLoadShedder在挑选bundle的时候,不仅会根据流量大小来挑选,还会根据消息速率来挑选;而ThresholdShedder和OverloadShedder只会根据流量大小来挑选。不过这一点的影响比较难看出来。
  • UniformLoadShedder没有处理负载抖动的逻辑,比如说流量短时间内飙很高或者降很低,这个负载数据点它是正常采纳的,从而触发bundle unload,但是没过多久这个topic的流量就会恢复正常,那么很有可能要再次触发bundle unload,这种bundle unload实际上是要避免的。这种场景还是很常见的,如下例:

而ThresholdShedder在打分时会对历史打分加上很高的权重,因此能处理这种场景。

·UniformLoadShedder在判定高载低载broker的时候,不会依赖CPU使用率、网卡使用率等指标来判定的,而是根据消息速率、流量吞吐大小来判定的;而ThresholdShedder和OverloadShedder都依赖CPU使用率等机器资源指标来判定。

如果我们使用的集群机器是异构的话,比如说不同机器的硬件配置不同,或者broker所在的机器上还有其他进程共享资源,那么UniformLoadShedder很有可能会误判高低载broker,从而将负载从高性能但低负载的broker迁移到低性能但高负载的broker上,因此不建议用户在异构环境下使用UniformLoadShedder。

·UniformLoadShedder执行一次shedding只会从一台最高载broker上卸载bundle,对于大集群来说,这可能会使得集群消耗相当长的时间才能完成所有负载均衡任务。

比如说当前集群有100台高载broker,扩容100台机器,粗略估算需要执行100次shedding才能完成均衡任务,但是由于LoadSheddingStrategy策略的执行时间间隔由配置loadBalancerSheddingIntervalMinutes决定,默认为1min一次,因此需要100min才能完成所有任务,对于使用大分区topic的用户来说,这100min内他的任务大概率会多次被切断连接,这是非常影响使用体验的。

从用户体验的角度,在遇到扩缩容broker、用户上下线导致的负载增减等偶发事件时,快速地完成一次负载均衡之后,后面都基本不用执行负载均衡才是理想的效果,而不是长时间执行负载均衡。

ModularLoadManagerStrategy

前面介绍了LoadSheddingStrategy策略的所有实现,LoadSheddingStrategy 策略是用来卸载高负载broker的bundle的,但是一个集群的负载均衡模块要想运行效果良好,不仅要“卸载”对,还要“放”对,两者缺一不可,因此两者之间的配合度也是一个值得重点关注的点。

放置策略由配置loadBalancerLoadPlacementStrategy控制,默认使用LeastLongTermMessageRate。

实现的接口名为org.apache.pulsar.broker.loadbalance.ModularLoadManagerStrategy,有三种实现,但是一般不会考虑RoundRobinBrokerSelector,它是使用RoundRobin的方式来放置bundle,没有考虑真实的负载等指标,我们接下来也只介绍另外两种。

LeastLongTermMessageRate

LeastLongTermMessageRate 策略一开始是为了配合OverloadShedder来使用的,因此它在计算Broker 的负载分数scores时也没有考虑权值配置,直接使用 CPU、直接内存和网络的最大资源使用率作为broker的分数,并且复用了OverloadShedder的配置loadBalancerBrokerOverloadedThresholdPercentage,如果分数大于它(默认 85%),则设置 score=INF;否则更新broker的分数为该broker的长期聚合得到的消息进出速率之和。

即score= longTermMsgIn rate + longTermMsgOut rate,最后从分数最小的 Broker 中随机选择一个broker返回。如果每个broker的分数都是INF,则随机挑选broker来返回。

LeastLongTermMessageRate这里的打分算法其实本质上是根据消息速率来打分的,虽然一开始会考察最大资源使用率,但是它是为了排除掉超载broker,这些超载broker不会作为候选broker被返回(除非全部broker都超载),因此绝大部分情况下都是以消息速率的大小作为分数对broker进行排序。

既然LeastLongTermMessageRate的打分是根据消息速率来执行的,那么它也跟UniformLoadShedder一样,会面临异构环境的问题。

那可不可以把这里改成根据资源使用率来打分呢?答案是不可以。在详细回答这个问题之前,先介绍一个问题:过度加载。举个例子,假如当前存在11个broker,资源使用率均为50%左右,阈值设置为70%,由于集群压力不大尝试缩容3个broker,那么这三个broker上面的bundle会全部卸载下来,然后开始加载到其余broker上。因为负载数据是定期更新的,远远赶不上topic lookup、bundle load的速度(而且由于Zookeeper的性能限制,也注定无法加快),因此leader broker内存中每个broker的负载数据可以认为是短期内无更新的,那么这段时间内最低分的broker都会是同一个broker,因此bundle会全部加载到该低载broker上,即过度加载。

这里LeastLongTermMessageRate使用了PreallocatedBundleData里的数据解决了这个问题。

org.apache.pulsar.broker.loadbalance.impl.LeastLongTermMessageRate#getScore

在对broker打分时,不仅会使用broker自身聚合得到的长期消息速率,还会加上已经分配给该broker但是还没统计进broker负载数据的bundle的消息速率。比如说现在有两个bundle均为20KB/s,broker1、broker2分别为100KB/s、110KB/s,则分配bundle的时候首先分配给了broker1,但是broker1的负载数据短期内还不会更新,接下来要分配第二个bundle,此时虽然broker1的负载数据还没变化,但是打分已经变成了100+20=120KB/s,因此这次分配bundle给broker2,这就避免了过度加载问题。

因此现在可以回答前面提出的问题:不可以改成根据资源使用率来打分,因为bundle只有消息速率、流量吞吐的统计数据,无法预测加载一个bundle会使某个broker增加多少资源使用率,因此也会无法使用PreallocatedBundleData的数据来预估。

注记:这个问题正是后面的算法LeastResourceUsageWithWeight所要面对的,因为它是根据资源使用率来打分的。

下面我们尝试结合LoadSheddingStrategy策略来一起分析效果。首先是

  • LeastLongTermMessageRate + OverloadShedder

这是最开始的一个组合,但是由于OverloadShedder固有的一些缺陷,不太推荐。

  • LeastLongTermMessageRate + ThresholdShedder

这个组合比LeastLongTermMessageRate+OverloadShedder还要更糟糕一点,不推荐。因为OverloadShedder给broker打分时会使用最大加权资源使用率以及历史分数来计算,而LeastLongTermMessageRate是根据消息速率来打分的,这种卸载和放置的标准不一致很有可能导致反复的负载均衡执行,卸载出来bundle后却放到了错误的brokerX上,导致下一次执行shedding时又判定brokerX为超载。这也是为什么后面会提出新的放置策略LeastResourceUsageWithWeight。

  • LeastLongTermMessageRate + UniformLoadShedder

这是一个比较合适的组合,推荐。卸载和放置都是以消息速率为标准,但是以消息速率为标准自然就会面临异构环境的问题。当然,LeastLongTermMessageRate也部分考虑了资源使用率,可以靠调节loadBalancerBrokerOverloadedThresholdPercentage来部分缓解这个问题。

LeastResourceUsageWithWeight

LeastResourceUsageWithWeight使用同样的打分算法对broker进行打分,即使用加权最大资源使用率,结合历史分数来加权计算得到当前分数,并得到所有broker的平均分。接下来根据配置loadBalancerAverageResourceUsageDifferenceThresholdPercentage来挑选候选broker。

如果某个broker的打分加上此阈值仍然不大于平均分数,则该broker会加入到候选broker列表里,得到候选broker列表后,从中随机挑选一个broker;如果没有候选broker,则从全体broker里随机挑选。

例如:broker1 资源使用率 10%,broker2 资源使用率 30%,broker3 资源使用率 80%,则平均资源使用率是 40%。放置策略可以选择 Broker1 和 Broker2 作为最佳候选者,因为阈值为10,10+10<=4030+10<=40。这样,从broker3身上卸载下来的bundle就会一致均摊到broker1、broker2身上,而不是全砸给broker1。

再回顾一下前面介绍的过度加载问题,因为这里是根据资源使用率来对broker进行打分排序的,无法将刚分配的bundle的负载估算到broker身上,因此如果只选打分最低的broker会导致过度加载的问题。因此LeastResourceUsageWithWeight引入了新配置loadBalancerAverageResourceUsageDifferenceThresholdPercentage,挑选一批候选的broker,然后再随机挑选,以此来避免过度加载问题。

但实践过程会发现,loadBalancerAverageResourceUsageDifferenceThresholdPercentage很难确定一个合适的值,经常会触发兜底的全局随机挑选逻辑。

比如说,当前集群有6个broker,打分分别为40,40,40,40,69,70,则平均分为49.83,使用默认的配置,则没有候选broker,因为40+10>49.83,此时触发兜底的全局随机挑选逻辑,因此bundle可能会从高载的broker5卸载到同样高载的broker6,或者反过来,这就是错误的负载均衡决策,而错误的负载均衡决策正是集群反复执行负载均衡的根本原因!优秀的负载均衡算法只需要一两次决策。

尝试将配置值调小从而扩大随机池,如设置为0,那么也可能将部分高载的broker纳入候选broker列表里。如下例,当前集群有5个broker,打分分别为10,60,70,80,80,则平均分为60,配置值为0,则broker1、broker2都是候选broker,此时如果broker2分担一半卸载下来的流量,它大概率会超载。

因此LeastResourceUsageWithWeight算法是很难配置得好这个值的,避免不了错误的负载均衡。当然,如果你要使用ThresholdShedder算法的话,ThresholdShedder + LeastResourceUsageWithWeight这个组合还是会优胜于 ThresholdShedder + LeastLongTermMessageRate 组合,因为起码LeastResourceUsageWithWeight的打分算法跟ThresholdShedder 的是一致的。

最后,我们再介绍一下历史加权打分算法的问题。历史加权打分算法被ThresholdShedder、LeastResourceUsageWithWeight算法所使用,算法如下:

historyUsage = historyUsage == null ? resourceUsage : historyUsage * historyPercentage + (1 - historyPercentage) * resourceUsage;

historyPercentage默认值为0.9,可见上一次计算得到的分数对当前分数占据了绝大部分影响,当前的最大资源使用率只占0.1,很明显这能解决负载抖动的问题,但是引入这个算法是有其副作用的!

比如过度卸载的问题。如下例,当前集群有1台broker1,负载为90%,由于其稳定运行了,因此历史打分为90,现加入一台broker2,其当前负载为10%。则

  1. 第一次执行shedding:broker1打分90,broker2打分10,为简单起见假设算法会移动部分bundles以使得两者的score相同,则负载迁移完成后broker1、broker2的真实负载均为50分。
  2. 第二次执行shedding:broker1打分90*0.9+50*0.1=86,broker2打分10*0.9+50*0.1=14

    注意这里broker1的真实负载是50,但是却高估为86!broker2的真实负载也是50,但是却低估为14!

    由于两者打分仍然悬殊,虽然两者的真实负载已经相同,但是还会继续卸载流量。因此,从broker1中卸载36分对应的流量给broker2,导致broker1的负载变成14,broker2的负载变成86,负载倒过来了!

  3. 第三次执行shedding:broker1打分86*0.9+14*0.1=78.8,broker2打分14*0.9+86*0.1=21.2,这个时候还是判断broker1负载比broker2高,还是会从broker1卸载流量给broker2,那么会把broker1身上剩下的所有负载都卸了,broker1变成跟broker2刚加入一样,不承担任何bundles,所有负载加到broker2上了。

这个例子虽然是一个理想化的理论分析,但是我们还是可以看到,采用历史打分算法会严重错估broker的真实负载,特别是在broker新增负载或者降低负载的时候,它虽然能避免负载抖动的问题,但是会引入更严重且更广泛的问题:错估broker的真实负载,从而导致错误的负载均衡执行。在这个例子中,就导致了过度卸载的问题,这个问题我们会在下一篇文章中的实验里复现,而且我们还会从实验中发现:历史加权打分算法会加剧过度加载问题。

注记:如果用户想避免这个缺陷,可以把配置loadBalancerHistoryResourcePercentage设置为0,但是这就无法应对负载抖动的问题了。

策略选择

根据前面的这些分析,虽然我们有三种shedding策略和两种放置策略,可以产生3*2=6种组合,但其实我们只有两种比较推荐的选择:

  • ThresholdShedder + LeastResourceUsageWithWeight
  • UniformLoadShedder + LeastLongTermMessageRate

这两种选择各有优缺点,读者可根据场景需求自行选择,下面表格总结了两种选择的优缺点:

策略

适应异构环境

适应负载抖动

过度加载问题

过度卸载问题

负载均衡速度

ThresholdShedder+ LeastResourceUsageWithWeight

一般①

一般③

UniformLoadShedder +LeastLongTermMessageRate

差②

一般④

  • ① 在适应异构环境这一点上,ThresholdShedder + LeastResourceUsageWithWeight只能打分为一般,这是因为ThresholdShedder也不是完全适应异构环境的,虽然它不会将高载的broker误判为低载,但异构环境还是会对ThresholdShedder的负载均衡效果产生较大影响。

    比如说,当前集群存在三台broker,资源使用率分别为10,50,70,broker1、broker2同构,broker3上面是空载,但是因为部署了其他进程,导致它资源使用率达到70。此时我们会希望broker分摊部分压力给broker1,但是由于平均负载为43.33,43.33+10>50,因此不会判定broker2为超载,而超载的broker3也没有流量可以卸载,从而使得负载均衡算法处于无法工作的状态。

  • ② 还是相同的场景,如果使用UniformLoadShedder +LeastLongTermMessageRate,则问题会更严重一些,会将部分负载从broker2卸载到broker3身上,那么broker3服务的那些topic都会发生严重的性能下降,因此打分为差。

    因此,不建议将Pulsar运行在异构的环境下,当前的负载均衡算法都无法很好地适应,如果实在无法避免,那么建议选择ThresholdShedder + LeastResourceUsageWithWeight。

  • ③ 在负载均衡速度方面,虽然ThresholdShedder + LeastResourceUsageWithWeight能一次性将所有高载broker的负载卸下来,但是由于历史打分算法会严重影响负载均衡决策的准确性,因此实际上它也需要好多次负载均衡执行才能最终稳定下来,这一点在下一篇文章的实验里会得到验证,因此只能得分一般。
  • ④ 而UniformLoadShedder + LeastLongTermMessageRate由于一次只能处理一个超载broker,因此在broker数目较多时需要耗费较长时间来完成负载均衡,因此得分也是一般。

总结

这篇文章详细介绍了Pulsar的负载均衡机制,主要分为卸载策略和放置策略,分别有多种实现算法,每个算法都会涉及到较多配置,如果不深入了解过这些算法,用户在配置这些值时往往会一头雾水。算法的选择也是一件对用户要求较高的事情,卸载策略有3种,放置策略有2种,因此用户是能搭配出3*2=6种组合方式的,但实际上只有两种组合方式是值得推荐的,而且这两种组合也各有优缺点,用户还需要根据自己的场景需求来选择适合的策略,具体的配置值也需要根据具体场景来进一步微调。

但是根据前面的统计表格可知,这两种组合其实都不算完美,都有各自的硬伤,因此我们内部开发了一个新的负载均衡算法AvgShedder,它同时实现了卸载策略和放置策略,并且解决了前面提到的一些问题,目前已经在线上运行快两年了,下一篇文章我们会详细介绍它。

参考资料

[1]

https://github.com/apache/pul... https://github.com/apache/pul...

[2]

https://github.com/apache/pul... https://github.com/apache/pul...

[3]

https://github.com/apache/pul... https://github.com/apache/pul...

[4]

https://github.com/apache/pul... https://github.com/apache/pul...

PulsarMeetup 北京 2024

-   PulsarMeetup -

Pulsar Meetup 北京 2024 活动将于 2024 年 9 月 22 日(周日)由谙流科技和小红书联合举办。大会邀请了小红书、腾讯、360、滴滴和谙流科技的诸多 Pulsar 专家,给大家分享最新的 Pulsar 场景案例、 技术探究、 运维实战和生态演讲,详见议程。干货多多,礼品多多,不容错过。

  • 主办单位:AscentStream 谙流科技、小红书 活动时间:2024年09月22日 14:00-17:30

    活动地址:北京城奥大厦 15A层(阿凡达会议室)(安贞门地铁站 300米) 活动形式:线下为主,线上同步直播和转播

更多内容:

邀请函 | Pulsar Meetup 北京 2024

邀请函 | Pulsar Meetup 北京 2024 报名入口

Pulsar Meetup 北京 2024 讲师和议题介绍

扫码报名

-   Registration -

本次活动以线下活动为主,同时辅以线上直播和合作伙伴转播。线下人数限制在 120 人,先报先得,赶紧来参加吧。扫描下方二维码,免费报名线下活动,也可从我们合作伙伴处报名。

PulsarMeetup 北京 2024 报名入口

线上直播可以预约PowerData视频号(抢先关注),同步观看直播。

热点推荐

REVIEW

参与问卷赢百页小册《Apache Pulsar 调优指南》

联系 PulsarBot 报名成为社区志愿者

最新 Pulsar 岗位招聘,快来点击(公众号菜单-联系社区-名企直达)

联系社区

微信号:pulsarbot

视频号:AscentStream谙流科技

**

结尾

**

- The End -


PowerData
1 声望6 粉丝

PowerData社区官方思否账号