这个坑值得记一笔。消费订单事件写 PostgreSQL,先 commit 再处理业务,消费者在提交位移后崩溃,重启从上次位移重放,同一批数据写了两遍,报表金额翻倍。
刚开始盯着「用 at-least-once 语义却按 exactly-once 设计」,折腾了半个多小时,排掉了。
这类报错背后常见的原因有几处,逐个排除就行。位移提交早于业务事务,崩溃后必然重放;业务表没有唯一键,重放直接插入新行;去重表与业务表不在同一个事务里。
先统计重复的订单号与条数
python -c "import psycopg2;c=psycopg2.connect('dbname=app');cur=c.cursor();cur.execute('select order_no,count(*) from orders group by 1 having count(*)>1 limit 10');print(cur.fetchall())"
再往下在业务表加唯一键挡住重放
psql -d app -c 'alter table orders add constraint uk_order unique (order_no)'
随后把位移提交改到业务事务提交之后
python consumer.py --commit-after-tx --batch 200
剩下的就是用同一条消息手工重放验证不再写重
kafka-console-producer --topic orders --bootstrap-server broker:9092 < one.json
别只看报错消失,验证要多做一步。同一消息重放十次后 orders 表仍只有一行,消费位点与处理完成数一致,报表金额与源数据对齐。
提醒一点。去重要么靠唯一键要么靠事务内写去重表,两者都得覆盖业务写入本身,只记录消息 id 而不约束业务表挡不住重放。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。
