Kafka从0.8.x版本开始引入副本机制,这样可以极大的提高集群的可靠性和稳定性。不过这也使得Kafka变得更加复杂起来,失效副本就是所要面临的一个难题。Kafka中的每个分区(partition)都会分配多个副本(replica),具体的副本数量由Broker级别参数default.replication.factor(默认大小为1)指定,也可以在创建topic的时候通过 --replication-factor ${num} 显式指定副本的数量(副本因子)。一般情况下,将前者default.replication.factor设置为大于1的值,这样在参数auto.create.topic.enable为true的时候,自动创建的topic会根据default.replication.factor的值来创建副本数;或者更加通用的做法是使用后者而指定大于1的副本数。
每个分区的多个副本称之为AR(assigned replicas),包含至多一个leader副本和多个follower副本。与AR对应的另一个重要的概念就是ISR(in-sync replicas),ISR是指与leader副本保持同步状态的副本集合,当然leader副本本身也是这个集合中的一员。而ISR之外,也就是处于同步失败或失效状态的副本,副本对应的分区也就称之为同步失效分区,即under-replicated分区。
1、失效副本的判定
从Kafka 0.9.x版本开始通过唯一的一个参数replica.lag.time.max.ms(默认大小为10,000)来控制,当ISR中的一个follower副本滞后leader副本的时间超过参数replica.lag.time.max.ms指定的值时即判定为副本失效,需要将此follower副本剔出除ISR之外。
实现原理:
当follower副本将leader副本的LEO(Log End Offset,每个分区最后一条消息的位置)之前的日志全部同步时,则认为该follower副本已经追赶上leader副本,此时更新该副本的lastCaughtUpTimeMs标识。
Kafka的副本管理器(ReplicaManager)启动时会启动一个副本过期检测的定时任务,而这个定时任务会定时检查当前时间与副本的lastCaughtUpTimeMs差值是否大于参数replica.lag.time.max.ms指定的值。
注意:
不要错误的认为follower副本只要拉取leader副本的数据就会更新lastCaughtUpTimeMs,试想当leader副本的消息流入速度大于follower副本的拉取速度时,follower副本一直不断的拉取leader副本的消息也不能与leader副本同步,如果还将此follower副本置于ISR中,那么当leader副本失效,而选取此follower副本为新的leader副本,那么就会有严重的消息丢失。
Kafka源码注释中说明了一般有两种情况会导致副本失效:
(1)follower副本进程卡住,在一段时间内根本没有向leader副本发起同步请求,比如频繁的Full GC。
(2)follower副本进程同步过慢,在一段时间内都无法追赶上leader副本,比如IO开销过大。
如果通过工具增加了副本因子,那么新增加的副本在赶上leader副本之前也都是处于失效状态的。如果一个follower副本由于某些原因(比如宕机)而下线,之后又上线,在追赶上leader副本之前也是出于失效状态。
在Kafka 0.9.x版本之前还有另一个Broker级别的参数replica.lag.max.messages(默认大小为4000)也是用来判定失效副本的,当一个follower副本滞后leader副本的消息数超过replica.lag.max.messages的大小时则判定此follower副本为失效副本。它与replica.lag.time.max.ms参数判定出的失败副本去并集组成一个失效副本的集合,从而进一步剥离出ISR。
不过这个replica.lag.max.messages参数很难给定一个合适的值,若设置的太大则这个参数本身就没有太多意义,若设置的太小则会让follower副本反复的处于同步、未同步、同步的死循环中,进而又会造成ISR的频繁变动。而且这个参数是Broker级别的,也就是说对Broker中的所有topic都生效,就以默认的值4000来说,对于消息流入速度很低的topic来说,比如TPS=10,这个参数并无用武之地;而对于消息流入速度很高的topic来说,比如TPS=20,000,这个参数的取值又会引入ISR的频繁变动,所以从0.9.x版本开始就彻底移除了这一参数
具有失效副本的分区可以从侧面洞悉出Kafka集群的很多问题,毫不夸张的说:如果只能用一个指标来衡量Kafka,那么失效副本分区的个数必然是首选。Kafka本身也提供了一个相关的指标,即UnderReplicatedPartitions,这个可以通过JMX访问:
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions
来获取其值,取值范围是大于等于0的整数。如果获取的UnderReplicatedPartitions值大于0,就需要对其进行告警,并进一步诊断其背后的真正原因,有可能是某个Broker的问题,也有可能引申到整个集群的问题,也许还要引入其他一些信息、指标等配合找出问题之所在。注意:如果Kafka集群正在做分区迁移(kafka-reassign-partitions.sh)的时候,这个值也会大于0。
2、优先副本的选举
所谓的优先副本是指在Kafka的AR列表中的第一个副本。理想情况下,优先副本就是该分区的leader副本,所以也可以称之为preferred leader。Kafka要确保所有主题的优先副本在Kafka集群中均匀分布,这样就保证了所有分区的Leader均衡分布。保证Leader在集群中均衡分布很重要,因为所有的读写请求都由分区leader副本进行处理,如果leader分布过于集中,就会造成集群负载不均衡。试想一下,如果某分区的leader副本在某个很空闲的Broker上,而它的follower副本宿主于另一个很繁忙的Broker上,那么此follower副本很可能由于分配不到足够的系统资源而无法完成副本同步的任务,进而造成副本失效。
所谓的优先副本的选举是指通过自动或者手动的方式促使优先副本选举为leader,也就是分区平衡,这样可以促进集群的均衡负载,也就进一步的降低失效副本生存的几率。需要注意的是分区平衡并不意味着Kafka集群的负载均衡,因为这还要考虑到集群中的分区分配是否均衡。更进一步每个分区的leader的负载也是各不相同,有些leader副本的负载很高,比如需要承受TPS为3W的负荷,而有些leader副本只需承载个位数的负荷,也就是说就算集群中的分区分配均衡,leader分配也均衡也并不能确保整个集群的负载就是均衡的,还需要其他一些硬性的指标来做进一步的衡量
随着集群运行时间的推移,可能部分节点的变化导致leader进行了重新选举,若优先副本的宿主Broker在发生故障后由其他副本代替而担任了新的leader,就算优先副本的宿主Broker故障恢复而重新回到集群时若没有自动平衡的功能,该副本也不会成为分区的leader。Kafka具备分区自动平衡的功能,且默认情况下此功能是开启的,与此对应的参数是
auto.leader.rebalance.enable=true。如果开启分区自动平衡,则Kafka的Controller会创建一个分区重分配检查及分区重分配操作(onPartitionReassignment)的定时任务,这个定时任务会轮询所有的Broker,计算每个Broker的分区不平衡率(Broker中的不平衡率=非优先副本的leader个数 / 分区总数)是否超过leader.imbalance.per.broker.percentage配置的比率,默认是10%,如果超过设定的比率则会自动执行优先副本的选举动作以求分区平衡。默认执行周期是leader.imbalance.check.interval.seconds=300,即5分钟。
不过在生产环境中不建议将auto.leader.rebalance.enable设置为默认的true,因为这可能会引起负面的性能问题,也有可能会引起客户端一定时间的阻塞。因为执行的时间无法自主掌控,如果在关键时期(比如电商大促波峰期)执行关键任务的关卡摆上一道优先副本的自动选举操作,势必会有业务阻塞、频繁超时之类的风险。前面也分析过分区的均衡也不能确保集群的均衡,而集群一定程度上的不均衡也是可以忍受的,为防关键时期掉链子的行为,建议还是把这类的掌控权把控在自己的手中,可以针对此类相关的埋点指标设置相应的告警,在合适的时机执行合适的操作。
优先副本的选举是一个安全的(Kafka客户端可以自动感知分区leader的变更)并且也容易执行的一类操作。执行优先副本的选举是通过$KAFKA_HOME/bin/路径下的kafka-preferred-replica-election.sh脚本来实现的。举例某Kafka集群有3个Broker,编号(broker.id)为[0,1,2],且创建了名称为“topic-1”、副本数为3, 分区数为9的一个topic,细节如下(注意其中的IP地址是虚构的):
[root@zzh kafka_1.0.0]# bin/kafka-topics.sh --describe --zookeeper 192.168.0.2:2181,192.168.0.3:2181,192.168.0.3:2181/kafka --topic topic-1
Topic:topic-1 PartitionCount:9 ReplicationFactor:3 Configs:
Topic: topic-1 Partition: 0 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1
Topic: topic-1 Partition: 1 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: topic-1 Partition: 2 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
Topic: topic-1 Partition: 3 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0
Topic: topic-1 Partition: 4 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1
Topic: topic-1 Partition: 5 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: topic-1 Partition: 6 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1
Topic: topic-1 Partition: 7 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: topic-1 Partition: 8 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
可以看到初始情况下,所有的leader都是AR中的第一个副本也就是优先副本。此时关闭再开启broker.id=2那台Broker,就可以使得topic-1中存在非优先副本的leader,细节如下:
Topic:topic-1 PartitionCount:9 ReplicationFactor:3 Configs:
Topic: topic-1 Partition: 0 Leader: 0 Replicas: 2,0,1 Isr: 0,1,2
Topic: topic-1 Partition: 1 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: topic-1 Partition: 2 Leader: 1 Replicas: 1,2,0 Isr: 1,0,2
Topic: topic-1 Partition: 3 Leader: 1 Replicas: 2,1,0 Isr: 1,0,2
Topic: topic-1 Partition: 4 Leader: 0 Replicas: 0,2,1 Isr: 0,1,2
Topic: topic-1 Partition: 5 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: topic-1 Partition: 6 Leader: 0 Replicas: 2,0,1 Isr: 0,1,2
Topic: topic-1 Partition: 7 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: topic-1 Partition: 8 Leader: 1 Replicas: 1,2,0 Isr: 1,0,2
此时可以执行对应的kafka-preferred-replica-election.sh脚本来进行优先副本的选举操作,相关细节如下:
[root@zzh kafka_1.0.0]# bin/kafka-preferred-replica-election.sh --zookeeper 192.168.0.2:2181,192.168.0.3:2181,192.168.0.3:2181/kafka
Created preferred replica election path with {"version":1,"partitions":[{"topic":"topic-1","partition":6},{"topic":"topic-1","partition":0},{"topic":"topic-1","partition":7},{"topic":"topic-1","partition":3},{"topic":"topic-1","partition":8},{"topic":"topic-1","partition":2},{"topic":"topic-1","partition":5},{"topic":"topic-1","partition":4},{"topic":"topic-1","partition":1}]}
Successfully started preferred replica election for partitions Set([topic-1,6]], [topic-1,5], [topic-1,4], [topic-1,3], [topic-1,2], [topic-1,7], [topic-1,1], [topic-1,8], [topic-1,0])
最终的leader分配又回到初始情况下的状态。不过上面的执行方法是针对Kafka集群中的所有topic都执行一次优先副本的选举,如果集群中存有大量的分区,这一操作有可能会失效,因为这个请求的内容会写入到Zookeeper的节点之中,如果这个请求的内容体过大而超过节点所能存储的数据(默认为1MB)时请求会失败。Kafka提供了更细粒度的优先副本的选举操作,它可以细化到某个topic的某个分区这个层级,这样在面对一次请求过大的问题时可以选择性的进行细粒度拆分,也可以在实现自定义的个性化优先副本的选举操作。
在实现细粒度的优先副本的选举操作之前,首先要建立一个JSON文件,将所需要的topic以及对应的分区编号记录于其中,比如针对topic-1的编号为0的分区进行优先副本的选举操作,对应的JSON文件内容如下(假设此文件命名为partitions.json):
{
"partitions":[
{
"partition":0,
"topic":"topic-1"
}
]
}
之后再执行kafka-preferred-replica-election.sh脚本时通过–path-to-json-file参数来指定此
JSON文件,相关细节如下:
[root@zzh kafka_2.12-0.10.2.1]# bin/kafka-preferred-replica-election.sh --zookeeper 192.168.0.2:2181,192.168.0.3:2181,192.168.0.3:2181/kafka --path-to-json-file partitions.json
Created preferred replica election path with {"version":1,"partitions":[{"topic":"topic-1","partition":0}]}
Successfully started preferred replica election for partitions Set([topic-1,0])
参考博客:
https://blog.youkuaiyun.com/u013256816/article/details/78851989