可靠性研究
# 一、副本剖析
kafka采用了多副本的机制,以此来实现水平扩展、提供容灾能力,而副本是在不同的节点上持久化同一份数据,当某一个节点上存储的数据丢失时,可以从副本上读取该数据。
# 1、失效副本
正常情况下,分区的所有副本都处于ISR集合下,即AR=ISR,在ISR集合之外,处于同步失效或功能失效的副本统称为失效副本。
如何判断为失效副本
kafka的副本管理器会启动一个副本过期检测的定时任务,而这个定时任务会定期检查当前时间与副本的lastCaughtUpTimeMs差值是否大于指定参数
为什么副本不自己更新
假设现在leader副本中消息的流入速度大于follower副本中拉取的速度时,相当于follower永远也赶不上leader副本
副本失效的情况
- follower副本进程卡住,在一段时间内根本没有向leader副本发出同步请求
- follower副本进程同步过慢,一段时间都无法赶上leader副本
注意:
- 通过副本因子的方式来增加的副本在赶上leader副本之前也是失效副本。
- 一个follower副本由于某些原因宕机下线之后在上线,在赶上leader副本之前也是失效副本。
# 2、ISR的伸缩
kafka启动的时候会开启和ISR相关的定时任务(isr-expiration
和isr-change-propagation
)
isr-expiration
会周期性检测每个分区是否需要缩减ISR集合,当检测到ISR集合中存在失效副本时,就会收缩ISR集合,当ISR集合发生变更后,会将变更后的记录缓存到isrChangeSet
isr-change-propagation
会周期性检查isrChangeSet,如果发现isrChangeSet中有ISR集合的变更记录,就会创建一个持久顺序节点。
# 3、LEO和HW
- HW:High Watermark
- LW:Low Watermark
- LEO: Last End Offset
现在来分析消息同步过程上诉属性的变化,假设此时leader副本的LEO增加到5,HW为0:
follower副本开始第一次拉取
leader副本返回给follower副本相应的信息,并且带有自身的HW,两个副本接受消息之后,更新各自的LEO为3和4,并且更新自身的HW(LEW和HW最小值)
follower副本开始第二次拉取
这个时候会告诉leader副本各自的LEO,然后leader副本计算最新的HW(min(15,3,4) = 3)
leader副本接受消息之后,再把最新的HW传递给follower副本,follower副本更新HW。
# 4、Leader Epoch的介入
背景1 - 消息丢失
现在假设有两个副本A和B,初始状态如下,其中B是leader副本:
现在假设A宕机,A重启之后,由于HW是0,会把m2这条消息给丢失,此时它会找leader副本进行同步,假设同步的时候B也宕机了,那么现在的leader副本就是A了。
当B重启的时候,B就成了follower副本不能比leader副本高,所以m2这条消息也要丢失。
解决这个办法的一种方法就是follower副本恢复之后,在收到leader副本消息之前,不要截断消息,但是这样存在消息不一致的情况。
背景2 - 消息不一致
假设A和B副本情况如下,其中副本A是leader副本,两者重启之后,B副本先恢复过来,成为leader副本:
此时B副本新来一条消息,并且把HW修改为2:
此时A和B副本的HW都是2,但是消息不一样。
Leader Epoch应对数据丢失
和之前一样,A宕机重启后,不是先急着截断日志,而是先给B发送请求,B这个时候会去查询LEO,A发现LEO和副本B的LEO相同,就不需要截断日志。
Leader Epoch应对数据不一致
现在假设副本A和副本B同时宕机,其中副本A为leader副本:
之后重启过程中,B先恢复成为了Leader副本,并且写入了新的数据m3,
等到A恢复之后,给B发送请求,发现现在offset为1,A就截断日志并删除m2:
# 二、为什么不支持读写分离
在kafka中,生产者写入消息、消费者读取消息的操作都是和leader副本进行交互的,并非读写分离的方式,其中读写分离存在两个问题:
- 数据一致性问题:数据从主节点到从节点必然会有一个延时的时间窗口,这个时间窗口会导致主从节点
- 延时问题
# 三、日志同步机制
略
# 四、可靠性分析
# 1、分区副本保证可靠
就kafka而言,越多的副本数能够保证数据的可靠性,不过副本数越多也会引起磁盘、网络带宽的浪费,同时引起性能的下降。
设置副本数为3就可以满足绝大多数场景对可靠性的要求,而对可靠性要求更高的场景下,一般设置副本数为5就行
# 2、ack机制
除了依靠副本数来支持可靠性,ack机制也可以一定程度提高消息的可靠性:
ack为1,相当于只有leader副本接受到消息后就告知生产者完成了,如果在还没有同步给其他副本的时候,leader副本发生宕机,那么这条消息就丢失了
对于ack为1,相当于是异步方式发送消息,不需要等待确认,就告知生产者完成了,这种情况最不可靠。
对于ack为-1,是当消息成功写入leader副本之后,并且在ISR集合的所有副本同步完成后才告知生产者完成,这种情况最为可靠。
kafka通过retries参数可以配置重试的次数。
下面对ack为-1进行如下分析:
由于这种机制下,要求所有ISR集合中的副本全部同步完成之后才告知生产者同步完成,如果出现leader副本的消息流入速度很快,而follower副本同步速度很慢,那么在某一个临界点所有的follower副本剔除ISR集合,那么ISR中就只有一个leader副本,这种情况相当于是ack=1的情况。
针对这种情况,要指定ISR集合中最小的副本数不能为1