当前位置: 首页>后端>正文

深入了解Kafka可靠性(三)

前言

我们知道 Kafka 的分区的多副本设计是采用主从设计的,leader 负责对外服务,follower 负责数据同步。如果 leader 宕机了,那么就要重新选举新的 leader。互联网发生网络分区是很常见的,那么要如何避免脑裂问题就很重要了。

leader 选举机制

在 Kafka 集群中会有一个或者多个 broker,其中有一个 broker 会被选举为控制器,它负责管理整个集群中所有分区和副本的状态。分区的 leader 出现故障时,由控制器负责为其选举新的 leader;当某个分区的 ISR 发生变化时,由控制器负责通知所有 broker 更新其元数据信息;当某个 topic 的分区数量发生变化时,还是由控制器负责分区的重新分配。因此,只要控制器正常工作,分区的 leader 就是唯一的,不会有脑裂问题。

那么 Kafka 是如何保证控制器只有一个的呢?如果控制器发生异常了怎么办?控制器的选举和异常恢复又是怎样的?

控制器

控制器是 broker 维度的角色,它负责管理整个集群中所有分区和副本的状态。

Kafka 中的控制器选举工作依赖与 ZooKeeper,成功竞选为控制器的 broker 会在 ZooKeeper 中创建 /controller 临时节点,节点会存储以下信息:

{
    "version ": 1,
    "brokerid": 0,
    "timestamp": "1529210278988"
}

其中 version 目前是固定值不用管,brokerid 是成为控制器的 broker 的 id,timestamp 是 broker.id=0 的 broker 成为控制器的时间戳。

在任意时刻,Kafka 集群中有且仅有一个控制器。每个 broker 启动时会尝试读取 ZooKeeper 的 /controller 节点的 brokerid 的值,如果 brokerid ≠ -1,则表示当前集群已有控制器,broker 就会放弃竞选;如果不存在 /controller 节点,broker 就会尝试创建节点,创建成功的 broker 就会成为控制器,将自己的 ID 赋予 brokerid,而对于创建节点失败的 broker 则会在内存中保存当前控制器的 brokerid 值,这个值标识为 activeControllerId。

上面是启动 Kafka 集群以及正常情况下添加 broker 情况下的选举过程。那么当控制器出现故障时,就需要重新选举了。ZooKeeper 中还有一个与控制器有关的 /controller_epoch 节点,该节点是持久节点,里面存储了一个整型的 controller_epoch 值,初始值是 1。当控制器发生变化时,controller_epoch 就会加 1。每个和控制器交互的请求一定会带上 controller_epoch,当控制器发现请求带上的 controller_epoch 比自己内存的小,那么这个请求则是无效请求;如果请求带上的 controller_epoch 比自己内存的大,说明自己不再是控制器。由此可见,Kafka 是通过 controller_epoch 来保证控制器的唯一性,进而保证相关操作的一致性

这里再扩展以下,说下作为控制器的 broker 多出来的责任:

  • 监听分区相关的变化

    • 在 ZooKeeper 的 /admin/reassign_partitions 节点注册 PartitionReassignmentHandler,用来处理分区重分配的动作。
    • 在 ZooKeeper 的 /isr_change_notification 节点注册 IsrChangeNotificetionHandler,用来处理 ISR 集合变更的动作。
    • 在 ZooKeeper 的 /admin/preferred-replica-election 节点注册 PreferredReplicaElectionHandler,用来处理优先副本的选举举动。
  • 监听主题相关的变化

    • 在 ZooKeeper 的 /brokers/topics 节点注册 TopicChangeHandler,用来处理主题增减的变化。
    • 在 ZooKeeper 的 /admin/delete_topics 节点注册 TopicDeletionHandler,用来处理删除主题的动作。
  • 监听 broker 相关的变化

    在 ZooKeeper 的 /brokers/ids 节点注册 BrokerChangeHandler,用来处理 broker 增减的变化。

  • 从 ZooKeeper 中读取当前所有与主题、分区及 broker 有关的信息并进行相应的管理

    对所有主题对应的在 ZooKeeper 中的 /brokers/topics/<topic> 节点添加 PartitionModificationsHandler,用来监听主题中的分区分配变化。

  • 启动并管理分区状态机和副本状态机

  • 更新集群的元数据信息

  • 如果设置了 auto.leader.rebalance.enable = true,则还会开启一个名为“auto-leader-rebalance-task”的定时任务来负责维护分区的优先副本的均衡

成功竞选控制器的 broker 会在当选后读取包括不限于上面提到的在 ZooKeeper 中的节点的数据,初始化上下文信息,并且进行管理。在 Kafka 中,因为会有大量需要读取或者更新上下文信息的操作,所以这里会有多线程问题。如果单纯采用锁机制实现,那么整体性能会大打折扣。因此,控制器采用的是单线程基于事件队列的模型。将所有相关的操作、事件进一步封装成一个个事件,按照事件发生的顺序存入 LinkedBlockingQueue 中,最后再使用一个专用线程按 FIFO 的原则处理各个事件。

控制器、非控制器 broker、ZooKeeper 关系图如下


深入了解Kafka可靠性(三),第1张
控制器.png

只有控制器会注册相应的监听器关注节点的数据变化,其他 broker 则不关注这些节点的数据变化(除了 /controller)。因为所有 broker 都会关心当前的控制器到底是谁,当 /controller 的数据发生变化时,就要更新自己内存中的 activeControllerId。如果原来是控制器的 broker,发现自己现在不是了,就需要关闭资源,如注销只有控制器才需要的监听器等。不管什么原因造成 /controller 的 brokerid 变更,再重新选举控制器之前,要先确定参选的 broker 里面是否有前控制器,如果有,就要先“退位”,再开始新的选举。

优点:只有控制器注册监听器,可以有效避免严重依赖 ZooKeeper 的设计的通病——脑裂、羊群效应、ZooKeeper 过载。


https://www.xamrdz.com/backend/3mc1941233.html

相关文章: