Kafka是一款高性能、高可用的分布式
发布订阅消息系统,为了将资源最大化的利用,所以针对broker的负载均衡就尤为重要。以下整理了保障集群的负载相对均衡的一些机制。
partition replica 负载均衡
我们知道生产/消费的真正实体是对应的分区的leader,所以服务端的负载是否均衡就基本上取决于leader分布的是否均匀。
那么服务端是如何尽可能的让leader分布均匀的呢?
首先我们需要了解在创建topic的时候分区副本是如何分布的,然后再了解是如何从这些副本中选出leader的。
我们先来看看topic创建时,分区副本的分布过程是什么样子的,我们假设创建一个TopicA,有5分区,3副本
过程概述:
-
随机挑选一个
startIndex
,图示中挑选的startIndex = 0
。 -
tartIndex`开始按照 broker顺序 生成对应的第一个副本。
-
下一轮从
startIndex + 1
开始顺序生成第二个副本,以此类推。
这里的随机挑选也是保障服务端负载均衡的一部分,让分区尽可能的打散到各个broker中。
leader 负载均衡
此时,我们可以得到分区副本的分部列表
那么如何从这些副本中选出leader的?基于优先副本
,也就是挑选出AR列表中的第一个brokerId上的副本成为leader。
另外补充一下,随着时间的推移,集群状态的变化,或多或少会发生一些leader的切换和迁移,可能会导致某些broker上的leader会多一些从而导致负载不均衡,kafka针对这个问题提供了分区自动重平衡的功能,对应broker端的参数是 auto.leader.rebalance.enable
,默认为true即开启。
开启之后呢,Controller会启动一个定时任务,每隔一段时间(5分钟)会去轮询所有的broker,计算每个broker节点的分区不平衡率,看是否超过了设置的阈值(默认10%)如果达到则自动进行分区迁移,将leader副本迁移回原先的优先副本所在的broker。
需要注意的是,这个自动迁移的过程可能会引起负面的性能问题,从而影响到现有的业务,所以需要根据业务情况来判断是否要开启这个功能;另外kafka有提供专门的脚本去手动执行该迁移操作:kafka-preferred-replica-election.sh
consumer group 负载均衡
通过 partition.assignment.strategy 参数去设置
RangeAssignor
按照 Topic 的维度进行分配的,也就是说按照Topic 对应的每个分区平均的按照范围区段分配给Consumer 实例。这种分配方案是按照Topic 的维度去分发分区的,此时可能会造成先分配分区的Consumer 实例的任务过重。
RoundRobinAssignor
可以理解为为轮询,也就是顺序一个一个的分发。其中代码里面的大概逻辑如下:拿到组内所有Consumer 订阅的 Topic Partition,按照顺序挨个分发给 Consumer,此时如果和当前Consumer 没有订阅关系,则寻找下一个Consumer。从上面逻辑可以看出,如果组内每个消费者的订阅关系是同样的,这样TopicPartition 的分配是均匀的。
当组内每个消费者订阅的Topic 是不同的,这样就可能会造成分区订阅的倾斜。
StickyAssignor
Kafka从0.11 版本开始引入,是分配策略中最复杂的一种,从字面上可以看出是具有“粘性”的分区策略,其主要实现了两个功能:
- 主题分区的分配要尽可能的均匀;
- 当Rebalance 发生时,尽可能保持上一次的分配方案。
当然,当上面两个条件发生冲突是,第一个提交件要优先于第二个提交,这样可以使分配更加均匀。下面我们看一下官方提供的 2 个例子,来看一下RoundRoubin 和 Sticky 两者的区别。
从上面我们可以看出,初始状态各个Consumer 订阅是相同的时候,并且主题的分区数也是平均的时候,两种分配方案的结果是相同的。但是当Rebalance 发生时,可能就会不太相同了,加入上面的C1 发生了离组操作,此时分别会有下面的 Rebalance 结果:
从上面Rebalance 后的结果可以看出,虽然两者最后分配都是均匀的,但是RoundRoubin 完全是重新分配了一遍,而Sticky 则是在原先的基础上达到了均匀的状态。
broker controller 选举
每个Broker都会在Controller Path (/controller)上注册一个Watch。 当前Controller失败时,对应的Controller Path会自动消失(因为它是临时节点),此时该Watch被fire,所有“活” 着的Broker都会去竞选成为新的Controller (创建新的Controller Path),但是只会有一个竞选成功(这点由Zookeeper保证)。竞选成功者即为新的Leader,竞选失败者则重新在新的Controller Path上注册Watch。因为Zookeeper的Watch是一次性的, 被fire一次之后即失效,所以需要重新注册。
controller_epoch
-
controller_epoch记录控制器发生变更的次数,初始值为1,当控制器发生变更时,每选出一个新的控制器就将该字段值加1。
-
每个和控制器交互的请求都会携带上controller_epoch这个字段,如果请求的controller_epoch值小于内存中的controller_epoch值,则认为这个请求是向已经过期的控制器所发送的请求,那么这个请求会被认定为无效的请求。
leader 选举
由controller执行:
- 从Zookeeper中读取当前分区的所有ISR(in-sync replicas)集合
- 调用配置的分区选择算法选择分区的leader
broker controller 作用
主题管理(创建、删除、增加分区)
- 帮助我们完成对 Kafka 主题的创建、删除以及分区增加的操作。
分区重分配
- 分区重分配主要是指,kafka-reassign-partitions 脚本,提供的对已有主题分区进行细粒度的分配功能。这部分功能也是控制器实现的。
Preferred 领导者选举
- Preferred 领导者选举主要是 Kafka 为了避免部分 Broker 负载过重而提供的一种换 Leader 的方案
集群成员管理(新增 Broker、Broker 主动关闭、Broker 宕机)
- 包括自动检测新增 Broker、Broker 主动关闭及被动宕机。这种自动检测是依赖于前面提到的 Watch 功能和 ZooKeeper 临时节点组合实现的。
- 比如,控制器组件会利用 Watch 机制检查 ZooKeeper 的 /brokers/ids 节点下的子节点数量变更。目前,当有新 Broker 启动后,它会在 /brokers 下创建专属的 znode 节点。一旦创建完毕,ZooKeeper 会通过 Watch 机制将消息通知推送给控制器,这样,控制器就能自动地感知到这个变化,进而开启后续的新增 Broker 作业。
- 侦测 Broker 存活性则是依赖于刚刚提到的另一个机制:临时节点。每个 Broker 启动后,会在 /brokers/ids 下创建一个临时 znode。当 Broker 宕机或主动关闭后,该 Broker 与 ZooKeeper 的会话结束,这个 znode 会被自动删除。同理,ZooKeeper 的 Watch 机制将这一变更推送给控制器,这样控制器就能知道有 Broker 关闭或宕机了,从而进行“善后”。
数据服务
就是向其他 Broker 提供数据服务。控制器上保存了最全的集群元数据信息,其他所有 Broker 会定期接收控制器发来的元数据更新请求,从而更新其内存中的缓存数据。
-
所有主题信息。包括具体的分区信息,比如领导者副本是谁,ISR 集合中有哪些副本等。
-
所有 Broker 信息。包括当前都有哪些运行中的 Broker,哪些正在关闭中的 Broker 等。
zookeeper 作用
Kafka主要使用ZooKeeper来保存它的元数据、监控Broker和分区的存活状态,并利用ZooKeeper来进行选举。
- 保存Kafka的Broker信息,
/brokers/ids/[0...N]
- 保存主题和分区的信息。
/brokers/topics/
- 保存分区当前的leader和所有的ISR的BrokerID