kafka的副本数据的同步原理

在《kafka的partition 的高可用副本机制》一文中介绍了parttion的高可用原理,副本机制中的几个概念和协同机制后,下面来说说副本数据的同步原理。

数据的同步过程

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

  1. 怎么传播消息

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

数据的处理过程是

Producer 在 发 布 消 息 到 某 个 Partition 时 , 先 通 过ZooKeeper 找到该 Partition 的 Leader 【 get /brokers/topics//partitions/2/state】,然后无论该Topic 的 Replication Factor 为多少(也即该 Partition 有多 少个 Replica(副本)),Producer 只将该消息发送到该 Partition 的Leader。Leader 会将该消息写入其本地 Log。每个 Follower都从 Leader pull 数据。这种方式上,Follower 存储的数据顺序与 Leader 保持一致。Follower 在收到该消息并写入其Log 后,向 Leader 发送 ACK。一旦 Leader 收到了 ISR 中的所有 Replica 的 ACK,该消息就被认为已经 commit 了,Leader 将增加 HW(HighWatermark)并且向 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 的请求。

这两种情况下处理方式是不一样的。先来看第一种情况

follower 的 fetch 请求是当 leader 处理消息以后执行的

生产者发送一条消息

leader 处理完 producer 请求之后,follower 发送一个fetch 请求过来 。状态图如下

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

  1. 把消息追加到 log 文件,同时更新 leader 副本的 LEO

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

follower fetch 消息

follower 发送 fetch 请求,leader 副本的处理逻辑是:

  1. 读取 log 数据、更新 remote LEO=0(follower 还没有写入这条消息,这个值是根据 follower 的 fetch 请求中的offset 来确定的)

  2. 尝试更新 HW,因为这个时候 LEO 和 remoteLEO 还是不一致,所以仍然是 HW=0

  3. 把消息内容和当前分区的 HW 值发送给 follower 副本,follower 副本收到 response 以后

  4. 将消息写入到本地 log,同时更新 follower 的 LEO

  5. 更新 follower HW,本地的 LEO 和 leader 返回的 HW进行比较取小的值,所以仍然是 0 第一次交互结束以后,HW 仍然还是 0,这个值会在下一次follower 发起 fetch 请求时被更新

follower 发第二次 fetch 请求,leader 收到请求以后

  1. 读取 log 数据

  2. 更新 remote LEO=1, 因为这次 fetch 携带的 offset 是1.

  3. 更新当前分区的 HW,这个时候 leader LEO 和 remoteLEO 都是 1,所以 HW 的值也更新为 1

  4. 把数据和当前分区的 HW 值返回给 follower 副本,这个时候如果没有数据,则返回为空

follower 副本收到 response 以后

  1. 如果有数据则写本地日志,并且更新 LEO

  2. 更新 follower 的 HW 值到目前为止,数据的同步就完成了,意味着消费端能够消费 offset=0 这条消息。

follower 的 fetch 请求是直接从阻塞过程中触发

前面说过,由于 leader 副本暂时没有数据过来,所以follower 的 fetch 会被阻塞,直到等待超时或者 leader 接 收到新的数据。当 leader 收到请求以后会唤醒处于阻塞的fetch 请求。处理过程基本上和前面说的一直

  1. leader 将消息写入本地日志,更新 Leader 的 LEO

  2. 唤醒 follower 的 fetch 请求

  3. 更新 HW

kafka 使用 HW 和 LEO 的方式来实现副本数据的同步,本身是一个好的设计,但是在这个地方会存在一个数据丢失的问题,当然这个丢失只出现在特定的背景下。我们回想一下,HW 的值是在新的一轮 FETCH 中才会被更新。我们分析下这个过程为什么会出现数据丢失

数据丢失的问题

前提:min.insync.replicas=1 的时候。->设定 ISR 中的最小副本数是多少,默认值为 1, 当且仅当 acks 参数设置为-1(表示需要所有副本确认)时,此参数才生效. 表达的含义是,至少需要多少个副本同步才能表示消息是提交的所以,当 min.insync.replicas=1 的时候一旦消息被写入 leader 端 log 即被认为是“已提交”,而延迟一轮 FETCH RPC 更新 HW 值的设计使得 follower HW值是异步延迟更新的,倘若在这个过程中 leader 发生变更,那么成为新 leader 的 follower 的 HW 值就有可能是过期的,使得 clients 端认为是成功提交的消息被删除。

数据丢失的解决方案

在 kafka0.11.0.0 版本以后,提供了一个新的解决方案,使用 leader epoch 来解决这个问题,leader epoch 实际上是一对之(epoch,offset), epoch 表示 leader 的版本号,从 0开始,当 leader 变更过 1 次时 epoch 就会+1,而 offset 则对应于该 epoch 版本的 leader 写入第一条消息的位移。比如说

(0,0) ; (1,50); 表示第一个 leader 从 offset=0 开始写消息,一共写了 50 条,第二个 leader 版本号是 1,从 50 条处开始写消息。这个信息保存在对应分区的本地磁盘文件中,文 件 名 为 : /tml/kafka-log/topic/leader-epochcheckpoint

leader broker 中会保存这样的一个缓存,并定期地写入到一个 checkpoint 文件中。

当 leader 写 log 时它会尝试更新整个缓存——如果这个leader 首次写消息,则会在缓存中增加一个条目;否则就 不做更新。而每次副本重新成为 leader 时会查询这部分缓存,获取出对应 leader 版本的 offset

如何处理所有的 Replica 不工作的情况

在 ISR 中至少有一个 follower 时,Kafka 可以确保已经commit 的数据不丢失,但如果某个 Partition 的所有 Replica 都宕机了,就无法保证数据不丢失了

  1. 等待 ISR 中的任一个 Replica“活”过来,并且选它作为Leader

这就需要在可用性和一致性当中作出一个简单的折衷。如果一定要等待 ISR 中的 Replica“活”过来,那不可用的时 间就可能会相对较长。而且如果 ISR 中的所有 Replica 都无法“活”过来了,或者数据都丢失了,这个 Partition 将永远不可用。

     2.选择第一个“活”过来的 Replica(不一定是 ISR 中的)作为 Leader

选择第一个“活”过来的 Replica 作为 Leader,而这个Replica 不是 ISR 中的 Replica,那即使它并不保证已经包 含了所有已 commit 的消息,它也会成为 Leader 而作为consumer 的数据源(前文有说明,所有读写都由 Leader完成)。使用的是第一种策略

ISR 的设计原理

在所有的分布式存储中,冗余备份是一种常见的设计方式,而常用的模式有同步复制和异步复制,按照 kafka 这个副本模型来说

如果采用同步复制,那么需要要求所有能工作的 Follower 副本都复制完,这条消息才会被认为提交成功,一旦有一个follower 副本出现故障,就会导致 HW 无法完成递增,消息就无法提交,消费者就获取不到消息。这种情况下,故障的Follower 副本会拖慢整个系统的性能,设置导致系统不可用如果采用异步复制,leader 副本收到生产者推送的消息后,就认为次消息提交成功。follower 副本则异步从 leader 副本同步。这种设计虽然避免了同步复制的问题,但是假设所有follower 副本的同步速度都比较慢他们保存的消息量远远落后于 leader 副本。而此时 leader 副本所在的 broker 突然宕机,则会重新选举新的 leader 副本,而新的 leader 副本中没有原来 leader 副本的消息。这就出现了消息的丢失。

kafka 权衡了同步和异步的两种策略,采用 ISR 集合,巧妙解决了两种方案的缺陷:

1.当 follower 副本延迟过高,leader 副本则会把该 follower 副本提出 ISR 集合,消息依然可以快速提交。

2.当 leader 副本所在的 broker 突然宕机,会优先将 ISR 集合中follower 副本选举为 leader,新 leader 副本包含了 HW 之前的全部消息,这样就避免了消息的丢失。

发布了107 篇原创文章 · 获赞 96 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/madongyu1259892936/article/details/99596335