essay
重复消费MQ问题
#mq
避免重复消费MQ消息问题
为什么会出现重复消费MQ消息的问题?首先可以模拟一下的消费的过程:
- 消费者处理完 → 手动 ACK 确认
- 如果网络超时 / 消费者挂了 / 没来得及 ACK
- MQ 认为没消费成功,重新投递(消费者会再次拿到这条消息)
- → 重复消费就来了
解决 MQ 消息重复消费问题,核心在于消费者端的幂等性设计。其中幂等性指的是,同样的请求,一次和多次产生的结果是一样的。
可以这样解决:
- 数据库唯一索引:
给业务字段建 UNIQUE 唯一索引,比如 orderId,重复插入时数据库直接报错,捕获异常,跳过即可(利用数据库的原生机制)
优点:最简单、不用额外组件、最稳
缺点:只能用于插入场景
- 唯一 ID + 分布式锁(Redis):
- 每条消息带唯一 ID(orderId、msgId)
- 消费前:SETNX lock:order_1001
- 拿到锁 → 执行业务
- 没拿到锁 → 直接丢弃(已经处理过)
这里需要注意的是:一定要使用redis的SETNX操作,因为只有 key 不存在,才会设置成功;key 已存在,直接失败,整个操作是原子性的。
优点:通用、简单、高效
缺点:要引入 Redis
- 全局 ID(定位哪一条数据) + 状态机判断(业务逻辑幂等):
比如:
- 订单已支付 → 不再支付
- 用户已创建 → 不再创建
- 库存已扣 → 不再扣
状态机判断 = 给业务表加状态字段 + 根据状态判断是否可执行
需要注意的是,不要先使用SELECT,UPDATE,直接UPDATE。因为高并发下SELECT和UPDATE并不是同时完成的,而带条件的 UPDATE 本身是原子操作,天然防并发、天然幂等!
所以要这样:
UPDATE 表
SET 状态 = 已处理, 字段 = xxx
WHERE 全局ID = ? AND 状态 = 待处理这种方法不需要额外的存储成本,也没有查锁、加锁的开销。