一、前言
在上一篇文章里咱们探讨了 kafka 的工作流程、存储机制、分区策略,理分明了生产者生产的数据是怎么存储的以及怎么依据 offset 去查问数据这类问题:Kafka(一):工作流程、存储机制、分区策略
那么 kafka 是怎么保证数据可靠性的呢?怎么保障 exactly once 的呢?在分布式的环境下又是如何进行故障解决的呢?本篇文章咱们就来剖析这个问题。
二、数据可靠性
首先咱们要晓得kafka 发送数据的机制:Kafka 为了保障 producer 发送的数据,能牢靠的发送到指定的 topic,因而 topic 的每个 partition 收到 producer 发送的数据后,都须要向 producer 发送 ack 信息(acknowledgement 确认收到),如果 producer 收到 ack,就会进行下一轮的发送,否则从新发送数据。
2.1、正本数据同步策略
咱们晓得 kafka 的 partition 是主从构造的,因而当一个 topic 对应多个 partiton 时,为了保障 leader 挂掉之后,能在 follower 中选举出新的 leader 且不失落数据,就须要确保 follower 与 leader 同步实现之后,leader 再发送 ack。
大抵图示如下:
这时会产生一个问题也就是正本数据同步策略:多少个 follower 同步实现之后才发送 ack 呢?
有两个计划比照如下:
- 半数以上实现同步,就发送 ack(长处 :提早低; 毛病:选举新的 leader 时,容忍 n 台节点的故障,须要 2n+ 1 个正本)
- 全副实现同步,才发送 ack(长处 :选举新的 leader 时,容忍 n 台节点的故障,须要 n + 1 个正本; 毛病:提早高)
咱们晓得kafka 采纳零拷贝技术优化数据传输,因而网络提早对 kafka 的影响较小。然而因为 kafka 个别都是解决海量数据,在同样为了容忍 n 台节点故障的前提下,第一种计划须要 2n+ 1 个正本,而第二种计划只须要 n + 1 个正本,而 Kafka 的每个分区都有大量的数据,第一种计划会造成大量数据的冗余,因而 kafka 采纳了第二种计划:全副实现同步,才发送 ack。
2.2、ISR
kafka 选用第二种发案来同步正本数据后,可能会呈现一个问题:比方 leader 收到数据,而后开始向所有的 follower 同步数据,然而有那么一个或多个 follower 因为挂掉了之类的起因呈现了故障,不能和 leader 进行同步,那 leader 要始终等上来吗?当然不能够,为了解决这个问题,引入了 ISR 的概念。
ISR 是一个动静的 in-sync replica set 数据集,代表了和 leader 放弃同步的 follower 汇合。
当 ISR 中的 follower 实现数据的同步之后,leader 就会给 follower 发送 ack。如果 follower 长时间未向 leader 同步数据,则该 follower 将被踢出 ISR,该工夫阈值由 replica.lag.time.max.ms 参数设定。Leader 产生故障之后,就会从 ISR 中选举新的 leader。
相当于 leader 只有和 ISR 里的 follower 进行数据同步就能够了,呈现故障的会被 ISR 移出去,复原之后并通过解决还会退出进来。那移出去的 follower 要通过怎么的解决能力重新加入 ISR 呢?能够先思考一下,前面故障解决局部会进行剖析。
2.3、ack 应答机制
因为数据的重要水平是不一样的,有些能够大量容许失落,心愿快一点解决;有些不容许,心愿稳当一点解决,所以没必要所有的数据处理的时候都等 ISR 中的 follower 全副接管胜利。因而 kafka 解决数据时为了更加灵便,给用户提供了三种可靠性级别,用户能够通过调节 acks 参数来抉择适合的可靠性和提早。
acks 的参数别离能够配置为:0,1,-1。
它们的作用别离是:
- 配置为 0:producer 不期待 broker 的 ack,这一操作提供了一个最低的提早,broker 一接管到还没有写入磁盘就曾经返回,当 broker 故障时有可能 失落数据;
- 配置为 1:producer 期待 broker 的 ack,partition 的 leader 写入磁盘胜利后返回 ack,然而如果在 follower 同步胜利之前 leader 故障,那么将会 失落数据;
- 配置为 -1:producer 期待 broker 的 ack,partition 的 leader 和 follower 全副写入磁盘胜利后才返回 ack。然而如果在 follower 同步实现后,broker 发送 ack 之前,leader 产生故障,此时会选举新的 leader,然而新的 leader 曾经有了数据,然而因为没有之前的 ack,producer 会再次发送数据,那么就会造成 数据反复。
三、Exactly Once
3.1、幂等性机制
Kafka 在 0.11 版本之后,引入了幂等性机制(idempotent),指的是当发送同一条音讯时,数据在 Server 端只会被长久化一次,数据不丟不重,然而这里的幂等性是有条件的:
- 只能保障 Producer 在单个会话内不丟不重,如果 Producer 出现意外挂掉再重启是 无奈保障的。因为幂等性状况下,是无奈获取之前的状态信息,因而是无奈做到跨会话级别的不丢不重。
- 幂等性不能跨多个 Topic-Partition,只能保障单个 Partition 内的幂等性,当波及多个 Topic-Partition 时,这两头的状态并没有同步。
3.2、实现 exactly once
个别对于重要的数据,咱们须要实现数据的准确一致性,对于 kafka 也就是保障每条音讯被发送且仅被发送一次,不能反复,这就是 exactly once。咱们通过上篇文章曾经晓得当 acks = - 1 时,kafka 能够实现 at least once 语义,这时候的数据会被至多发送一次。再配合后面介绍的幂等性机制保证数据不反复,那合在一起就能够实现 producer 到 broker 的 exactly once 语义。
它们的关系能够写成一个公式:idempotent + at least once = exactly once
那怎么配置 kafka 以实现 exactly once 呢?
很简略,只需将 enable.idempotence 属性设置为 true,kafka 会主动将 acks 属性设为 -1。
四、故障解决
在剖析故障解决之前,咱们须要先晓得几个概念:
- LEO:全称 Log End Offset,代表每个正本的最初一条音讯的 offset
- HW:全称 High Watermark,代表一个分区中所有正本最小的 offset,用来断定正本的备份进度,HW 以外的音讯不被消费者可见。leader 持有的 HW 即为分区的 HW,同时 leader 所在 broker 还保留了所有 follower 正本的 LEO。
如下图,是一个 topic 下的某一个 partition 里的正本的 LEO 和 HW 关系:
留神:只有 HW 之前的数据才对 Consumer 可见,也就是只有同一个分区下所有的正本都备份实现,才会让 Consumer 生产。
它们之间的关系:leader 的 LEO >= follower 的 LEO >= leader 保留的 follower 的 Leo >= leader 的 HW >= follower 的 HW
因为 partition 是理论的存储数据的构造,因而 kafka 的故障次要分为两类:follower 故障和 leader 故障。
4.1、follower 故障
这部分能够答复后面 2.2 节最初提到的问题:移出去的 follower 要通过怎么的解决能力重新加入 ISR 呢?
通过后面咱们曾经晓得 follower 产生故障后会被长期踢出 ISR,其实待该 follower 复原后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的局部截取掉,从 HW 开始向 leader 进行同步。等该follower 的 LEO 大于等于该分区的 HW(leader 的 HW),即 follower 追上 leader 之后(追上不代表相等),就能够重新加入 ISR 了。
4.2、leader 故障
leader 产生故障之后,会从 ISR 中选出一个新的 leader,之后,为保障多个正本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的局部截掉,而后从新的 leader 同步数据。
留神:这只能保障正本之间的数据一致性,并不能保证数据不失落或者不反复。
那怎么解决故障复原后,数据失落和反复的问题呢?
kafka 在 0.11 版本引入了 Lead Epoch 来解决 HW 进行数据恢复时可能存在的数据失落和反复的问题。
4.3、引入 Lead Epoch
leader epoch 理论是一对值(epoch, offset),epoch 示意 leader 版本号,offset 为对应版本 leader 的 LEO,它在 Leader Broker 上独自开拓了一组缓存,来记录 (epoch, offset) 这组键值对数据,这个键值对会被定期写入一个检查点文件。Leader 每产生一次变更 epoch 的值就会加 1,offset 就代表该 epoch 版本的 Leader 写入的第一条日志的位移。当 Leader 首次写底层日志时,会在缓存中减少一个条目,否则不做更新。这样就解决了之前版本应用 HW 进行数据恢复时可能存在的数据失落和反复的问题
这就有点像 HashMap 源码外面的 modCount,用来记录整体的更新变动。