重试无处不在(移动网络、webhook回放)。高级工程师要会主动设计幂等,而不是寄希望客户端"小心重试"。
方案1:客户端带幂等Key(UUID)在header。服务端存(key→结果)。同Key直接返回缓存。
方案2:业务唯一键DB唯一约束(如out_trade_no)。重复插入抛异常,捕获后当成功处理。
方案3:状态机保护——UPDATE…WHERE status='PENDING'——只有第一次翻状态,后续0行。
方案4:Redis SETNX+TTL当轻量锁——第一个赢,重试看到token直接返回。
方案5:悲观锁(SELECT…FOR UPDATE)锁聚合根。简单但高争用下吞吐掉一大截。
创建订单我会组合两招:客户端在header带Idempotency-Key(SDK生成UUID),orders表这个字段加唯一约束。服务端INSERT…ON CONFLICT DO NOTHING RETURNING *。rows=1是新建、rows=0就SELECT返回已有订单——两条路径都是确定的。Idempotency-Key行有后台任务24小时后清。对下游副作用(扣款、锁库存)再叠状态机——转移条件WHERE status='PREV',重复事件没法二次扣款。看起来双保险,其实链路里每一段都有自己的重试行为,我要系统扛得住它们同时犯病。
别信"客户端说不重试"——网关、LB、用户都会重试。
幂等Key要按用户做隔离——跨用户碰撞是安全漏洞。
即答侠按"成本由低到高"摆这些方案——答出成本意识面试官印象深。
规范上是的,实践中缓存、埋点副作用、读-后-写都能让GET变得不幂等。当作啥都不白给来设计。
幂等=重试安全;恰好一次=副作用只发生一次。后者更强,通常是幂等+去重窗口一起做。