01 前言
在现代分布式系统中,确保数据处理的准确性和一致性是至关重要的。Apache Kafka,作为一个广泛使用的流处理平台,提供了强大的消息队列和流处理功能。随着业务需求的增长,Kafka 的事务消息功能应运而生,它允许应用程序以一种原子的方式处理消息,即要么所有消息都被正确处理,要么都不处理。本文将深入剖析 Kafka 的 Exactly-Once 语义实现原理,包括幂等性与事务消息的关键概念,以及它们是如何在 Kafka 中实现的。我们将探讨 Kafka 事务的流程,事务提供的 ACID 保证,以及在实际应用中可能遇到的一些限制。无论您是 Kafka 的新手还是经验丰富的开发者,本文都将为您提供有价值的见解和指导。
02 消息队列的事务场景
Kafka 目前用于流处理的场景:相当于一个有向无环图(DAG,Directed acyclic graph)每个节点是一个 Kafka Topic,每条边是一个流处理操作。在这样的场景下,有两种操作:ꔷ 消费上游消息并提交位点ꔷ 处理消息并发送到下游 Topic
对于由这两种操作构成的一组处理流程需要具备事务语义,这样我们就可以不重复(Exactly Once)的处理上游消息并将结果可靠地存储在下游 Topic 中。
上图是一个典型的 Kafka 事务的流程,我们可以看到:MySQL 的 binlog 作为上游数据源将数据写入到 Kafka 中,Spark Streaming 从 Kafka 中读取数据并进行处理,最后将处理结果写入到另外两个 Topic 中(图中三个 Topic 位于同一集群中)。其中消费 Topic A 与写入 Topic B 和 Topic C 的操作具备事务语义。
03 Kafka 的 Exactly Once 语义
从上述的场景中我们可以发现,事务消息最主要的动机是在流处理中实现 Exactly Once 的语义,这可以分为:
ꔷ 仅发送一次: 单分区仅发送一次由生产者幂等保证,多分区仅发送一次由事务机制保证
ꔷ 仅消费一次: Kafka 通过消费位点的提交来控制消费进度,而消费位点的提交被抽象成向系统 topic 发送消息。这就使得发送和消费行为统一起来,只要解决了多分区发送消息的一致性就能实现 Exactly Once 语义
04 生产者幂等性
在创建 Kafka 生产者时设置了 enable.idempotence 参数,用于开启生产者幂等性。
val props = new Properties()
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG