目录
[1. Kafka可靠性概述](#1. Kafka可靠性概述)
[2. 副本剖析](#2. 副本剖析)
[2.1 什么是副本](#2.1 什么是副本)
[2.2 副本失效场景](#2.2 副本失效场景)
[2.3 数据丢失场景](#2.3 数据丢失场景)
[2.4 解决数据丢失方案](#2.4 解决数据丢失方案)
[3. 日志同步机制](#3. 日志同步机制)
[4. 可靠性分析](#4. 可靠性分析)
1. Kafka可靠性概述
Kafka 中采用了多副本的机制,这是大多数分布式系统中惯用的手法,以此来实现水平扩展、提供容灾能力、提升可用性和可靠性等。
2. 副本剖析
2.1 什么是副本
副本(Replica)是分布式系统中常见的概念之一,指的是分布式系统对数据和服务提供的一种冗余方式。
2.2 副本失效场景
a. follower副本进程卡住,在一段时间内根本没有向leader副本发起同步请求,比如频繁的Full GC。
b. follower副本进程同步过慢,在一段时间内都无法追赶上leader副本,比如I/O开销过大。
c. 如果通过工具增加了副本因子,那么新增加的副本在赶上leader副本之前也都是处于失效状态的。
2.3 数据丢失场景
在某一时刻,B中有2条消息m1和m2,A从B中同步了这两条消息,此时A和B的LEO都为2,同时HW都为1;之后A再向B中发送请求以拉取消息,FetchRequest请求中带上了A的LEO信息,B在收到请求之后更新了自己的HW为2;B中虽然没有更多的消息,但还是在延时一段时间之后返回FetchResponse,并在其中包含了HW信息;最后A根据FetchResponse中的HW信息更新自己的HW为2。
数据丢失场景(part 1)
可以看到整个过程中两者之间的HW同步有一个间隙,在A写入消息m2之后(LEO更新为2)需要再一轮的FetchRequest/FetchResponse才能更新自身的HW为2。如果在这个时候A宕机了,那么在A重启之后会根据之前HW位置(这个值会存入本地的复制点文件replication-offset-checkpoint)进行日志截断,这样便会将m2这条消息删除,此时A只剩下m1这一条消息,之后A再向B发送FetchRequest请求拉取消息。
数据丢失场景(part 2)
此时若B 再宕机,那么 A 就会被选举为新的leader,B 恢复之后会成为follower,由于follower副本HW不能比leader副本的HW高,所以还会做一次日志截断,以此将HW调整为1。这样一来m2这条消息就丢失了(就算B不能恢复,这条消息也同样丢失)。
数据丢失场景(part 3)
对于这种情况,也有一些解决方法,比如等待所有follower副本都更新完自身的HW之后再更新leader副本的HW,这样会增加多一轮的FetchRequest/FetchResponse延迟,自然不够妥当。还有一种方法就是follower副本恢复之后,在收到leader副本的FetchResponse前不要截断follower副本(follower副本恢复之后会做两件事情:截断自身和向leader发送FetchRequest请求),不过这样也避免不了数据不一致的问题。
当前leader副本为A,follower副本为B,A中有2条消息m1和m2,并且HW和LEO都为2,B中有1条消息m1,并且HW和LEO都为1。假设A和B同时"挂掉",然后B第一个恢复过来并成为leader。
数据不一致场景(part 1)
数据不一致场景(part 2)
之后B写入消息m3,并将LEO和HW更新至2(假设所有场景中的min.insync.replicas参数配置为1)。此时A也恢复过来了,根据前面数据丢失场景中的介绍可知它会被赋予follower的角色,并且需要根据HW截断日志及发送FetchRequest至B,不过此时A的HW正好也为2,那么就可以不做任何调整了。
数据不一致场景(part 3)
如此一来A中保留了m2而B中没有,B中新增了m3而A也同步不到,这样A和B就出现了数据不一致的情形。
2.4 解决数据丢失方案
为了解决数据丢失问题,Kafka从0.11.0.0开始引入了leader epoch的概念,在需要截断数据的时候使用leader epoch作为参考依据而不是原本的HW。leader epoch代表leader的纪元信息(epoch),初始值为0。每当leader变更一次,leader epoch的值就会加1,相当于为leader增设了一个版本号。
A在收到2之后发现和目前的LEO相同,也就不需要截断日志了。之后B发生了宕机,A成为新的leader,那么对应的LE=0也变成了LE=1,对应的消息m2此时就得到了保留,之后不管B有没有恢复,后续的消息都可以以LE1为LeaderEpoch陆续追加到A中。
3. 日志同步机制
在Kafka中动态维护着一个ISR集合,处于ISR集合内的节点保持与leader相同的高水位(HW),只有位列其中的副本(unclean.leader.election.enable配置为false)才有资格被选为新的 leader。写入消息时只有等到所有 ISR 集合中的副本都确认收到之后才能被认为已经提交。位于 ISR 中的任何副本节点都有资格成为 leader,选举过程简单、开销低,这也是Kafka选用此模型的重要因素。Kafka中包含大量的分区,leader副本的均衡保障了整体负载的均衡,所以这一因素也极大地影响Kafka的性能指标。
在采用ISR模型和(f+1)个副本数的配置下,一个Kafka分区能够容忍最大f个节点失败,相比于"少数服从多数"的方式所需的节点数大幅减少。
4. 可靠性分析
生产者客户端参数 acks,相比于0和1,acks=-1(客户端还可以配置为all,它的含义与-1一样,以下只以-1来进行陈述)可以最大程度地提高消息的可靠性。
对于acks=1的配置,生产者将消息发送到leader副本,leader副本在成功写入本地日志之后会告知生产者已经成功提交,如图8-24所示。如果此时ISR集合的follower副本还没来得及拉取到leader中新写入的消息,leader就宕机了,那么此次发送的消息就会丢失。
ack=-1的配置,生产者将消息发送到leader副本,leader副本在成功写入本地日志之后还要等待 ISR 中的 follower 副本全部同步完成才能够告知生产者已经成功提交,即使此时leader副本宕机,消息也不会丢失,如果在消息成功写入leader副本之后,并且在被ISR中的所有副本同步之前leader副本宕机了,那么生产者会收到异常以此告知此次发送失败。