1. 过滤-去重型(显式挡回重复包)
思路:在业务逻辑之前,先检查"这条请求 / 这条消息是不是第一次见到"。
常见实现
• 数据库唯一索引:INSERT ... ON CONFLICT DO NOTHING
• 去重表 / Redis SET:SADD msgId
看返回值 0/1
• 带 TTL 的布隆过滤器:高 QPS 场景减小内存
优点
• 对已有系统侵入小,只要给消息加 msgId
。
• 过滤失败后可以"啥也不做"直接 ACK,不会二次执行。
缺点
• 需要额外存储去重键,热点写压力大。
• 强制把"是否重复"与"业务逻辑"分离,代码路径分叉。
• 时间窗口过长会造成键无限增长;过短又可能误判"新老交叉"。
适用
支付、扣款、库存扣减等"不能多扣一分钱/一件货"的硬幂等场景。
2. 状态幂等型(让同一操作幂次方 = 1)
思路:业务本身就是一棵状态机,外部多发同一个动作不再产生新副作用。
实现方式
• 不可变资源:PUT /users/42/address
多次覆盖写,最终状态一致。
• CAS / 乐观锁:只在 version=N
时成功;失败说明已经处理过。
• 状态字段:订单 status = PAID
再收到支付回调直接忽略。
优点
• 内部数据结构最简,复用现成行记录。
• 不依赖额外键值表,天然水平扩展。
• 重放/回溯也安全。
缺点
• 并非所有业务都能"覆盖写"------扣减库存、累计积分就不是幂等操作。
• 状态设计稍有不慎会出现"订单半付款"之类边界态。
适用
资源更新(配置中心、用户资料)、软删除标记、流式统计覆盖写。
怎样选
- 一次性副作用(扣钱 / 发货) → 过滤-去重 +/or 事务-原子
- 状态覆盖(用户资料) → 状态幂等