概念
什么是副本
Kafka的Topic分区本质是一个用于存储Topic下的消息的日志,但是只存一份日志会因为机器损坏或其他原因导致消息丢失不可恢复,
因此需要多个相同的日志作为备份,提高系统可用性,这些备份在kafka中被称为副本(replica)。
kafka将分区的所有副本均匀的分配到所有broker上,并从这些副本中选取一个作为leader副本对外提供读写服务,
其他副本则被称为follower副本,只能被动的向leader副本请求数据以此保持和leader副本的状态同步。
什么是ISR
在生产环境下,因为各种不可抗因素,服务可能会发生宕机,例如对外提供服务的leader副本,如果其发生宕机不可用,将会影响系统的使用,
因此在leader副本发生宕机时,follower副本就发生作用了,kafka将从follower副本中选取一个作为新的leader副本对外提供服务,
当然,并不是所有的follower副本都有资格成为leader,因为有些follower副本可能因为各种原因,此时保存的数据落后于之前的leader,
如果数据落后的follower成为了leader,将会引发消息的丢失因此kafka引入了ISR的概念。
ISR,全称 in-sync replicas,是一组动态维护的同步副本集合,每个topic分区都有自己的ISR列表,ISR中的所有副本都与leader保持同步状态(也包括leader本身),只有ISR中的副本才有资格被选为新的leader,
Producer发送消息时,消息只有被全部写到了ISR中,才会被视为已提交状态,若分区ISR中有N个副本,那么该分区ISR最多可以忍受 N-1 个副本崩溃而不丢失消息。
follower副本同步
follower副本只做一件事,向leader副本请求数据,副本有如下几个概念:
- 起始位移(base offset)表示该副本当前所含第一条消息的offset。
- 高水印值(high watermark, HW)副本高水印值,它保存了该副本最新一条已提交消息的位移。leader副本的HW值决定副本中已提交消息的范围,也确定了consumer能够消费到的消息的上限,超过HW值的所有消息都被视为未提交成功,consumer看不到这些未提交成功的消息每个follower副本也都有自己的HW值,不过只有leader的HW值才能决定consumer可以看到的消息数量。
- 日志末端位移(log end offset, LEO)副本日志中下一条待写入消息的offset,所有副本都需要维护自己的LEO,每当leader接收到producer的消息时,其更新自己的LEO(通常+1),follower副本接收到了leader的数据后,也会更新自己的LEO,只有当ISR中的副本都更新了对应的LEO后,leader副本才会向右移动HW值,表示写入成功。
副本同步过程
假设某Kafka集群中(broker1、2、3)仅有一个Topic,该Topic只有一个分区,该分区有3个副本,ISR中也是这3个副本,该Topic中目前没有任何数据,因此3个副本中的LEO和HW都是0。
此时某Producer(Producer的acks参数设置成了-1)向broker1中的leader副本发送了一条消息,接下的流程如下:
- broker1上的leader副本接收到消息,将自己的LEO更新为1
- broker2和3上的follower副本各自发送请求给broker1
- broker1分别将消息推送给broker2、3上的副本
- follower副本收到消息后,进行写入然后将自己的LEO也更新为1
- leader副本收到其他follower副本的数据请求响应(response)后,更新HW值为1,此时位移为0的消息可以被consumer消费
Producer的acks参数设置成了-1后,必须完整的做完上面的几步操作,Producer才能被正常返回,即该消息发送成功。
ISR设计
ISR是与leader副本保持同步的集合,这意味着是存在与leader副本无法保持同步的副本的(out-of-sync),那么如何界定ISR,在Kafka中大体可以分为两种情况,Kafka0.9版本前后的界定ISR方式不同。
0.9版本前的界定方式
relica.lag.max.messages
0.9版本之前,Kafka提供了一个名为
relica.lag.max.messages
用于控制follower副本落后leader副本的消息数,一旦落后的消息超过这个参数数值,则该follower被视为不同步的副本,随后要被踢出ISR集合中。
举一个案例描述follower副本如何落后于leader副本的情况,假设现有集群broker1、2、3,有一个单分区的Topic,副本数量是3,
leader副本处于broker1中,其他两个broker中的副本都是follower副本,此时
relica.lag.max.messages
参数的值设置为4,
现有一个Producer每次向该Topic发送3条消息,在正常初始状态下,每个follower副本都是可以追上leader副本的,如下:
随后再一次,Producer发送了一条消息过来,此时broker3发生了full gc,导致无法与leader副本保持一致,于是状态就变成了下图:
此时,leader的LEO已经不再与HW相等,最新生产的这条消息不会被认为已提交,除非broker3上的follower副本被踢出ISR集合,但此时
relica.lag.max.messages
的值是4,而broker3的副本仅落后一条,因此也不会被踢出,
对于broker3上的副本而言,只需要追上leader的LEO即可,因此当full gc过去后,此时的日志状态如下:
此时leader的HW和LEO再次重叠,两个follower也已经与leader保持同步。
除了FullGC导致的副本同步落后,一般还有下面的几种情况导致同步落后:
- 请求速度追不上由于follower副本所在的broker的网络IO开销过大导致从leader处获取消息缓慢。
- 进程卡住由于频繁GC或者程序bug导致follower无法向leader请求数据。
- 副本是新创建的新增加的副本在创建完毕后会全力追赶leader的进度,在追赶这段时间内,通常与leader副本的状态都是不同步的。
replica.lag.time.max.ms
除了消息落后的最大数量,0.9之前的版本还提供了一个
replica.lag.time.max.ms
参数用于在同步时间上做出判断,超时该时间没有向leader请求数据的follower副本将被视为不同步,随后被踢出ISR。
relica.lag.max.messages 存在的问题
relica.lag.max.messages
参数限定了follower副本与leader副本之间同步时,follower副本可落后的消息数量,例如上面设置的是4,意味着主从副本之间消息同步不可超过这值,
但是,如果Producer一次性发送了4条消息,此时与
relica.lag.max.messages
的值相等,那么上面的两个follower副本都会认为与leader副本不同步,从而被踢出ISR,
此时两个follower副本都处于存活状态(alive),且没有任何性能问题,很快就可以追上leader的LEO,并且重新加入ISR,
因此当后续的Producer发送的消息都是4或者大于4时,上面的follower副本被踢出ISR然后重新加入ISR的过程就会一直重复,造成很大的资源开销浪费。
有些用户会将
relica.lag.max.messages
调的过大来解决Producer发送过多的消息时导致的follower副本被来回踢出ISR的情况。
例如将该值设置为4000,这样follower副本就不会被来回踢出了,但是这又会引发另一个问题,
relica.lag.max.messages
的值是对全局生效的,即所有的topic都受到该值的影响,
如果kafka集群中有两个topic,topic1和topic2,这两个topic的流量差异比较大,topic1的生产者可能一次性生产了5000条消息,直接突破了
relica.lag.max.messages
的限定值,又引发了ISR的进出重复,
而topic2的生产者每次仅生产10条消息,
relica.lag.max.messages
的值过大导致可能topic2的follower副本有些真的已经落后不同步了,
但是需要达到
relica.lag.max.messages
的值后才会被发现,这样一旦出现宕机重选leader副本,很容易造成数据的丢失。
0.9版本后的界定方式
由于
relica.lag.max.messages
的弊端,很难被把控和调优,在0.9版本之后,kafka采用统一的参数来处于界定ISR不同步,
摈弃了
relica.lag.max.messages
,只留下了
replica.lag.time.max.ms
,该值默认时间为10秒。
0.9版本后针对由于请求速度追不上的情况,检测机制做了调整,即如果一个follower副本落后leader的时间持续性的超过了这个参数值,
那么该follower副本则被认定为不同步,这样在出现Producer瞬时峰值流量时,只要follower不是持续性落后,也不会反复在ISR中进出。
简单来说,0.9版本之前,Kafka通过 落后消息数+时间 来界定ISR的同步状态,而0.9版本后,统一通过时间来界定。
版权归原作者 多动手,勤思考 所有, 如有侵权,请联系我们删除。