Kafka可靠性
本文将从 Partition 负载均衡、Broker 控制器、Partition Leader 选举、Consumer group 重平衡四个方面来分析 Kafka 的可靠性。
一、Load Balancing
Kafka 允许 Topic 的 Partition 拥有若干副本,为避免大量 Topic 的 Partition 集中在其中少数几个节点上,造成性能、吞吐量降低等负面作用,Kafka 采用分区策略来实现 Partition 的负载均衡。
partitioner.class
一个类,用于确定在生成消息时要发送到哪个分区。包括:
(1) DefaultPartitioner 默认分区
-如果未指定分区但存在键,根据键的哈希值选择分区
-如果不存在分区或键,使用粘性分区 StickyPartition
粘性分区简而言之,基于随机选择一个分区 (若只剩一个可用分区则无需选择),先将这个分区 batch.size 填满发送消息之后,再选择另一个分区进行消息的填充和发送。
推荐阅读 Kafka生产者3种分区分配策略-云社区-华为云 (huaweicloud.com)
(2) UniformStickyPartitioner 粘性分区:所有情况都使用粘性分区
(3) RoundRobinPartitioner 轮询分区 (顺序)
(4) 自定义分区
接口 org.apache.kafka.clients.producer.Partitioner 可用于实现自定义分区策略
type: class
default: null
valid values:
importance: medium
代码实现只需进行配置即可:
// properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class);
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, UniformStickyPartitioner.class);
自定义分区:
public class CustomPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, CustomPartitioner.class);
二、Broker Controller
进入正题之前,先了解下 Zookeeper,它是个分布式协调服务。Zookeeper ≈ 数据存储节点 + 监听机制
数据存储节点,即 Znode 分为:
短暂/临时 (Ephemeral):当客户端和服务端断开连接后,所创建的 Znode (节点)会自动删除。包括临时目录节点、临时顺序编号目录节点。
持久 (Persistent):当客户端和服务端断开连接后,所创建的 Znode (节点)不会删除。包括持久化目录节点、持久化顺序编号目录节点。
当目录节点 Znode 发生变化 (数据改变、被删除、子目录节点增删) 时,Zookeeper 会通知客户端。更多:什么是ZooKeeper? - 知乎 (zhihu.com)
Controller 控制器的主要作用是在 Zookeeper 的帮助下管理和协调 Kafka 集群。集群中任一 Broker 都能充当控制器的角色,但在运行过程中,只有一个 Broker Controller。
Broker 在启动时,会尝试去 Zookeeper 中创建 /controller临时节点。第一个成功创建 /controller节点的 Broker 会被指定为控制器。当前 Broker Controller 失效时,选举新的 Controller 也是依据创建 /controller节点的速度。
控制器的作用
(1) 创建、删除主题,增加分区并分配 leader 分区
(2) 集群 Broker 管理 (新增 Broker、Broker 主动关闭、Broker 故障)
(3) preferred leader 选举
(4) 分区重分配
(5) 元数据管理
当一个 broker 停止或崩溃时,该 broker 上的分区的 leader 会转移到其他副本。这意味着在 broker 重新启动时,默认情况下,它将只是所有分区的跟随者,不会用于客户端的读取和写入。
为了避免这种不平衡,Kafka 有一个首选副本 preferred leader 的概念。如果分区的副本列表为 [3, 1, 5],则节点 3 为首选leader,因为它在副本列表中位于前面。
默认情况下,Kafka 集群将尝试将 leader 恢复到 preferred leader。此 Broker 配置如下:
auto.leader.rebalance.enable
启用自动 leader 平衡。后台线程定期检查分区 leader 的分布,可通过 “leader.imbalance.check.interval.seconds” 进行定时配置。如果 leader 不平衡超过 “leader.imbalance.per.broker.percent”,则会触发 leader 重新平衡到分区的 preferred leader。
type: boolean
default: true
valid values:
importance: high
update mode: read-only
脑裂
如果控制器停止或崩溃,Kafka 会选举出新的控制器。但如果之前被取代的控制器又恢复正常了,它依旧认为自己是控制器,这样集群就会出现两个控制器,这就是脑裂。
解决方法:
为了解决 Controller 脑裂问题,Zookeeper 中还有一个与 Controller 有关的持久节点 /controller_epoch,存放的是一个整形值的 epoch number (纪元编号,也称为隔离令牌),集群中每选举一次控制器,就会通过 Zookeeper 创建一个数值更大的 epoch number,如果有 broker 收到比这个 epoch 数值小的数据,就会忽略消息。
今天想和你聊聊Kafka的Controller(控制器) - 腾讯云开发者社区-腾讯云 (tencent.com)
三、Leader Election
Kafka 动态维护了一个同步状态的备份的集合 (a set of in-sync replicas),简称 ISR,在这个集合中的节点都是和 leader 保持高度一致的,只有这个集合的成员才有资格被 Partition 选举为 Leader。
Leader 会追踪所有 “in sync” 的节点。Kafka 判断节点是否 “in sync” 的两个条件:
(1) 节点必须维护和 Broker Controller 的连接,以便定期接受元数据更新。
(2) 如果节点是个 follower,它必须能及时的同步 leader 的写操作,并且不能延时太久。
如果有节点挂掉、写超时或心跳超时,leader 就会把它从同步副本列表中移除。同步超时和写超时由 replica.lag.time.max.ms 配置确定。
replica.lag.time.max.ms
在此规定时间内,如果 follower 没有向 leader 发送任何获取请求,或者没有同步 leader 的 end offset,则 leader 将从 ISR 中删除该 follower。
type: long
default: 30000 (30 seconds)
valid values:
importance: high
update mode: read-only
选举 leader 通常选择满足条件在线的 ISR的第一个副本。若备份节点全部挂掉,那么有两种考虑:
(1) 等待一个 ISR 的副本重新恢复正常服务,并选择这个副本作为 leader (它有极大可能拥有全部数据)。
(2) 选择第一个重新恢复正常服务的副本 (不一定是 ISR 中的) 作为 leader。
这是可用性和一致性之间的权衡,如果侧重可用性那么策略二服务恢复无疑更快,虽然数据可能丢失。
如果侧重一致性,那么策略一肯定数据丢失的概率更小,但同时服务可能会一直不可用。
Kafka 默认禁用 unclean leader 选举,即使用策略一,一致性优先于可用性。可以配置属性 unclean.leader.election.enable 来更改策略。
unclean.leader.election.enable
在不得已情况下,是否允许不在ISR中的副本可被选为领导者,即使可能会导致数据丢失。
type: boolean
default: false
valid values:
importance: medium
前序系列文章描述 acks = -1/all 时,需要所有备份都保存成功返回,这里更精准的描述是需要所有 ISR 备份成功返回。但如果 ISR 副本数过少,达不到数据一致性的效果。Kafka 提供了 topic 配置,可指定最小 ISR 数量。
min.insync.replicas
当生产者将 acks 设置为 “-1/all” 时,此配置指定了必须确认写入才能认为写入成功的最小副本数。若不满足,则生产者将引发异常 (NotEnoughReplicas 或 NotEnoughtReplicasAfterAppend)。
一个典例是设置复制因子为 3,ISR 为 2,acks 为 all,这样能较好地保证数据一致性。
type: int
default: 1
valid values: [1, ...]
importance: medium
选举详细流程及源码相关推荐阅读:你想知道的所有关于Kafka Leader选举流程和选举策略都在这(内含12张高清图) - 腾讯云开发者社区-腾讯云 (tencent.com)
四、Consumer group Rebalance
这三种情况会发生 Rebalance:消费组成员个数发生变化;订阅的 Topic 个数发生变化;订阅 Topic 的分区数发生变化。
试想做一个项目,突然多了个人进来,这时候就可能按能力分配任务,或者从编码到写文档都分配一点。消费者组重平衡也是如此,需要考虑到分配的策略。
partition.assignment.strategy
分区分配策略,用来管理消费者组的分区分配。
org.apache.kafka.clients.sumer.RangeAssignator:按 topic 分配分区。
org.apache.kafka.clients.sumer.RoundRobinAssignator:以循环方式将分区分配给消费者。
org.apache.kafka.clients.sumer.StickyAssignator:保证分配最大限度地平衡,同时尽量不更改现有分区分配。
org.apache.kafka.clients.consumer.CooperativeStickyAssignor:遵循 StickyAssignor 的逻辑,但将全局重平衡改为数次小规模重平衡,直至最终平衡。
默认的赋值器是 [RangeAssignor, CooperativeStickyAssignor],默认情况下,它将使用 RangeAssignor,但可以升级到 Cooperative StickyAssignor,从列表中删除 RangeAssignor 即可。
实现 org.apache.kafka.clients.consumer.ConsumerPartitionAssignor 接口允许自定义分配策略。
type: list
default: class org.apache.kafka.clients.consumer.RangeAssignor,
class org.apache.kafka.clients.consumer.CooperativeStickyAssignor
valid values: non-null string
importance: medium
1. Range
该策略定义 n = 分区数 / 消费者数量,m = 分区数 % 消费者数量,前 m 个消费者每个分配 n + 1 个分区,后面的 (消费者数量 - m 个) 消费者每个分配 n 个分区。注:分区数特指单个 Topic 有的分区数,而非所有分区
RangeAssignor (kafka 3.3.1 API) (apache.org)
设消费组内有 3 个消费者 C0、C1 和 C2,都订阅了主题 T0 和 T1,并且每个主题都有 3 个分区 P0、P1、P2。则共有 6 个分区 T0P0, T0P1, T0P2. T1P0, T1P1, T1P2
分配结果:
C0:T0P0, T1P0
C1:T0P1, T1P1
C2:T0P2, T1P2
分区数等于消费者数量时,Range 策略能很好平衡分配结果。
若此时新增一个分区 P3,则共有 8 个分区 T0P0, T0P1, T0P2, T0P3. T1P0, T1P1, T1P2, T1P3
分配结果:
C0:T0P0, T0P1, T1P0, T1P1
C1:T0P2, T1P2
C2:T0P3, T1P3
分区数不等于消费者数量时,Range 策略会造成部分消费者分区分配过多。
2. RoundRobin和Sticky
RoundRobinAssignor (kafka 3.3.1 API) (apache.org)
StickyAssignor (kafka 3.3.1 API) (apache.org)
(1) 设消费组内有 3 个消费者 C0、C1 和 C2,都订阅了 4 个主题 T0、T1、T2、T3,并且每个主题都有 2 个分区 P0、P1。则共有 8 个分区 T0P0, T0P1. T1P0, T1P1. T2P0, T2P1. T3P0, T3P1
Range:
C0:T0P0, T1P0, T2P0, T3P0
C1:T0P1, T1P1, T2P1, T3P1
C2:
此时 RoundRobin 和 Sticky 分配结果是相同的:
C0:T0P0, T1P1, T3P0
C1:T0P1, T2P0, T3P1
C2:T1P0, T2P1
但如果消费者 C1 脱离了消费组,那么 RoundRobin 会重新轮询,而 Sticky 会发挥特性“尽量不更改现有分区分配”。
RoundRobin:
C0:T0P0, T1P0, T2P0, T3P0
C2:T0P1, T1P1, T2P1, T3P1
Sticky:
C0:T0P0, T1P1, T3P0, T2P0
C2:T1P0, T2P1, T0P1, T3P1
红色分区为相比之前新增的分区,可以看出 Sticky 是在原有分区上添加了 C1 的分区,比 RoundRobin 策略更改的分区更少。使用 Sticky 策略对消费者组重平衡更友好。
(2) 设消费组内有 3 个消费者 C0、C1 和 C2,它们共订阅了 3 个主题 T0、T1、T2,但是 T0 只有分区 P0,T1 有分区 P0、P1,T2 有分区 P0、P1、P2,此时有 6 个分区,T0P0. T1P0, T1P1. T2P0, T2P1, T2P2
其中,消费者 C0 只订阅了主题 T0,消费者 C1 订阅了主题 T0 和 T1,消费者 C2 订阅了全部主题 T0、T1和 T2
Range 和 RoundRobin:
C0:T0P0
C1:T1P0
C2:T1P1, T2P0, T2P1, T2P2
Sticky:
C0:T0P0
C1:T1P0, T1P1
C2:T2P0, T2P1, T2P2
在分区数量不等、消费者组内消费者订阅主题不等的情况下,Sticky 策略依旧表现更好。
若此时消费者 C0 脱离了消费组,重平衡结果为:
Range和RoundRobin:
C1:T0P0, T1P0
C2:T1P1, T2P0, T2P1, T2P2
Sticky:
C1:T1P0, T1P1, T0P0
C2:T2P0, T2P1, T2P2
重平衡过程中,消费者无法从 Kafka 消费消息。若 Kafka 有大量节点,那么重平衡可能耗时很久,这段时间内 Kafka 基本处于不可用状态。所以在实际环境中,应该尽量避免重平衡发生。
针对上文提过的 Rebalance 的发生情况,其中 Kafka 误认为消费者挂掉而导致重平衡是不被期待的。比如消费者负载过重或网络堵塞导致发送心跳超时,Kafka 有以下 Consumer Configs 控制误判概率:
session.timeout.ms
用于检测客户端是否超时。客户端定期发送心跳信号,如果在会话超时到期之前 broker 没有收到心跳,那么 broker 将从 group 中删除此客户端并进行重平衡。注意,该值必须在 broker configuration 的 group.min.session.timeout.ms 和 group.max.session.timeout.ms 允许范围之内。
type: int
default: 45000 (45 seconds)
valid values:
importance: high
heartbeat.interval.ms
消费者发送心跳的间隔时间。该值必须低于 session.timeout.ms,通常设置为不高于 session.timeout.ms 的 1/3。间隔时间越短,越容易判断此时该消费者保持活跃,但也会消耗更多资源。
type: int
default: 3000 (3 seconds)
valid values:
importance: high
max.poll.interval.ms
poll() 调用最大延迟时间,如果到期之前未调用 poll(),则认为消费者挂掉,消费者组将进行重平衡。但如果消费者已配置非空 group.instance.id,会被视为静态成员。静态成员挂掉后不会立即重新重平衡,而是先停止发送心跳,并在 session.timeout.ms 过期后再进行重平衡。
type: int
default: 300000 (5 minutes)
valid values: [1, ...]
importance: medium
看到这儿,如果这个系列看了七七八八的话,那再去看官方文档除了英语,其他应该是没什么大问题了 ➡ Apache Kafka,再多的就是 Kafka 实际环境的应用和配置....
参考:
一文理解Kafka的选举机制与Rebalance机制 - 腾讯云开发者社区-腾讯云 (tencent.com)
Preferred Leader设置_L13763338360的博客-CSDN博客
Kafka分区分配策略(1)——RangeAssignor_朱小厮的博客-CSDN博客_rangeassignor
Kafka2.4源码阅读——消费者客户端(PartitionAssignor) - 掘金 (juejin.cn)
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情