← 每日面试题 学习看板

如何解决消息重复消费、重复下单等问题?

面试题/01实践场景题/实践场景题目/消息队列与异步通信/如何解决消息重复消费、重复下单等问题?.md

面试记录 2026-10-14

题目:如何解决消息重复消费、重复下单等问题?

难度:medium

标签:practice/mq, mq/kafka, mq/idempotent

评分:⭐⭐⭐⭐☆(4/5)

我的回答

类似这种重复性的问题,基本上都是幂等性没有设计好。主要是考虑如何处理对应的幂等性。常用的幂等性处理方案是业务 ID 幂等性,或者是 token 幂等性,然后入参幂等性。针对几种方案进行不同的处理和设计。

首先消息的重复,消息上使用唯一的消息 ID 去确保每个消息只重复消费,不重复消费,只消费一次,消费过就直接过掉,下次来了也不再进行二次消费。基于对消息 ID 的进行去重处理。

重复下单的话,可以涉及两个方向,一个是入参,入参所有入参都是一样的,并且时间间隔很短,这时候可以考虑基于 Redis 进行去重密等,处理对应的重复请求。另外可以在每个请求前给前端一个 Token,唯一的 Token,每个请求都唯一 Token,或者说 UUID,让它去唯一标识这个请求。这时候它请求过来,我之前这个已经使用过了,就不能再次请求,告知它这个请求已经重复了。

那就是基于订单的状态机去进行判定,状态只能从一个状态修改为另一个状态,如果二次修改的话是不能生效的,也可以实现对应的归档。

点评

优点:

不足:

补充:

完整讲解

第一部分:知识重点剖析

核心考点定位:考察"幂等性"在分布式系统中的完整设计——从消息层到请求层到数据层的组合拳。

知识点拆解:

  1. MQ 重复的根本原因:
  1. 幂等性三种实现:
  1. 状态机 + 乐观锁:UPDATE orders SET status='已支付' WHERE id=? AND status='待支付',依赖唯一索引防并发,affected rows = 0 表示重复
  2. 事务消息方案:
  1. 降级策略:Redis 不可用时降级到本地内存 Map + 数据库唯一索引兜底

第二部分:考察方向与延伸递进

考察方向:

延伸递进面试题:

  1. RocketMQ 事务消息的三阶段具体是什么?
  1. 本地消息表怎么保证消息不丢不重?
  1. Token 幂等和状态机怎么选?

第三部分:面试者参考回答(三步法口语化,300-400 字)

【一句话结论】

面试官您好,重复消费和重复下单本质是幂等性问题,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 分钟,配合限流 + 风控做二次防护。