深入kafka之副本机制

分区的副本(Replica)机制

我们已经知道Kafka的每个topic都可以分为多个Partition,并且多个partition会均匀分布在集群的各个节点下。虽然这种方式能够有效的对数据进行分片,但是对于每个partition来说,都是单点的,当其中一个partition不可用的时候,那么这部分消息就没办法消费。所以kafka为了提高partition的可靠性而提供了副本的概念(Replica),通过副本机制来实现冗余备份

每个分区可以有多个副本,并且在副本集合中会存在一个leader的副本,所有的读写请求都是由leader副本来进行处理。剩余的其他副本都做为follower,follower副本会从leader副本同步消息日志
这个有点类似zookeeper中leader和follower的概念,但是具体的实现方式还是有比较大的差异。所以我们可以认为,副本集会存在一主多从的关系

一般情况下,同一个分区的多个副本会被均匀分配到集群中的不同broker上,当leader副本所在的broker出现故障后,可以重新选举新的leader副本继续对外提供服务。通过这样的副本机制来提高kafka集群的可用性

例如:下面展示了secondTopic这个topic的3个分区以及分区的副本的存储
secondTopic_0 是分区0

在这里插入图片描述
副本的leader选举
要了解leader选举,我们需要了解几个概念

副本角色
Kafka分区为了进一步实现高可用。副本根据角色的不同可分为几类:

  • leader:响应clients端的读写请求
  • follower:被动地备份leader中的数据,不能响应clients端读写请求
  • ISR:包含了leader和所有与leader保持同步的follower的集合

副本属性
每个Kafka副本对象都有的两个重要属性:
**LEO:**即日志末端位移,记录了该副本底层日志(log)中下一条消息的位移值。也就是说,如果LEO=10,那么表示该副本保存了10条消息,位移值范围是[0, 9]。另外,leader LEO和follower LEO的更新是有区别的
**HW:**位移值。对于副本而言其HW值不会大于LEO值。小于等于HW值的所有消息都被认为是“已备份”的(replicated)。同理,leader副本和follower副本的HW更新是有区别的

从生产者发出的 一 条消息首先会被写入分区的leader 副本,不过还需要等待ISR集合中的所有follower副本都同步完之后才能被认为已经提交,之后才会更新分区的HW, 进而消费者可以消费到这条消息

副本协同机制
kafka的消息的读写操作都只会由leader节点来接收和处理。follower副本只负责同步数据。写请求首先由Leader副本处理,之后follower副本会从leader上拉取写入的消息,这个过程会有一定的延迟,follower副本中保存的消息会略少于leader副本,但是只要没有超出阈值都可以容忍。但是如果一个follower副本出现异常,比如宕机、网络断开等原因长时间没有同步到消息,那这个时候,leader就会把它踢出去。kafka通过ISR集合来维护一个分区副本信息

ISR
ISR表示目前“可用且消息量与leader相差不多的副本集合,这是整个副本集合的一个子集”。怎么去理解可用和相差不多这两个词呢?

具体来说,ISR集合中的副本必须满足两个条件

  1. 副本所在节点必须维持着与zookeeper的连接
  2. 副本最后一条消息的offset与leader副本的最后一条消息的offset之间的差值不能超过指定的阈值replica.lag.time.max.ms。如果该follower在此时间间隔内一直没有追上过leader的所有消息,则该follower就会被剔除isr列表

ISR数据保存在Zookeeper的 /brokers/topics//partitions//state节点中

副本过期检查
follower把leader的LEO之前的日志全部同步完成时,则认为follower副本已经追赶上了leader,这个时候会更新这个副本的lastCaughtUpTimeMs标识,kafka副本管理器会启动一个副本过期检查的定时任务,这个任务会定期检查当前时间与副本的lastCaughtUpTimeMs的差值是否大于参数replica.lag.time.max.ms 的值,如果大于,则会把这个副本踢出ISR集合

在这里插入图片描述
假如最右侧的follower副本被踢出ISR集合,这个分区的HW也会发生变化,变成了3

如何处理所有的Replica不工作的情况
在ISR中至少有一个follower时,Kafka就可以确保已经commit的数据不丢失,但如果某个Partition的所有Replica(副本)都宕机了,就无法保证数据不丢失了,解决方案有两种:

  1. 等待ISR中的任一个Replica“活”过来,并且选它作为Leader
  2. 选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader
    这就需要在可用性和一致性当中作出一个折衷
  • 如果一定要等待ISR中的Replica“活”过来,那不可用的时间就可能会相对较长。而且如果ISR中的所有Replica都无法“活”过来了,或者数据都丢失了,这个Partition将永远不可用。
  • 如果选择第一个“活”过来的Replica作为Leader,而这个Replica不是ISR中的Replica,那即使它并不保证已经包含了所有已commit的消息,它也会成为Leader而作为consumer的数据源
副本数据同步原理

了解了副本的协同过程以后,还有一个重要的机制,就是数据的同步过程。它需要解决两个问题:

  1. 怎么传播消息
  2. 在向消息发送端返回ack之前需要保证多少个Replica已经接收到这个消息

副本数据同步过程:
(1)Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader,然后Producer将该消息发送到该Partition的Leader
(2)Leader会将该消息写入其本地Log,每个Follower都从Leader pull数据。这种方式Follower存储的数据顺序可以与Leader保持一致。Follower在收到该消息并写入其Log后,向Leader发送ACK
(3)一旦Leader收到了ISR中的所有Replica的ACK,该消息就被认为已经commit了,Leader将增加HW(位移值)并且向Producer发送ACK

初始状态

在初始状态下,leader和follower的HW和LEO都是0,leader副本会保存remote LEO,表示所有follower 的LEO为0,这个时候,producer没有发送消息。follower会不断地向leader发送FETCH请求,但是因为没有数据,这个请求会被leader寄存,当在指定的时间之后会强制完成请求,这个时间配置是(replica.fetch.wait.max.ms),如果在指定时间内producer有消息发送过来,那么kafka会唤醒fetch请求,让leader继续处理

数据的同步处理会分两种情况

  • 第一种是leader处理完producer请求之后,follower发送一个fetch请求过来
  • 第二种是follower阻塞在leader指定时间之内,leader副本收到producer的请求
    这两种情况处理流程基本一致

下面我们来分析一下第一种情况下的数据同步流程:

生产者发送一条消息
(1)leader处理完producer请求
在这里插入图片描述

leader副本收到请求以后,会做几件事情

  1. 把消息追加到log文件,同时更新leader副本的LEO
  2. 尝试更新leader HW值。这个时候由于follower副本还没有发送fetch请求,那么leader的remote
    LEO仍然是0。leader会比较自己的LEO以及remote LEO的值发现最小值是0,与HW的值相同,所
    以不会更新HW

(2)follower 发送fetch请求,leader副本的处理fetch请求

在这里插入图片描述

  1. 读取log数据、更新remote LEO=0(follower还没有写入这条消息,这个值是根据follower的fetch
    请求中的offset来确定的)
  2. 尝试更新HW,因为这个时候LEO和remoteLEO还是不一致,所以仍然是HW=0
  3. 把消息内容和当前分区的HW值发送给follower副本

follower副本收到response以后
在这里插入图片描述

  1. 将消息写入到本地log,同时更新follower的LEO
  2. 更新follower HW,本地的LEO和leader返回的HW进行比较取小的值,所以仍然是0

第一次交互结束以后,HW仍然还是0

(3)follower发第二次fetch请求,leader处理请求
在这里插入图片描述

  1. 读取log数据
  2. 更新remote LEO=1, 因为这次fetch携带的offset是1
  3. 更新当前分区的HW,这个时候leader LEO和remote LEO都是1,所以HW的值也更新为1
  4. 把数据和当前分区的HW值返回给follower副本,这个时候如果没有数据,则返回为空

follower副本收到response以后
在这里插入图片描述

  1. 如果有数据则写本地日志,并且更新LEO
  2. 更新follower的HW值

第二种情况
leader接收到新的数据。当leader收到请求以后会唤醒处于阻塞的fetch请求。处理过程基本上和前面说的一致
1.leader将消息写入本地日志,更新Leader的LEO
2.唤醒follower的fetch请求
3.更新HW

但是kafka的这种设计会存在数据丢失的问题,当然这个丢失只出现在特定的背景下。这是由于HW的值是在新的一轮FETCH 中才会被更新。我们分析下这个过程为什么会出现数据丢失

replicas
min.insync.replicas=1 //表达的含义是,至少需要多少个副本同步才能表示消息是提交的,默认值为1(在server.properties中配置,并且acks参数设置为-1(表示需要所有副本确认)时,此参数才生效

producer的ack
acks配置表示producer发送消息到broker上以后的确认值。有三个可选项

  • 0:表示producer不需要等待broker的消息确认。这个选项时延最小但同时风险最大(因为当server宕机时,数据将会失)。
  • 1:表示producer只需要获得kafka集群中的leader节点确认即可,这个选择时延较小同时确保了leader节点确认接收成功
  • all(-1):需要ISR中所有的Replica给予接收确认,速度最慢,安全性最高,但是ISR可能会缩小到仅包含一个Leader Replica,所以设置参数为all并不能一定避免数据丢失

当min.insync.replicas=1的时候,一旦消息被写入 leader 端本地 log 即被认为是“已提交”,而延迟一轮 fetch 更新 HW 值的设计使得 follower 的 HW 值是异步延迟更新的,倘若在这个过程中 leader 发生变更,那么成为新 leader 的 follower 的 HW 值就有可能是过期的,使得消费端端认为是成功提交的消息被删除,如下图所示:
在这里插入图片描述
那在 Kafka 有没有解决数据丢失的解决方案呢?
答案是有的,Kafka 中引入了一个 leader epoch 来解决这个问题

epoch 实际上就是一对值(epoch,offset),epoch 代表 leader 的版本号,从0开始递增,当 leader 发生过变化 epoch 的值就+1,而 offset 则对应这个 epoch 版本的 leader 写入第一条消息的 offset

  • 如果 follower 宕机并且恢复之后,有两种情况,leader 没有挂,也就意味呢没有 leader 选举,那 follower 恢复之后并不会去截断自己的日志,而是先发送一个 OffsetsForLeaderEpochRequest 请求给到 leader 副本,leader 副本收到请求之后返回当前的 LEO

  • 如果 follower 的 epoch 和 leader 副本的 epoch 相同,leader 的 LEO 只可能大于或者等于 follower 副本的 LEO 值,所以这个时候不会发生截断。

  • 如果 follower 副本和 leader 副本的 epoch 值不同,那么 leader 副本会查找 follower 副本传过来的 epoch+1 在本地文件中存储的 StartOffset 返回给 follower 副本,也就是新leader副本的LEO。这样也避免了数据丢失的问题。

  • 如果 leader 副本宕机了重新选举新的 leader,那原本的 follower 副本就会变成 leader,意味着 epoch 会增加,使得原本 follower 副本中 LEO 的值的到了保留

Leader副本的选举过程
KafkaController会监听ZooKeeper的/brokers/ids节点路径,一旦发现有broker挂了,执行下面的逻辑

leader副本在该broker上的分区就要重新进行leader选举,目前的选举策略是
a) 优先从isr列表中选出第一个作为leader副本,这个叫优先副本,理想情况下有限副本就是该分区的leader副本
b) 如果isr列表为空,则查看该topic的unclean.leader.election.enable配置。
unclean.leader.election.enable:为true则代表允许选用非isr列表的副本作为leader,那么此时就意味着数据可能丢失,为false的话,则表示不允许,直接抛出NoReplicaOnlineException异常,造成leader副本选举失败
c) 如果上述配置为true,则从其他副本中选出一个作为leader副本,并且isr列表只包含该leader副本。一旦选举成功,则将选举后 的leader和isr和其他副本信息写入到该分区的对应的zk路径上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值