kafka09-可靠性探究

1、副本剖析

  • 副本(Replica)是分布式系统中常见的概念之一,指的是分布式系统对数据和服务提供的一种冗余方式。在常见的分布式系统中,为了对外提供可用的服务,我们往往会对数据和服务进行副本处理。
    • 数据副本是指在不同的节点上持久化同一份数据,当某一个节点上存储的数据丢失时,可以从副本上读取该数据,这是解决分布式系统数据丢失问题最有效的手段。
    • 服务副本指多个节点提供同样的服务,每个节点都有能力接收来自外部的请求并进行相应的处理。
  • 组成分布式系统的所有计算机都有可能发生任何形式的故障。一个被大量工程实践所检验过的“黄金定理”:任何在设计阶段考虑到的异常情况,一定会在系统实际运行中发生,并且在系统实际运行过程中还会遇到很多在设计时未能考虑到的异常故障。所以,除非需求指标允许,否则在系统设计时不能放过任何异常情况。
  • Kafka从0.8版本开始为分区引入了多副本机制,通过增加副本数量来提升数据容灾能力。同时,Kafka通过多副本机制实现故障自动转移,即在Kafka集群中某个broker节点失效的情况下仍然可以保证服务可用。
  • 在前面中我们已经简要介绍过副本的概念,并且同时介绍了与副本相关的AR、ISR、HW和LEO的概念,这里简要地复习一下相关的概念:
    • 副本是相对于分区而言的,即副本是特定分区的副本。
    • 一个分区中包含一个或多个副本,其中一个为leader副本,其余为follower副本,各个副本位于不同的broker节点中。只有leader副本对外提供服务,follower副本只负责数据同步。
    • 分区中的所有副本统称为AR,而ISR是指与leder副本保持同步状态的副本集合,当然leade副本本身也是这个集合中的一员。
    • LEO标识每个分区中最后一条消息的下一个位置,分区的每个副本都有自己的LEO,ISR中最小的LEO即为HW,俗称高水位,消费者只能拉取到HW之前的消息。
  • 从生产者发出的一条消息首先会被写入分区的leader副本,不过还需要等待ISR集合中的所有follower副本都同步完之后才能被认为已经提交,之后才会更新分区的HW,进而消费者可以消费到这条消息。

1.1、失效副本

  • 正常情况下,分区的所有副本都处于ISR集合中,但是难免会有异常情况发生,从而某些副本被剥离出ISR集合中。在ISR集合之外,也就是处于同步失效或功能失效(比如副本处于非存活状态)的副本统称为失效副本,失效副本对应的分区也就称为同步失效分区,即under-replicated分区。
  • 正常情况下,我们通过kafka-topics.sh脚本的--under-replicated-partitions参数来显示主题中包含失效副本的分区时结果会返回空。
--正常情况下,返回空
~]# kafka-topics.sh --zookeeper localhost:2181 --describe --topic topic-name --under-replicated-partitions

--将集群中的brokerId为2的节点关闭,再来执行同样的命令,结果显示如下:
~]# kafka-topics.sh --zookeeper localhost:2181 --describe --topic topic-name --under-replicated-partitions
	Topic: topic-name	Partition: 0	Leader: 3	Replicas: 3,1,2	Isr: 3,1
	Topic: topic-name	Partition: 1	Leader: 1	Replicas: 1,2,3	Isr: 1,3
	Topic: topic-name	Partition: 2	Leader: 3	Replicas: 2,3,1	Isr: 3,1
  • 怎么判定一个分区是否有副本处于同步失效的状态呢?
    • Kafka从0.9.x版本开始就通过唯一的broker端参数replica.lag.time.max.ms来执择。
    • 当ISR集合中的一个follower副本滞后leader副本的时间超过此参数指定的值时则判定为同步失败,需要将此follower副本剔除出ISR集合。
    • replica.lag.time.max.ms参数的默认值为30000。
  • 当follower副本将leader副本LEO(LogEndOffset)之前的日志全部同步时,则认为该follower副本已经追赶上leader副本,此时更新该副本的lastCaughtUpTimeMs标识。Kafka的副本管理器会启动一个副本过期检测的定时任务,而这个定时任务会定时检查当前时间与副本的lastCaughtUpTimeMs差值是否大于参数replica.lag.time.max.ms指定的值。具体可以参考图8-1:

  • 注意,千万不要错误地认为follower副本只要拉取leader副本的数据就会更新lastCaughtUpTimeMs。试想一下,当leader副本中消息的流入速度大于follower副本中拉取的速度时,就算follower副本一直不断地拉取leader副本的消息也不能与leader副本同步。如果还将此follower副本置于ISR集合中,那么当leader副本下线而选取此follower副本为新的leader副本时就会造成消息的严重丢失。
  • Kafka源码注释中说明了一般有两种情况会导致副本失效
    • follower副本进程卡住,在一段时间内根本没有向leader副本发起同步请求,比如频繁的Full GC。
    • follower副本进程同步过慢,在一段时间内都无法追赶上leader副本,比如I/O开销过大。
  • 在这里再补充一点,如果通过工具增加副本因子,那么新增加的副本在赶上leader副本之前也都是处于失效状态的。如果一个follower副本由于某些原因(比如宕机)而下线,之后又上线,在追赶上leader副本之前也处于失效状态。
  • 在0.9.x版本之前,Kafka中还有另一个参数replica.lag.max.messages(默认值为4000),它也是用来判定失效副本的,当一个follower副本滞后leader副本的消息数超过replica.lag.max.messages的大小时,则判定它处于同步失效的状态。它与replica.lag.time.max.ms参数判定出的失效副本取并集组成一个失效副本的集合,从而进一步剥离出分区的ISR集合。
  • 具有失效副本的分区可以从侧面反映出Kafka集群的很多问题,毫不夸张地说:如果只用一个指标来衡量Kafka,那么同步失效分区(具有失效副本的分区)的个数必然是首选。

1.2、ISR的伸缩

  • Kafka在启动的时候会开启两个与ISR相关的定时任务,名称分别为"isr-expiration”和"isr-change-propagation"

1.2.1、缩减ISR

  • isr-expiration任务会周期性地检测每个分区是否需要缩减其ISR集合。这个周期是replica.lag.time.max.ms参数大小的一半,默认值为15000ms。当检测到ISR集合中有失效副本时,就会收缩ISR集合。
  • 如果某个分区的ISR集合发生变更,则会将变更后的数据记录到ZooKeeper对应的/brokers/topics/<topic>/partition/<parititon>/state节点中。节点中的数据示例如下:
    • {"controller_epoch":26,"leader":1,"version":1,"leader_epoch":2,"isr":[1,3]}
    • controller_epoch表示当前Kafka控制器的epoch,leader表示当前分区的leader副本所在的broker的id编号,version表示版本号(当前版本固定为1),leader_epoch表示当前分区的leader纪元,isr表示变更后的ISR列表。
  • 除此之外,当ISR集合发生变更时还会将变更后的记录缓存到isrChangeSet中,isr-change-propagation任务会周期性(固定值为2500ms)地检查isrChangeSet,如果发现isrChangeSet中有ISR集合的变更记录,那么它会在ZooKeeper的/isr_change_notification路径下创建一个以isr_change_开头的持久顺序节点(比如/isr_change_notification/isr_change_0000000000),并将isrChangeSet中的信息保存到这个节点中。Kafka控制器为/isr_changenotification添加了一个Watcher,当这个节点中有子节点发生变化时会触发Watcher的动作,以此通知控制器更新相关元数据信息并向它管理的broker节点发送更新元数据的请求,最后删除/isrchangenotification路径下已经处理过的节点。频繁地触发Watcher会影响Kafka控制器、ZooKeeper甚至其他broker节点的性能。为了避免这种情况,Kafka添加了限定条件,当检测到分区的ISR集合发生变化时,满足下面两个条件之一才可以将ISR集合的变化写入目标节点:
    • (1)上一次ISR集合发生变化距离现在已经超过5s。
    • (2)上一次写入ZooKeeper的时间距离现在已经超过60s。

1.2.2、扩充ISR

  • 随着follower副本不断与leader副本进行消息同步,follower副本的LEO也会逐渐后移,并最终追赶上leader副本,此时该follower副本就有资格进入ISR集合。追赶上leader副本的判定准则是此副本的LEO是否不小于leader副本的HW,注意这里并不是和leader副本的LEO相比。
  • ISR扩充之后同样会更新ZooKeeper中的/brokers/topics/<topic>/partition/<parititon>/state节点和isrChangeSet,之后的步骤就和ISR收缩时的相同。

1.2.3、对HW的影响

  • 当ISR集合发生增减时,或者ISR集合中任一副本的LEO发生变化时,都可能会影响整个分区的HW
  • 如图8-2所示,leader副本的LEO为9,followerl副本的LEO为7,而follower2副本的LEO为6,如果判定这3个副本都处于ISR集合中,那么这个分区的HW为6;如果follower3己经被判定为失效副本被剥离出ISR集合,那么此时分区的HW为leader副本和followerl副本中LEO的最小值,即为7。

  • 冷门知识:很多读者对Kafka中的HW的概念并不陌生,但是却并不知道还有一个LW的概念。LW是Low Watermark的缩写,俗称“低水位”,代表AR集合中最小的logStartOffset值。副本的拉取请求(FetchRequest,它有可能触发新建日志分段而旧的被清理,进而导致logStartOffset的增加)和删除消息请求(DeleteRecordRequest)都有可能促使LW的增长。

1.3、LEO与HW

  • 对于副本而言,还有两个概念:本地副本(Local Replica)和远程副本(Remote Replica),本地副本是指对应的Log分配在当前的broker节点上,远程副本是指对应的Log分配在其他的broker节点上。
  • 在Kafka中,同一个分区的信息会存在多个broker节点上,并被其上的副本管理器所管理,这样在逻辑层面每个broker节点上的分区就有了多个副本,但是只有本地副本才有对应的日志。
  • 如图8-3所示,某个分区有3个副本分别位于broker0、brokerl和broker2节点中,其中带阴影方框的表示本地副本。假设broker0上的副本1为当前分区的leader副本,那么副本2和副本3就是follower副本,整个消息追加的过程可以概括如下:
    • (1)生产者客户端发送消息至leader副本(副本1)中。
    • (2)消息被追加到leader副本的本地日志,并且会更新日志的偏移量。
    • (3)follower副本(副本2和副本3)向leader副本请求同步数据,
    • (4)leader副本所在的服务器读取本地日志,并更新对应拉取的follower副本的信息
    • (5)leader副本所在的服务器将拉取结果返回给follower副本。
    • (6)follower副本收到leader副本返回的拉取结果,将消息追加到本地日志中,并更新日志的偏移量信息。

  • 了解了这些内容后,我们再来分析在这个过程中各个副本LEO和HW的变化情况。
    • 下面的示例采用同图8-3中相同的环境背景,如图8-4所示,生产者一直在往leader副本中写入消息。某一时刻,leader副本的LEO增加至5,并且所有副本的HW还都为0。
    • 之后follower副本向leader副本拉取消息,在拉取的请求中会带有自身的LEO信息,这个LEO信息对应的是FetchRequest请求中的fetch_offset。leader副本返回给follower副本相应的消息,并且还带有自身的HW信息,如图8-5所示,这个HW信息对应的是FetchResponse中的high_watermark。
    • 此时两个follower副本各自拉取到了消息,并更新各自的LEO为3和4。与此同时,follower副本还会更新自己的HW,更新HW的算法是比较当前LEO和leader副本中传送过来的HW的值,取较小值作为自己的HW值。当前两个follower副本的HW都等于0(min(0,0)=0)。

  • 接下来follower副本再次请求拉取leader副本中的消息,如图8-6所示。
  • 此时leader副本收到来自follower副本的FetchRequest请求,其中带有LEO的相关信息,选取其中的最小的LEO作为新的HW,即min(15,3,4)=3。然后连同消息和HW一起返回FetchResponse给follower副本,如图8-7所示。
  • 注意,leader副本的HW是一个很重要的东西,因为它直接影响了分区数据对消费者的可见性。
  • 两个follower副本在收到新的消息之后更新LEO并且更新自己的HW为3(min(LEO,3)=3)。

  • 在一个分区中,leader副本所在的节点会记录所有副本的LEO,而follower副本所在的节点只会记录自身的LEO,而不会记录其他副本的LEO。对HW而言,各个副本所在的节点都只记录它自身的HW。
  • 变更图8-3,使其带有相应的LEO和HW信息,如图8-8所示。leader副本中带有其他follower副本的LEO,那么它们是什么时候更新的呢?leader副本收到follower副本的FetchRequest请求之后,它首先会从自己的日志文件中读取数据,然后在返回给follower副本数据前先更新follower副本的LEO。

 

 

  • Kafka的根目录下有cleaner-offset-checkpoint、log-start-offset-checkpoint、recovery-point-offset-checkpoint和replication-offset-checkpoint四个检查点文件。
    • 清理检查点文件cleaner-offset-checkpoint:用来记录每个主题的每个分区中已清理的偏移量。
    • 恢复点文件recovery-point-offset-checkpoint:存放所有分区的LEO。Kafka中会有一个定时任务负责将所有分区的LEO刷写到该文件,定时周期由broker端参数log.flush.offset.checkpoint.interval.ms来配置,默认值为60000。
    • 复制点文件replication-offset-checkpoint:存放所有分区的HW。Kafka中会有一个定时任务负责将所有分区的HW刷写到到该文件,定时周期由broker端参数replica.high.watermark.checkpoint.interval.ms来配置,默认值为5000。
    • 起始点文件log-start-offset-checkpoint:存放所有分区的logStartOffset。Kafka中有一个定时任务来负责将所有分区的logStartOffset写到该文件,定时周期由broker端参数log.flush.start.offset.checkpoint.interval.ms来配置,默认值为60000。
      • log-start-offset-checkpoint文件对应logStartOffset(注意不能缩写为LSO,因为在Kafka中LSO是LastStableOffset的缩写),在FetchRequest和FetchResponse中也有它的身影,它用来标识日志的起始偏移量。各个副本在变动LEO和HW的过程中,logStartOffset也有可能随之而动。

1.4、Leader Epoch的介入

1

#                                                                                                                        #
posted @ 2022-02-23 18:53  麦恒  阅读(33)  评论(0编辑  收藏  举报