3. Kafka 事务核心概念:事务性生产者、事务协调器、事务日志

Kafka 事务机制是构建端到端精确一次语义(EOS)的基石。它确保跨多个分区和主题的消息写入要么全部成功,要么全部失败,并且不会出现重复或丢失。理解其核心组件——事务性生产者、事务协调器与事务日志——是掌握事务边界的关键。

3.1 事务性生产者(Transactional Producer)

事务性生产者是事务的发起者。与普通生产者不同,它必须调用 initTransactions() 进行初始化,并在发送消息前后使用 beginTransaction() 和 commitTransaction() / abortTransaction() 来界定事务边界。

关键行为: 事务性生产者会为每条消息附加一个单调递增的 生产者 ID(PID) 和 序列号(Sequence Number),Broker 利用这些信息进行幂等去重,确保即使生产者重试,消息也仅被写入一次。

事务性生产者工作流程

  1. 初始化: 调用 initTransactions(),向协调器注册 PID 并获取事务超时时间。
  2. 开启事务: 调用 beginTransaction(),标记事务开始。
  3. 发送消息: 将消息发送到目标主题分区,此时消息处于“未提交”状态,对消费者不可见。
  4. 提交/中止: 调用 commitTransaction() 或 abortTransaction(),协调器将事务结果写入事务日志。
// 事务性生产者示例(Java)
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("transactional.id", "my-transactional-id");
props.put("enable.idempotence", "true");

KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();

try {
    producer.beginTransaction();
    producer.send(new ProducerRecord<>("topic-a", "key1", "value1"));
    producer.send(new ProducerRecord<>("topic-b", "key2", "value2"));
    producer.commitTransaction();
} catch (Exception e) {
    producer.abortTransaction();
} finally {
    producer.close();
}

3.2 事务协调器(Transaction Coordinator)

每个 Kafka Broker 上都运行着一个事务协调器组件。它负责管理事务的生命周期,包括分配 PID、持久化事务状态、处理超时以及执行提交或中止的协调工作。

职责 说明
PID 分配 为每个 transactional.id 分配唯一的 PID,并维护 PID 与事务 ID 的映射关系。
事务状态管理 跟踪事务的当前状态(如 Ongoing、PrepareCommit、CompletedCommit、Aborted)。
事务超时处理 如果事务在超时时间内未完成,协调器主动中止该事务,防止悬挂事务。
提交/中止协调 在提交阶段,协调器向所有参与分区写入事务标记(Commit Marker),通知消费者该事务已提交。
定位方式: 生产者通过 transactional.id 的哈希值计算目标分区,从而找到对应的协调器 Broker。公式为:abs(hash(transactional.id)) % numPartitions,其中分区来自内部主题 __transaction_state。

3.3 事务日志(Transaction Log)

事务日志是一个名为 __transaction_state 的内部主题,默认有 50 个分区,每个分区存储一组事务的元数据。它是事务协调器的持久化存储,确保协调器故障后能够恢复事务状态。

事务日志存储的关键信息

  • 事务 ID 与 PID 映射: 记录每个 transactional.id 对应的 PID 和当前 epoch。
  • 事务状态: 当前事务处于哪个阶段(Empty、Ongoing、PrepareCommit、CompletedCommit、Dead)。
  • 参与分区列表: 该事务涉及的所有主题分区。
  • 最后心跳时间戳: 用于检测事务超时。
注意: 事务日志的副本因子建议设置为 3,且 min.insync.replicas 应至少为 2,以防止协调器故障导致事务状态丢失。如果事务日志丢失,所有未完成的事务将被强制中止。

3.4 事务状态机与核心流程

Kafka 事务遵循严格的状态机转换。下图展示了事务从创建到完成的核心状态流转,以及协调器与生产者之间的交互。

Kafka 事务状态机与核心流程 事务性生产者 事务协调器 事务日志 Ongoing PrepareCommit CompletedCommit initTransactions 持久化状态 commit 写入标记 事务提交流程说明 1. 生产者调用 commitTransaction() 请求协调器。 2. 协调器将状态改为 PrepareCommit,并向所有参与分区写入 Commit Marker。 3. 协调器将最终状态 CompletedCommit 持久化到事务日志,通知生产者提交成功。

3.5 事务边界与幂等性保障

事务机制与幂等性生产者紧密配合。每个事务性生产者都自动启用了幂等性(enable.idempotence=true),通过 PID + 序列号实现去重。事务边界则进一步将多个幂等写入操作原子化。

特性 幂等生产者 事务性生产者
作用范围 单次生产者会话内的单个分区 跨分区、跨主题的多次写入
去重粒度 PID + 序列号 PID + 序列号 + 事务 ID
原子性 不支持 支持(全部成功或全部失败)
消费者可见性 消息立即可见 仅提交后的消息可见
实践建议: 在需要跨分区原子写入的场景(如支付系统、订单状态同步)中,必须使用事务性生产者。对于单分区单消息的写入,幂等生产者已足够,无需引入事务开销。

3.6 事务超时与故障恢复

事务协调器通过 transaction.timeout.ms 参数控制事务的最大存活时间。如果生产者崩溃或网络分区导致事务长时间未完成,协调器会主动中止事务,释放资源。

  • 超时检测: 协调器定期扫描事务日志,检查最后心跳时间戳是否超过超时阈值。
  • 僵尸生产者防护: 每个 transactional.id 关联一个 epoch 计数器。当新生产者实例启动时,epoch 递增,旧实例的请求会被拒绝,防止僵尸生产者写入脏数据。
  • 恢复流程: 协调器故障后,新的协调器通过读取事务日志恢复所有未完成事务的状态,并重新执行超时检测。
事务超时与僵尸生产者防护 正常生产者 僵尸生产者 事务协调器 epoch=5 事务日志 epoch=5 请求(epoch=5) 请求(epoch=4)拒绝 持久化 epoch
关键参数: 建议将 transaction.timeout.ms 设置为 60000(60 秒),既避免事务长时间占用资源,又给正常业务处理留出足够时间。对于长时间运行的事务,生产者应定期调用 sendOffsetsToTransaction() 发送心跳,防止超时。