一、重复消费:高并发下的定时炸弹

图片

真相

  1. 生产者发送消息后未收到ACK,自动重试。
  2. 消费者处理完业务但提交Offset失败,消息被二次投递。
    后果:资金损失、数据错乱、用户投诉!

二、终极方案:四层防御,滴水不漏

图片

1. 生产端:从源头扼杀重复

  • 招式一:唯一ID

    • 每条消息携带业务主键(如订单ID),类似快递单号。
1
Message msg = new Message();  msg.setKey("ORDER_20231111001"); // 唯一标识  
  • 招式二:事务消息(如RocketMQ)

    • 本地事务与消息发送绑定,要么全成功,要么全回滚

2. 消息队列:过滤重复投递

  • Kafka:开启生产者幂等性+事务

1
enable.idempotence=true  transactions.id=my_tx_id  
  • RocketMQ:Broker端根据Message Key去重。

3. 消费端:幂等性设计(核心!)

  • 三大神器

    • 数据库唯一索引:插入前校验主键。
1
INSERT INTO orders (order_id, ...) VALUES ('20231111001', ...);  -- 重复插入直接报错  乐观锁:版本号控制。
    • 乐观锁:版本号控制。
1
if redis.set("lock:ORDER_20231111001", "1", ex=10, nx=True):      process_message()  
    • 分布式锁:Redis锁确保单线程处理。
1
if redis.set("lock:ORDER_20231111001", "1", ex=10, nx=True):      process_message()  

4. 存储层:最后的防线

  • 去重表:记录已处理的消息ID。

图片

三、实战案例:秒杀系统如何抗住10万QPS?

图片

关键点

  • 用户ID+商品ID作为消息Key,天然幂等。

  • Redis锁防止并发扣库存。

四、避坑指南:你以为的“完美方案”可能翻车!

  • Redis锁超时:锁自动释放导致重复消费?

    • 方案:加锁后启动看门狗线程自动续期。
  • 分库分表后唯一索引失效****:

    • 方案:全局唯一ID生成器(如Leaf)。
  • Kafka Exactly-Once成本高

    • 真相:99%场景用At-Least-Once + 幂等更划算!

** **

五、结语:没有银弹,只有权衡

图片

** **

记住

  • 大厂方案 = 分层防御 + 业务妥协 + 监控兜底。