Skip to content

解决重复消费

一、重复消费的成因

重复消费的核心诱因有两类:

  • 网络抖动:消费者处理完消息后,ack回执丢失,MQ未收到确认,消息被重新投递
  • 消费者宕机:消费者处理消息过程中服务宕机,未发送ack,消息重回队列后被重新投递

该问题在Kafka、RabbitMQ、RocketMQ等主流消息中间件中均可能出现。


二、通用解决方案:幂等性设计

核心思路是让同一条消息执行多次,对业务数据的影响和执行一次完全相同,关键步骤:

  1. 为每条消息设置业务唯一标识ID(如支付ID、订单ID、文章ID)
  2. 消费前校验该标识ID是否已被处理:
    • 已处理:直接跳过,不执行业务逻辑
    • 未处理:执行业务逻辑,并记录该标识ID的处理状态

三、常见幂等实现方案

  1. 数据库锁方案:悲观锁、乐观锁
  2. 分布式锁方案:基于Redis、ZooKeeper实现分布式锁,保证同一时间只有一个线程处理该消息
  3. 状态校验方案:通过业务表的状态字段(如订单状态)判断消息是否已处理

四、面试高频问答:悲观锁与乐观锁的区别

  1. 核心思想不同

    • 悲观锁:认为并发冲突大概率会发生,每次操作数据前都先加锁,其他线程阻塞等待
    • 乐观锁:认为并发冲突大概率不会发生,操作数据时不加锁,仅在提交更新时校验是否冲突
  2. 实现方式不同

    • 悲观锁:通过数据库的SELECT ... FOR UPDATE语句实现,查询时直接锁定数据行
    • 乐观锁:通过版本号(version字段)或时间戳实现,更新时判断版本号是否与读取时一致,一致则更新,不一致则重试
  3. 性能表现不同

    • 悲观锁:并发高时会导致大量线程阻塞,性能较差,适合并发冲突频繁的场景
    • 乐观锁:无阻塞等待,性能更高,适合并发冲突少的场景
  4. 典型应用场景

    • 悲观锁:支付扣减库存、订单锁定等强一致性业务
    • 乐观锁:文章点赞、阅读量更新、接口防重提交等并发冲突低的场景

五、面试高频问答:重复消费如何解决?

答: 重复消费的核心解决方案是实现幂等性,通用步骤如下:

  1. 为每条消息设置业务唯一标识ID(如订单ID、支付ID);
  2. 消费前校验该标识ID是否已被处理;
  3. 处理方式可采用数据库锁(悲观锁/乐观锁)、分布式锁或状态校验方案,确保同一消息仅被处理一次。

Powered by VitePress 1.6.4 | 持续更新中