Kafka 数据一致与可靠性保证 (下篇)
前言
在上篇中着重讲了 ACK,ISR,Exactly Once 和 事务性等机制来保证 producer 端发送至 broker 的数据不丢失不重复,而本篇会介绍 broker 端和 consumer 端保证数据一致性与可靠性的一些机制,文中如有纰漏请不吝指正,共同进步!
HW & LEO
HW(High Watermark) 和 LEO(Log End Offset) 是 Kafka 中保证分区数据对消费者而言始终一直的特性,我们先不管这俩个名词,先来看看去除这俩个机制的情况下 consumer 消费 partition 中数据会出现的问题。
在 Kafka 中同一个 partition 下会有一个 leader 和 多个 follower,而 producer 和 consumer 对于数据的所有操作都是与 leader 通讯的,因此 leader 副本中的数据一定是最新的相对的 follower 副本的数据会在一定程度上落后与 leader 副本如下图,在一开始我们的 consumer 与 leader 通讯不不断的消费消息,但是当消费到图中红色消息时 leader 节点挂了,不过好在有 follower 来进行故障转移因此我们的 follower2 就称为了这个分区的新 leader 但此时问题就出现了因为 leader 的数据是领先于所有 follower 的而之前 consumer 已经消费到了红色的消息但新的 leader 这还没同步到因此 consumer 的下一条消费的消息就会重复消费,此外可以看到 follower1 的数据也领先 new leader 两条,因此当新的数据到达后 new leader 和 follower1 在之后的两个位置上会出现数据不一致的情况。
上述的情况显然不是我们希望看到的,由此 Kafka 就使用了开头提及的两个机制 HW 和 LEO 来防止这种情况的出现,HW 用于标记整个 partition 中对于消费者可见的最大数据 offset 而 LEO 则标记了当前副本数据的最大 offset,HW 即为 ISR 中最小的 LEO 值,可见下图来帮助理解。
LEO 的作用很好理解即标识当前副本新增数据的 offset 其一定程度上来说不参与数据的一致性保证,因此我们着重来看 HW 的作用,其可以在故障发生时防止上述两个情况的发生,我们分别来看:
-
防止数据重复消费
这个很好理解因为消费者所能消费的数据一定不会超过 HW 即 ISR 中最落后的进度因此即使新的 leader 是最落后的 follower 节点也不会出现之前所说的重复消费的情况
-
防止数据不一致
在 Leader 发生故障后会选举出一个新的 leader,而新的 leader 不一定是整个 ISR 中消息进度最快的那个,就如我们之前所描述的那种情况,但是 HW 标记之前的数据一定是 ISR 中所有副本都具有的,因此新的 leader 会发出一条指令让所有的 follower 从HW 开始将 log 日志截断即超出 HW 部分的数据全都丢弃,这样 ISR 中的数据一致性就得到了保证,但是可以看到这种方法保证了一致性但是数据截断并不能保证数据的不丢失。
Offset
由于 kafka 是一个数据持久化的消息队列因此 consumer 端并不需要在故障时担心数据的丢失,不过数据的重复消费是 consumer 需要解决的一个问题,而在 kafka 中 consumer 通过维护 offset 来标识已经消费完成的数据并会从 offset 位置开始新的消费,offset 这个词在前文就有提到过 HW 是一个 offset LEO 同样也是一个 offset,总的来说在一个 log 日志内 kafka 存在四种 offset:
- High Watermark:即前文介绍的 HW 其标志的 consumer 所能看见的最大数据 offset
- Log End Offset:即前文介绍的 LEO 其标识了当前副本最大的数据 offset,如果是 leader 副本 HW 至 LEO 之间的数据为已经写入 leader 但还未完全同步至 ISR 的数据
- Last Commited Offset:消费者组(consumer group) 最后一次提交的 offset 代表此 offset 之前的数据已经被当前消费者组消费完成
- Current Position:当前消费者组正在处理(可能已经完成一部分消费) 但还未提交 offset 的数据
在 Last Commited Offset 和 Current Position 之间的数据在 consumer 从故障中恢复后可能出现重复消费的情况。例如:在消费完成 消息2 之后出现了故障等待从故障中恢复后会从最后提交的 offset 处继续消费即 消息1 处于是便出现重复消费的情况。解决这一问题的方法就是在消费者 API 中选择手动提交 offset 而不使用 kafka 默认的自动提交 offset,因为自动提交的 kafka 根据时间来判断是否提交的在实际开发中无法把握提交实际,而选择手动提交那么就可以在拉取一批数据并在处理成功后再进行提交,期间如果发生了故障可以将已经处理的数据全部退回这样就可以有效的避免重复消费的情况出现。