分布式系统幂等性设计
一、重复消费:高并发下的定时炸弹
真相:
- 生产者发送消息后未收到ACK,自动重试。
- 消费者处理完业务但提交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 + 幂等更划算!
** **
五、结语:没有银弹,只有权衡
** **
记住:
- 大厂方案 = 分层防御 + 业务妥协 + 监控兜底。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Calico's Space!
评论