消息传递语义

2025年5月14日 | 阅读 5 分钟

Apache Kafka 提供了一个健壮且容错的分布式消息系统,是许多实时数据处理架构中的关键组件。Kafka 中最重要的概念之一是消息传递语义——消息如何传递给消费者。在 Kafka 等分布式系统中,理解并确保消息能够根据应用程序的需求一致、可靠地传递至关重要。

Kafka 提供三种消息传递语义:

  1. 最多一次传递。
  2. 至少一次传递。
  3. 仅一次传递。

每种传递语义都有其优点和权衡,具体选择哪种取决于应用程序的需求。在本详细解释中,我们将介绍 Kafka 消息传递的底层机制,探讨这些语义的工作原理,并为每种类型提供程序化示例。

1. 最多一次传递语义

最多一次传递语义保证消息最多被传递给消费者一次。但这也意味着消息可能根本不会被传递。在这种情况下,如果消息在从 Kafka Broker 获取后但在被消费者成功处理之前发生故障,则可能发生消息丢失。

对于消息丢失可以接受,但不能接受收到重复消息的应用来说,最多一次传递是适用的。例如,发送通知,其中避免垃圾邮件比保证每条通知都已收到更重要。

工作原理

  • 消费者从 Kafka 读取一条消息,并立即提交偏移量(在处理消息之前)。
  • 如果消费者在提交偏移量后未能处理该消息,则该消息将被视为“已处理”,并且不会被重新处理。

示例程序

在下面的代码中,消费者在轮询消息后立即提交偏移量。

输出

Message Delivery Semantics

在此示例中,偏移量会自动提交(ENABLE_AUTO_COMMIT_CONFIG = true),确保即使消息处理失败,Kafka 也不会重新发送该消息。因此,它遵循最多一次语义。

2. 至少一次传递语义

至少一次传递保证消息至少会传递给消费者一次,但在发生故障时可能会被多次传递。这可能导致重复处理消息,但不会丢失任何消息。消费者必须设计为能够处理重复处理。

对于消息丢失不可接受,但可以处理或接受重复消息处理的应用来说,至少一次传递是适用的。例如,在金融交易中,宁愿处理一次交易两次也不愿丢失它。

工作原理

  • 消费者从 Kafka 读取一条消息并进行处理。
  • 消费者在处理消息后提交偏移量。
  • 如果在提交偏移量之前发生消费者故障,则在恢复后将重新传递该消息,这可能会导致消息被处理多次。

示例程序

在此示例中,消费者在处理消息后手动提交偏移量。

输出

Message Delivery Semantics

此处,在消息处理完毕后手动提交偏移量(ENABLE_AUTO_COMMIT_CONFIG = false,并在处理后调用 commitSync())。这确保即使发生故障也不会丢失任何消息,但同时也引入了重复处理的可能性,从而遵循至少一次语义。

3. 仅一次传递语义

仅一次传递是最理想的消息传递形式,它确保每条消息被消费者处理恰好一次。Kafka 引入了幂等生产者事务性消息来实现这一点。

仅一次语义中:

  • Kafka 确保消息既不会丢失也不会被多次处理。
  • 这需要生产者和消费者之间进行额外的协调,并且比其他语义更复杂、资源消耗更大。

仅一次语义对于不允许数据丢失或重复的关键数据管道是理想的,例如金融系统、计费或敏感数据管道。

工作原理

  • 生产者使用幂等写入,确保重试不会导致重复消息。
  • Kafka 事务确保批次中的所有记录都完全且仅处理一次,要么全部成功,要么全部不执行。

实现仅一次传递的关键概念

  1. 幂等生产者: Kafka 允许通过将 enable.idempotence 标志设置为 true 来以幂等方式生产消息。这确保不会产生重复消息。
  2. 事务性生产者: Kafka 允许生产者在事务中写入消息,因此一系列写入可以原子地提交。
  3. 消费者隔离: 消费者仅查看已提交的事务,并避免读取中间(未提交)结果。

示例程序

下面是一个生产者和消费者实现仅一次传递语义的程序。

带事务的幂等生产者

输出

Message Delivery Semantics

消费者隔离

输出

Message Delivery Semantics

ExactlyOnceProducer 中,我们设置 enable.idempotence 并定义了 TRANSACTIONAL_ID_CONFIG,确保生产者使用事务来仅一次地传递消息。ExactlyOnceConsumer 使用 read_committed 隔离级别来避免读取任何未提交的事务,从而确保不消费中间状态。