题目:如何解决消息重复消费、重复下单等问题?
难度:medium
标签:practice/mq, mq/kafka, mq/idempotent
评分:⭐⭐⭐⭐☆(4/5)
类似这种重复性的问题,基本上都是幂等性没有设计好。主要是考虑如何处理对应的幂等性。常用的幂等性处理方案是业务 ID 幂等性,或者是 token 幂等性,然后入参幂等性。针对几种方案进行不同的处理和设计。
首先消息的重复,消息上使用唯一的消息 ID 去确保每个消息只重复消费,不重复消费,只消费一次,消费过就直接过掉,下次来了也不再进行二次消费。基于对消息 ID 的进行去重处理。
重复下单的话,可以涉及两个方向,一个是入参,入参所有入参都是一样的,并且时间间隔很短,这时候可以考虑基于 Redis 进行去重密等,处理对应的重复请求。另外可以在每个请求前给前端一个 Token,唯一的 Token,每个请求都唯一 Token,或者说 UUID,让它去唯一标识这个请求。这时候它请求过来,我之前这个已经使用过了,就不能再次请求,告知它这个请求已经重复了。
那就是基于订单的状态机去进行判定,状态只能从一个状态修改为另一个状态,如果二次修改的话是不能生效的,也可以实现对应的归档。
优点:
不足:
INSERT ... ON DUPLICATE KEY UPDATEUPDATE ... WHERE status = '待支付',依赖唯一索引防并发补充:
UPDATE orders SET status='已支付' WHERE id=? AND status='待支付')核心考点定位:考察"幂等性"在分布式系统中的完整设计——从消息层到请求层到数据层的组合拳。
知识点拆解:
unique key on biz_id),INSERT ... ON DUPLICATE KEY UPDATESET key value NX EX 300,请求前发 token,请求时消耗UPDATE orders SET status='已支付' WHERE id=? AND status='待支付',依赖唯一索引防并发,affected rows = 0 表示重复考察方向:
延伸递进面试题:
【一句话结论】
面试官您好,重复消费和重复下单本质是幂等性问题,MQ 侧靠"业务 ID 去重 + at-least-once 容忍",请求侧靠"Token + 状态机 + 乐观锁"组合拳。
【三点展开】
第一,先理解为什么 MQ 会重复:Kafka/RocketMQ 都是 at-least-once 语义,生产端未收到 ack 就崩溃会重试、Consumer 处理完但未提交 offset 会 Rebalance 重新消费。所以不追求"不重复",而是"重复了也能正确处理"。消息侧用唯一消息 ID 去重,INSERT ... ON DUPLICATE KEY UPDATE 或 Redis SET NX EX 600 记已消费 ID。
第二,请求侧重复下单有三个层次:① 入口层用 Token(Redis SETNX + TTL 5 分钟)防双击;② 业务层用唯一索引(unique key on out_order_no)防重复写入;③ 状态机用乐观锁 UPDATE orders SET status='已支付' WHERE id=? AND status='待支付',affected rows = 0 表示重复。三者组合拳,任一层兜住都不会重复。
第三,跨服务场景用事务消息:RocketMQ 事务消息(半消息 + 本地事务 + 回查)或本地消息表(业务表 + 消息表同事务,定时扫表发 MQ)。这样本地事务和消息发送的原子性才能保证,不会出现"下单成功但通知没发"的中间态。
【一句边界】
当然,幂等设计有个坑:Token TTL 太短会导致重复(Token 过期后客户端重试),太长会导致 Token 泄露被恶意利用。生产上一般是 5-10 分钟,配合限流 + 风控做二次防护。