文章目录
副本与ISR设计
对于特定的主题下的特定分区,可能存在多个副本,具体分为两类:
- 首领副本,每个分区有一个首领副本,所有生产者和消费者请求都会经过这个副本。首领需要确保跟随者和自己的状态是一致的。为了保持和首领同步,跟随者向首领发送获取数据的请求,会附带想要获取的消息的偏移量,这个偏移量表示消费者的消费进度。如果跟随者在10s内没有请求任何消息或者请求最新的数据,则会被认为是不同步的。
- 跟随者副本,跟随者不处理来自客户端的请求,只负责从首领复制消息,保持与首领一直的状态。如果首领发生崩溃,其中的一个和首领状态完全同步的跟随者会被提升为新首领。
分区首领是同步副本,而对于跟随者而言,需要满足以下条件才能被认为是同步的:
- 与Zookeeper保持一个活跃的会话,在过去配置时间内向Zookeeper发送心跳
- 在过去配置时间内从首领那里获取过消息
- 在过去配置时间内从首领那里获取过最新的消息。
一个滞后的同步副本会知道生产者和消费者变慢,因为生产者可能需要等待其同步,消费者需要确保都同步后才能收到标记为已提交的消息。
所有的同步副本信息维护在zookeeper的ISR中,每个topic分区都有自己的ISR列表。
follower 副本同步
基于水印备份复制
先看副本的各个位置信息:
- 起始位移(base offset):表示该副本当前所含第一条消息的offset
- 高水印值(high watermark,HW)保存该副本最新一条已提交消息的位移。确定了consumer能够获取的消息上限。超过HW的消息对消费者来说都是不可见的
- 日志末端位移(log end offset, LEO):副本日志下一待写入消息的offset。所有副本的LEO信息可能会不一样。分区的HW即为所有副本中的最小的LEO。
LEO更新机制
- 对于leader端的LEO更新时机为每次写log的时候
- 对于follow端,会有两个副本LEO,分别存在leader和follow中,即leader存储了所有follow的LEO副本,基于这些副本来帮助leader更新HW。每次follow往leader拉取消息时,会同步更新leader端的follow LEO和follow端的LEO。
HW更新机制
-
对于follow,每次从leader拉取数据时,会比较当前LEO和leaderHW,取两者中的小值为新的HW。follow的HW值不会超过leader HW值
-
对于leader,有4中情况尝试更新,不满足条件则不更新:
- 分区leader发生变化,此时leader副本会尝试更新HW
- broker出现崩溃导致副本被踢出ISR时:若有broker崩溃,会检查是否波及当前分区
- producer向leader副本写入消息时:会更新leader的LEO,会查看HW值是否需要更新
- leader处理follower拉取请求时:会从底层的log读取数据,然后尝试更新HW值
满足更新尝试条件时,leader会找出所有的同步副本,比较所有的LEO,取其中的最小值为HW。其中同步副本满足以下两个条件之一:
1. 在ISR中
2. 副本LEO落后于leader LEO的时长不大于replica.lag.time.max.ms(默认为10s),主要处理特殊时期下刚好追上leader进度,但不在ISR的情况
缺陷
基于水印同步会引起两个问题:
- 数据丢失,基于以下图示来分析:
在开始时候,A.LEO=1,A.HW=0,B.HW=0,B.LEO=1。B向A发出拉取请求,此时A会更新自身HW=1(并通知生产者消费成功),B会尝试更新自身HW=min(A.HW=1,B.LEO=1)=1,但这个时候B故障重启,重启后会调整LEO=HW=0,导致HW未更新,而后A副本挂掉,B称为leader,导致了第一条消息丢失。 - 数据不一致/数据离散,基于以下图示分析:
开始A是leader,B是follow。消息情况如图所示,而后A、B挂掉,B称为leader,接收生产者消息3,但实际更新了自身的偏移wei位2,而后A恢复称为follow,此时从外部看A,B是同步的,但实际上A的第二条消息和B的第二条消息不是同一条消息,导致了数据不一致的情况。
基于leader epoch
在0.11.0.0版本后,kafka引入leader epoch替代HW,解决了水印备份复制机制的两个问题。
leader epoch实际为一对值(epoch,offset)。epoch表示leader的版本号,leader发生变化,则epoch+1。offset为epoch版本对应的leader写入第一条消息的偏移,假设存在两对值(0,0),(1,120)表