1. 副本

1.1. 概述

1.2. 失效副本

1.2.1.失效副本判定

从 Kafka 0.9.x 版本开始通过唯一的一个参数 replica.lag.time.max.ms[默认大小为10,000]来控制,当 ISR 中的一个 follower 副本滞后 leader 副本的时间超过参数 replica.lag.time.max.ms 指定的值时即判定为副本失效,需要将此 follower副本 剔出除 ISR 之外。

  • 注意

    在 Kafka 0.9.x 版本之前还有另一个 Broker 级别的参数 replica.lag.max.messages 也是用来判定失效副本的,当一个 follower 副本滞后 leader 副本的消息数超过replica.lag.max.messages 的大小时则判定此 follower 副本为失效副本。

    它与 replica.lag.time.max.ms 参数判定出的失败副本去并集组成一个失效副本的集合,从而进一步剥离出ISR。不过这个 replica.lag.max.messages 参数很难给定一个合适的值,若设置的太大则这个参数本身就没有太多意义,若设置的太小则会让 follower 副本反复的处于同步、未同步、同步的死循环中,进而又会造成ISR的频繁变动。而且这个参数是 Broker 级别的,也就是说对 Broker 中的所有 topic 都生效,就以默认的值4000来说,对于消息流入速度很低的topic来说,比如TPS=10,这个参数并无用武之地;而对于消息流入速度很高的topic来说,比如TPS=20,000,这个参数的取值又会引入ISR的频繁变动,所以从0.9.x版本开始就彻底移除了这一参数

当follower副本将leader副本的LEO(Log End Offset,每个分区最后一条消息的位置)之前的日志全部同步时,则认为该follower副本已经追赶上leader副本,此时更新该副本的lastCaughtUpTimeMs标识。Kafka的副本管理器(ReplicaManager)启动时会启动一个副本过期检测的定时任务,而这个定时任务会定时检查当前时间与副本的lastCaughtUpTimeMs差值是否大于参数replica.lag.time.max.ms指定的值。千万不要错误的认为follower副本只要拉取leader副本的数据就会更新lastCaughtUpTimeMs,试想当leader副本的消息流入速度大于follower副本的拉取速度时,follower副本一直不断的拉取leader副本的消息也不能与leader副本同步,如果还将此follower副本置于ISR中,那么当leader副本失效,而选取此follower副本为新的leader副本,那么就会有严重的消息丢失。

1.3. ISR

分区中所有副本统称为 AR (Assign Replicas).所有和 leader 副本保持一定程度同步的副本[包括leader 副本在内]组成 LSR

LSR 集合是 AR 集合的一个子集

消息会首先发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步,同步期内 follower副本相较 leader副本有一定的滞后,这个滞后在可忍受的滞后范围,这个范围可以通过参数进行配置

与 leader 副本滞后过多的副本组成 OSR

在正常情况下,所有的 follower 副本都应该与 leader副本保持一定程度的同步,即 AR=ISR, OR集合为空

1.3.1. 维护

leader 副本负责维护和跟踪 ISR 集合中所有 follower 副本的滞后状态,当follower副本落后太多或失效时,leader副本会把它从 ISR 集合中剔除。如果 OSR 集合中所有follower副本“追上”了leader副本,那么leader副本会把它从 OSR 集合转移至 ISR 集合。默认情况下,当leader副本发生故障时,只有在 ISR 集合中的follower副本才有资格被选举为新的leader,而在 OSR 集合中的副本则没有任何机会(不过这个可以通过配置来改变)。

1.3.2. ISR 缩减

  • isr-expiration定时任务会周期性的检测每个分区是否需要缩减其ISR集合,当检测到ISR中有是失效的副本的时候,就会缩减 ISR 集合;

  • 将变更后的数据记录到 ZooKeeper 对应 /brokers/topics//partition//state 节点;

  • ISR 集合发生变更时将变更后的数据缓存到 isrChangeSet

  • isr-change-propagation 定时任务会周期性(固定值为2500ms)地检查 isrChangeSet,在 zk 中的 /isr_change_notification 节点下创建 isr_change 开头的持久顺序节点,并保存 isrChangeSet 的数据

  • kafka控制器为 /isr_change_notification 添加了一个 Watcher,当这个节点中有子节点发生变化的时候会触发 Watcher 动作,以此通知控制器更新相关的元数据信息并向它管理的 broker 节点发送更新元数据信息的请求。最后删除 /isr_change_notification 的路径下已经处理过的节点

    • 注意

      频繁的触发 Watcher 会影响 kafka 控制器,zookeeper 甚至其他的 broker 性能。为了避免这种情况,kafka 添加了指定的条件,当检测到分区 ISR 集合发生变化的时候,还需要检查一下两个条件:

      • 上一次 ISR 集合发生变化距离现在已经超过5秒,
      • 上一次写入zookeeper的时候距离现在已经超过60秒。

      满足以上两个条件之一者可以将 ISR 写入集合的变化的目标节点。

1.3.3. ISR 增加

随着 follower 副本不断进行消息同步,follower 副本 LEO 也会逐渐后移,并且最终赶上 leader 副本,此时 follower 副本就有资格进入 ISR 集合,追赶上leader 副本的判定准侧是此副本的 LEO 是否大于等于 leader 副本 HW。更新 ZooKeeper 中的 /broker/topics//partition//state 节点和 isrChangeSet,之后的操作同 ISR 集合的缩减。

1.4. 副本同步

1.4.1. 相关概念

LEO [Log End Offset],标识当前日志文件中下一条待写入的消息的 offset。上图中 offset 为 9 的位置即为当前日志文件的 LEO

LEO 的大小相当于当前日志分区中最后一条消息的 offset 值加 1

分区 ISR 集合中的每个副本都会维护自身的 LEO ,而 ISR 集合中最小的 LEO 即为分区的 HW,对消费者而言只能消费 HW 之前的消息。

HW 是 High Watermark 的缩写,俗称高水位,水印,它标识了一个特定的消息偏移量[Offset],消费者只能拉取到这个 Offset 之前的消息。LSR 队列中的最小 LEO。

Leader副本 发生故障之后,从 LSR 中选出一个新的 Leader副本,为保证多个副本之间的数据一致性,其余的 Follower副本会从各自的日志文件中高于 HW 的部分截掉,然后从新的 Leader 副本同步数据。

未命名

1.4.2. 过程

某个分区有3个副本分别位于 broker0、broker1 和 broker2 节点中,假设 broker0 上的副本1为当前分区的 leader 副本,那么副本2和副本3就是 follower 副本,整个消息追加的过程可以概括如下:

  • 生产者客户端发送消息至 leader 副本中。
  • 消息被追加到 leader 副本的本地日志,并且会更新日志的偏移量。
  • follower 副本(副本2和副本3)向 leader 副本请求同步数据。
  • leader 副本所在的服务器读取本地日志,并更新对应拉取的 follower 副本的信息。
  • leader 副本所在的服务器将拉取结果返回给 follower 副本。
  • follower 副本收到 leader 副本返回的拉取结果,将消息追加到本地日志中,并更新日志的偏移量信息。

某一时刻,leader 副本的 LEO 增加至5,并且所有副本的 HW 还都为0。

之后 follower 副本 向 leader 副本 拉取消息,在拉取的请求中会带有自身的 LEO 信息[这个 LEO 信息对应的是 FetchRequest 请求中的 fetch_offset]。leader 副本返回给 follower 副本相应的消息,并且还带有自身的 HW 信息

follower 副本 各自拉取到了消息,并更新各自的 LEO 为3和4。与此同时,follower 副本 还会更新自己的 HW,更新 HW 的算法是比较当前 LEO 和 leader 副本 中传送过来的 HW 的值,取较小值作为自己的 HW 值。当前两个 follower 副本的 HW 都等于0

接下来 follower 副本 再次请求拉取 leader 副本中的消息。

leader 副本 收到来自 follower 副本 的 FetchRequest 请求,其中带有 LEO 的相关信息,选取其中的最小值作为新的 HW即 min(15,3,4)=3,然后连同消息和 HW 一起返回 FetchResponse 给 follower 副本。 leader 副本 的 HW 是一个很重要的东西,因为它直接影响了分区数据对消费者的可见性。

两个 follower 副本 在收到新的消息之后更新 LEO 并且更新自己的 HW 为3 [min(LEO,3)=3]。

1.4. Leader Epoch

leader epoch 代表 leader 的纪元信息(epoch),初始值为0。每当 leader 变更一次,leader epoch 的值就会加1,相当于为 leader 增设了一个版本号。
每个副本中还会增设一个矢量 <LeaderEpoch => StartOffset>,其中 StartOffset 表示当前 LeaderEpoch 下写入的第一条消息的偏移量。

1.5. 为什么不支持读写分离?

1.6. 日志同步