什么是高水位
在时刻T ,创建任意时间(Event Time)为T' 且T'<= T 的所有事件都已到达或被观测到,那么T就被定义为水位
水位是一个单调增加且表征最早未完成工作的时间戳
Completed部分蓝色部分代表已完成的工作,标注 IN-flight的红色部分正在进行中的工作,两者的边界就是水位线
Kafka 世界中,水位不是时间戳 更与时间无关。它是和位置信息绑定的,它是用消息位移来表征的。
LW 是与删除消息相关联的概念
高水位的作用
1定义消息可见性即用来标识分区下的哪些消息是可以被消费者消费的。
2帮助Kafka完成副本同步
排除事务,因为事务会影响消费者所能看到的消息的范围,它不单是简单的依赖高水位来判断,它依靠一个名为LSO
的位移值来判断事务型消费者的可见性
位移值等于高水位的消息也属于未提交消息,也就是说,高水位的消息不能被消费者消费的的
LEO 表示副本写入下一条消息的位移值。数字15所在的方框是虚线,表明这个副本当前只有15条消息,位移值是从0到14 下一条新消息的位移时15
介于高水位和LEO之间的消息都属于未提交消息,同一个副本对象,其高水位值不会大于LEO的值
Kafka使用Leader副本的高水位来定义所在分区的高水位;即分区高水位就是其Leader副本的高水位
高水位更新机制
Leader副本 还保存了其他Follower副本的LEO值
Kafka把broker0上保存的这些Follower副本称为远程副本。Kafka副本机制在运行过程中,会更新Broker1上ollwer副本的高水位和LEO值,同时也会更新Broker0上Leader副本的高水位和LEO以及远程副本的LEO,但是它不会更新远程副本的高水位值
Broker0上保存远程副本,是帮助Leader副本确定其高水位
副本更新机制
与Leader副本保持同步,判断条件有两个
1该远程Follower副本在ISR中
2该远程Follower副本LEO值落后于Leader副本LEO值的时间,不超过Broker端参数
replica.lag.time.max.ms 如果使用默认值,就是10秒
Leader副本
处理生产者请求的逻辑如下:
1写入消息到本地磁盘。
2更新分区高水位值
i 获取Leader副本所在Broker端保存的所有远程副本LEO值()LEO-1,LEO-2)
ii 获取Leader副本高水位值 HW
iii 更新HW min(HW,LEO-1,LEO-2,。。)
处理Follower副本拉取消息的逻辑:
1读取磁盘或页缓存中消息数据
2使用Follower副本发送请求中的位移值更新远程副本LEO值
3更新分区高水位值
Follower副本
从Leader拉取消息的处理逻辑
1写入消息到本地磁盘
2更新LEO值
3更新高水位值
i 获取Leader副本发送高水位值 HW
ii 获取步骤二中更新过的LEOZ值
iii 更新HW min(HW,LEO)
副本同步机制解析:
举例说明
Leader Epoch登场
Leader副本高水位和Follower副本高水位在时间上存在错配,这种错配导致 数据丢失或者数据不一致
0.11版本引入Epoch 来避免高水位更新错配导致的各种不一致问题
有两部分构成:
1Epoch 一个单调递增的版本号,每次到领导副本权发生变更时,会增加该版本号
2起始位移 Leader副本在改Epoch值上写入的首条消息的位移
Kafka Broker会在内存中为每个分区都缓存Leader Epoch数据,同时它还会定期地将这些消息持久化到一个checkpoint文件中
当Leader副本写入消息到磁盘时,Broker会尝试更新这部分缓存。如果该Leader是首次写入消息,那么Broker
会向缓存中增加一个LeaderEpoch 条目,否则就不做更新。每次有Leader变更时,新的Leader副本会查询这部分缓存,取出相应的Leader Epoch的起始位移,以避免数据丢失和不一致的情况。
举例
开始时,副本A和副本B都处于正常状态,A是Leader副本,某个使用了默认acks设置的生产者程序向A
发送了两条消息,A全部写入成功,此时Kafka会通知生产者说两条消息全部发送成功
假设 Leader 和Follower都写入这两条消息,而且Leader副本的高水位已经更新,Follower副本的高水位还未更新
假若此时副本B所在Broker宕机,当它重启回来后,副本B执行日志截断操作,将LEO值调整为之前的高水位值,也就是1.
也就是说,位移值为1的那条消息被副本B从磁盘中删除,此时副本B的底层磁盘文件只保留一条消息,即位移值为0的那条消息
当执行完截断操作后,副本B开始从A拉取消息,执行正常得消息同步。如果这时副本A所在的Broker宕机,那么Kafka,只能让副本B称为Leader,此时当A回来后 需要执行相同的日志截断操作,即将高水位调整为与B相同的值,也就是1.这样操作后,位移值为1的那条消息就从这两个副本中被永远地抹掉了
发生的前提是Broke端参数 mininsync.replicas =1
引入Leader Epoch 机制来规避这种数据丢失
当副本B重启回来,需要向A发送一个特殊的请求获取Leader的LEO值 ,在该列子中,该值为2,当获知到Leader LEO=2后
B发现该LEO值不比它自己的LEO值小,而且缓存中也没有任何起始位移值>2的Epoch条目 因此B无需执行任何日志截断操作。
副本是否进行日志截断不在依赖于高水位进行判断
现在 副本A宕机了,B成为Leader 同样地,当A重启回来后,执行与B相同的判断逻辑,发现也不用日志截断
后面生产者向B写入新消息时,副本所在的Broker缓存中,会生成Leader Epoch的新条目 【Epoch =1 Offset =2】