这是我参与更文挑战的第7天,活动详情查看:更文挑战
先对 kafka 高可用机制的基本原理开个篇:
kafka高可用是通过leader-follower多副本机制同步完成- 每一个分区含有多个副本 (分区是真正存储日志消息的物理存储介质)
- 多个副本只有一个
leader,也只有它对外提供服务 (读写服务都是) - 其他
follower副本只是一个备份冗余 follower副本是通过不断向leader 副本发送fetch以同步消息
所以如果 leader 副本 挂了,其中一个 follower副本 就会成为该分区下新的 leader副本。问题就来了,在选为新的 leader副本 时,old leader 副本 消息没有同步完全,会导致消息丢失或者离散吗?Kafka 是如何解决 leader副本 变更时消息不会出错?
HW 机制
先说几个关键术语:
HW:HW 一定不会大于日志末端位移,offset < HW的日志被认为是 已提交,已备份,对消费者可见LEO:last end offset,日志末端位移。下一个日志要写入的地方
这两个指标是怎么存储?
leader会保存自己的LEO, HW,同时会保存remote leader的LEO, HW- 每一个
follower只会保存自己的LEO, HW - 以上说明:
remote follower的LEO, HW会被存放在两个地方
下面说说这两个指标的更新机制:
LEO 更新
现在 Producer 发送一批消息到 broker,各个副本的变化:
leader副本自身LEO+1(因为读写都是由目前的leader副本提供功能)follower副本从leader副本fetch 日志消息到本地,然后LEO+1- 每次
followerfetch会将自身的LEO携带发送,leader副本中的remote follower LEO更新
HW 更新
无图🧂🔨:
基本流程过一下:
leader分区接收到producer发送的消息,该分区磁盘中写入消息,并LEO++follower向leaderfetch消息,携带fetchOffset=N(当前需要fetch到的消息offset),更新leader remote LEO,根据每个leader remote LEO, leaderHW更新leaderHW- 随即
leader发送当前的leaderHW以及日志消息响应消息,发送给follower。follower该分区写入消息,更新自己的follower LEO++ follower发送第二轮 fetch,携带当前最新的fetchOffset = N+1,leader接收到请求,更新remote LEO = N+1,根据leaderHW计算最新的的值,同时将leaderHW发送给followerfollower对比当前最新的LEO与leaderHW,取最小的作为新的followerHW值
HW 机制缺陷
总体来说,我们需要两轮 fetch req 才能完成 leader 对 leaderHW & followerHW 的更新。如果我们在这个过程中发生 leader切换,就会出现数据丢失/数据不一致
leader epoch
在 0.11 版本后推出,主要是为了弥补 HW 机制的问题。
我们可以看到这个 leader-epoch-checkpoint 文件,就是用来保存 leader 的 epoch data:
$ cat leader-epoch-checkpoint
0
1
0 0
3 10898
格式:<epoch, offset>。epoch 表示 leader 版本,保证单调递增,每次 leader 变更,epoch++;offset 表示每一代 leader 写入第一条消息的位移值。
工作机制
我们直接从缺陷这边看,就知道整个机制运行:
follower 宕机之后不会出现以前那种日志截断的情况
说明运行以上出现的情况:
- 宕机之前,
follower已不在 ISR 列表中,unclean.leader.election.enable=true,即允许非 ISR 中副本成为 leader follower消息写入到 pagecache,但尚未 flush 到磁盘。此时 HW 可能是整个同步写入的中间态
epoch 这里解决消息不一致的情况:
- 新的
follower发送epochReq会携带自己曾经的epoch - 新的
leader判断发送来的epoch != nowEpoch,然后会发送自己的epoch start offset - 新的
follower收到新的epoch start offset,会从这个位置往后截断自己的日志 (说白了就是要和成为leader的leader保持一致的过去) - 发送
fetchreq,开始新的同步,那么之后的消息都是一致的。