极高 进阶
幂等性设计#
一句话答案#
同一操作执行多次结果一致,方案:唯一请求 ID + 去重表、Token 机制、乐观锁 version、业务状态机。
核心要点
常见方案:
| 方案 | 适用 |
|---|---|
| 唯一请求ID + 去重表 | 通用 |
| Token 机制 | 表单提交防重 |
| 乐观锁(version) | 更新操作 |
| 状态机 | 有状态流转的业务 |
数据库层: INSERT ... ON DUPLICATE KEY / 唯一索引
面试回答(2分钟版)
幂等性是指同一个操作执行一次和执行多次产生的效果完全一致。在分布式系统中由于网络重试、MQ重复投递、用户重复点击等场景,幂等性设计非常关键。我总结有四种主要方案。第一是唯一请求ID加去重表,这是最通用的方案,客户端生成全局唯一requestId,服务端在处理前先查去重表判断是否已处理过,可以用Redis的SETNX或数据库唯一索引实现。第二是Token机制,适合表单防重复提交,先从服务端获取一次性Token,提交时携带Token,服务端校验后立即删除,校验和删除必须是一个原子操作(用 DEL 的返回值判断,或 Lua 脚本),否则两个并发请求可能都校验通过。第三是乐观锁,在UPDATE操作中带上version条件,比如UPDATE SET amount=amount-100, version=version+1 WHERE version=当前版本,版本不匹配则更新失败实现幂等。第四是业务状态机,利用状态只能单向流转的特性,比如订单状态从待支付到已支付到已发货,已支付状态下重复的支付请求直接拒绝。需要注意INSERT和a=a+1这类操作天然不幂等,需要通过唯一索引或其他机制显式保证。
追问与易错
追问方向:
- “幂等和去重一样吗?”→ 去重是幂等的一种实现手段(如唯一键去重),但幂等含义更广,状态机(只允许合法状态转换)和乐观锁(version 条件更新)也是幂等
- “乐观锁实现幂等的限制?”→ 只适合更新操作(UPDATE SET version=version+1 WHERE version=X),不适合插入操作;高并发下失败率高需要配合重试机制
- “电商下单链路各环节怎么做幂等?”→ 下单:前端先领 Token 或带客户端生成的 requestId,订单表对订单号/requestId 建唯一索引;支付回调:用状态机条件更新
UPDATE ... SET status=已支付 WHERE id=? AND status=待支付,影响行数为 0 就当重复回调直接返回成功;库存扣减:用WHERE stock>=n条件更新或乐观锁 version,并用「去重表插入 + 扣减」放同一个本地事务防 MQ 重复消费
易错点:
- ❌ 数据库自动保证幂等——INSERT 和 a=a+1 不是幂等的
- ❌ 加了唯一索引就幂等——唯一索引只能直接挡住重复 INSERT;UPDATE/扣款类操作要把「去重表插入」和业务操作放进同一个本地事务
- ❌ Redis SETNX 占位后业务失败不用管——占位 key 不删或不带状态,重试请求会被误判为「已处理」;要记录处理状态(处理中/成功/失败),失败时删除或改状态