essay

重复消费MQ问题

#mq

避免重复消费MQ消息问题

为什么会出现重复消费MQ消息的问题?首先可以模拟一下的消费的过程:

  1. 消费者处理完 → 手动 ACK 确认
  2. 如果网络超时 / 消费者挂了 / 没来得及 ACK
  3. MQ 认为没消费成功,重新投递(消费者会再次拿到这条消息)
  4. → 重复消费就来了

解决 MQ 消息重复消费问题,核心在于消费者端的幂等性设计。其中幂等性指的是,同样的请求,一次和多次产生的结果是一样的。

可以这样解决:

  1. 数据库唯一索引:

给业务字段建 UNIQUE 唯一索引,比如 orderId,重复插入时数据库直接报错,捕获异常,跳过即可(利用数据库的原生机制)

优点:最简单、不用额外组件、最稳
缺点:只能用于插入场景

  1. 唯一 ID + 分布式锁(Redis):
  • 每条消息带唯一 ID(orderId、msgId)
  • 消费前:SETNX lock:order_1001
  • 拿到锁 → 执行业务
  • 没拿到锁 → 直接丢弃(已经处理过)

这里需要注意的是:一定要使用redis的SETNX操作,因为只有 key 不存在,才会设置成功;key 已存在,直接失败,整个操作是原子性的。
优点:通用、简单、高效
缺点:要引入 Redis

  1. 全局 ID(定位哪一条数据) + 状态机判断(业务逻辑幂等):
    比如:
  • 订单已支付 → 不再支付
  • 用户已创建 → 不再创建
  • 库存已扣 → 不再扣

状态机判断 = 给业务表加状态字段 + 根据状态判断是否可执行

需要注意的是,不要先使用SELECT,UPDATE,直接UPDATE。因为高并发下SELECT和UPDATE并不是同时完成的,而带条件的 UPDATE 本身是原子操作,天然防并发、天然幂等

所以要这样:

UPDATE
SET 状态 = 已处理, 字段 = xxx 
WHERE 全局ID = ? AND 状态 = 待处理

这种方法不需要额外的存储成本,也没有查锁、加锁的开销。

comments如果有不同意见或者补充,直接留在这里。
contact

在别处继续找到我

如果你想聊技术、设计,或者只是打个招呼。

暂未配置外部链接