贴吧式
消费端幂等到底该用业务唯一键还是幂等表?两种做法我们吵了三年没结论
帖子正文与楼层回复。
楼主 江砚舟-905
消息中间件幂等Kafka消费端分布式
先说背景:我这边订单履约链路,Kafka 3.5,端到端是 at-least-once,下游消费端必须做幂等,避免重复消费导致重复扣库存、重复发货。团队里两种做法吵了三年,到现在新同学入职还在被问"到底该怎么写幂等",所以想抛出来听听大家的场景。
【两种常见做法】 A. 业务唯一键:把"订单号 + 操作类型"直接做成业务表的唯一索引,重复消费时 insert 撞 DuplicateKey 就吞掉,靠数据库唯一约束兜底。零额外表,逻辑极简。 B. 幂等表:单独一张 t_idempotent(msg_id / trace_id 做唯一键),消费前先 check,没命中再 insert 幂等记录、然后执行业务。语义是"消息级"的,跟业务解耦。
【吵了三年还没对齐的几个分歧点】
- 性能:幂等表多一次查询 + 一次写入,QPS 上万时这是实打实的额外 DB 开销;业务唯一键没有这笔账。
- 耦合:业务唯一键把"幂等"焊死在业务 schema 上。一旦同一业务键要支持多次合法写入(比如一笔订单可多次部分发货、多次退款),唯一键立刻撑不住,得回头改表。幂等表用 msg_id 当键,业务想怎么改 schema 都不用动幂等逻辑。
- 并发窗口:幂等表如果只靠 check-then-insert,两个 consumer 实例同时 check 都没命中、同时 insert,至少一个失败——所以幂等记录必须配合 DB 唯一约束兜底,不能裸 check。这里又回到"到底信不信数据库唯一约束"的问题。
- 原子性:幂等记录和业务写入要不要在同一个本地事务里?跨服务时怎么保证?
【我的倾向与理由】 我默认选"幂等表 + 数据库唯一约束兜底",而不是裸的 check-then-insert;业务唯一键只当作"业务语义上天然唯一"场景(如一笔订单只能发一次货)的加强项,不作为通用手段。理由就一条最硬:幂等表的键是 msg_id/trace_id,语义是"这条消息处理过没有",和业务语义正交,业务演进不用动它;而业务唯一键的约束语义是"业务上的",把两件事混在一个约束里,迟早出事故。至于 check 与 insert 之间的并发窗口,我直接交给 DB 唯一约束去吞 DuplicateKey,不额外上分布式锁。
【想听大家怎么选】
- 你们在什么场景下会直接用业务唯一键、干脆不建幂等表?是业务键天然唯一、还是单纯图省事?
- 多实例消费时,幂等记录和业务写入的原子性你们怎么保证:本地事务、分布式事务,还是单纯靠唯一约束吞异常?
- at-least-once + 幂等表,你们实测下额外 RT / DB QPS 大概涨多少?有没有把幂等记录放 Redis 再落库的折中方案?
- 有没有人踩过"业务唯一键被多次合法写入撑爆"的真实事故?想听听具体是什么业务模型。
欢迎带具体数字和表结构来拍。
赞 0 · 收藏 0 · 楼层 0
楼层
#1
作者
楼层正文
还没有人回复。