bigywater.tech
Kafka 内部原理 · 38

消费—处理—再生产如何实现事务一致性

验证一个读—处理—写循环把输出produce和offset提交塞进同一个Kafka事务,模拟处理完但崩溃在提交之前,看重试之后是不是真的只留下一份结果。

-1001
abort后committed offset真实的哨兵值(从未推进)
2条
无论重试几次,输出topic里真实精确的结果条数

验证:读、写、提交offset,用一个事务真实包起来

命令 · python (confluent-kafka) p.begin_transaction() msgs = consumer.poll(...) # 真实读到2条 for m in msgs: p.produce("rpw-output", value=transform(m.value())) p.flush() p.send_offsets_to_transaction( [TopicPartition(m.topic(), m.partition(), m.offset()+1) for m in msgs], consumer.consumer_group_metadata(), ) p.abort_transaction() # 模拟:处理完了,但提交前进程真实崩溃
真实实测 · 本机执行输出
committed offset after abort: -1001 (should still be None/unmoved)
rpw-output content after abort: [] (should be empty, the produce was rolled back too)

-1001是confluent-kafka客户端里"这个分区从来没有提交过offset"的哨兵值。哪怕处理逻辑真实跑完了,写到输出topic的那两条消息也真实发出去了,一旦事务真实没有提交,send_offsets_to_transaction 记的offset和刚才produce的输出消息真实会一起被回滚——输出topic里查不到任何东西,就像这次处理从未发生过。

验证:重启后重新处理同样的输入,这次真实提交

命令 · python consumer2 = Consumer({...}) # 同一个group.id p2.begin_transaction() msgs2 = consumer2.poll(...)
真实实测 · 本机执行输出
re-read messages (should be the SAME order-0/order-1, since offset never advanced):
  ['order-0', 'order-1']

committed offset after commit: 2
rpw-output content after commit:
  ['ORDER-0-PROCESSED', 'ORDER-1-PROCESSED']
  (exactly 2 entries, no duplicates from the aborted round 1 attempt)
因为offset真实从未推进过,重启之后消费者读到的真实是一模一样的两条输入消息,不是下一批——这正是"至少一次读取"本该有的行为。但输出topic里最终只有2条处理结果,不是4条:第一轮那次真实的produce虽然把消息发过去了,但因为从来没有提交事务,那两条输出消息真实和第一轮的offset提交一起被标记成了"未提交",对read_committed的消费者不可见,效果上等同于没发生过。第二轮真实提交成功后,事务日志里才真实留下唯一一份有效结果。

分步演示:读—处理—写循环真实怎么做到只生效一次

每一步都是本文上面两轮真实执行得到的原始结果。

崩溃在提交之前重试一次,输出永远只留下一份
这一步新验证的状态

    结论

    "消费—处理—再生产"这条链路真实的风险点在于:处理输入、写输出、确认已经消费到哪,是三件真实独立的事,任何一步之后崩溃都可能导致"数据丢了"或者"数据被处理了两次却看起来只处理一次"。把输出的produce和consumer的offset提交真实塞进同一个Kafka事务,这三件事就变成了真实同生共死的一个整体:要么全部生效(下游看到处理结果、offset真实往前走),要么全部不生效(下游看不到任何东西、下次重新从原来的offset开始)——没有中间状态。

    参考与说明

    • 数据来自本机3节点Kafka 4.3.1集群,输入topic rpw-input(5条真实预置消息),输出topic rpw-output,事务型producer + send_offsets_to_transaction。
    • 和《Read Committed 和 Read Uncommitted》一起看:那篇看到read_committed如何过滤未提交/已中止的数据,这篇看到这个过滤机制真实被用来实现"处理只生效一次"的效果。
    • 没有验证:真实的Kafka Streams框架内部怎么自动做这套事务包装;多个分区、多个消费者组同时参与同一个事务的场景。
    京ICP备2026067502号-1京公网安备11010502063487号