幂等的核心是同一请求(带唯一标识)无论执行多少次,业务状态只变更一次;关键不在不报错,而在不产生副作用,需客户端生成幂等键、服务端校验、DB事务内完成状态变更与幂等校验。
为什么
不能只靠重试加
重试 + 捕获异常看似能防重复,但实际只是掩盖了状态不一致。比如支付接口被调两次,第一次成功扣款但网络超时没收到响应,第二次重试又扣了一次——这不是幂等,是资损。
真正幂等的核心是:**同一请求(带唯一标识)无论执行多少次,业务状态只变更一次**。关键不在“不报错”,而在“不产生副作用”。
必须把幂等键(如
)作为业务逻辑入口参数,不是日志或中间件里偷偷记一笔
幂等键要由客户端生成(如 UUID),服务端只校验、不生成——否则分布式下无法保证跨请求一致性
存储校验点必须和业务更新在同一个事务里(如 PostgreSQL 的
),否则存在竞态窗口
用数据库唯一约束实现最简幂等(Python + SQLAlchemy)
这是中小系统最稳的起点,不依赖外部组件,靠 DB 原子性兜底。重点不是“怎么写 ORM”,而是怎么设计约束字段和事务边界。
常见错误:只在业务表加唯一索引,但没把幂等键和业务主键组合建索引,导致插入时冲突判断失效。
立即学习
“
Python免费学习笔记(深入)
”;
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
下载
幂等键字段类型建议用
(存 UUID)或
,避免截断;不要用
加长度限制后隐式截断
唯一约束必须包含
和业务关键状态字段(如
+
),否则无法区分“同一订单不同操作”
SQLAlchemy 中用
比
更可控——后者可能触发无意义 UPDATE
Django 视图层如何安全接入幂等头(
)
Django 默认不解析这个 header,直接读
就行,但坑在后续链路怎么传、怎么验。
典型翻车:视图里取到 key 后,把它当普通参数传给异步任务(如 Celery),结果任务重试时 key 丢了,或者多个任务并发写同一 key 没锁住。
必须在视图函数开头就校验 key 是否为空或格式非法(如非 UUID),空值直接
,别往后传
如果走异步,key 必须作为任务参数显式传递,且任务函数开头立刻做幂等检查——Celery 的
不会自动带原始 header
不要用 Django cache(如 Redis)单独存 key 状态,除非你同时用 Lua 脚本保证 set+check 原子性;否则
和
之间有竞态
Redis 实现高性能幂等(
)的三个硬限制
Redis 快,但不是万能解药。它适合“纯校验型”幂等(如发短信、发通知),不适合“需回滚”的业务(如库存预占)。
错误用法:用
计数然后判断是否 >1——这无法防止并发写入,两个请求几乎同时
都返回 1。
必须用
(
是关键),value 建议存请求摘要(如
),用于事后审计
过期时间不能拍脑袋定:要大于最长业务耗时(含下游超时重试),但也不能太长(如 7 天),否则 key 泛滥,Redis 内存扛不住
Redis 故障时必须降级策略——比如切到本地内存缓存(
)或直接拒绝(
),绝不能静默跳过幂等检查
事情说清了就结束。幂等不是加个装饰器的事,它是请求生命周期里每个环节对“同一性”的共同承诺。最容易被忽略的,是下游服务(比如发券、调第三方 API)自己有没有幂等能力——你这边拦住了,它那边重复执行,照样出问题。
idempotenttry/exceptidempotency_keyINSERT ... ON CONFLICT DO NOTHINGCHAR(32)TEXTVARCHARidempotency_keyorder_idstatussession.execute(text("INSERT INTO ... ON CONFLICT DO NOTHING"))merge()Idempotency-Keyrequest.META.get("HTTP_IDEMPOTENCY_KEY")return HttpResponse(400)retrygetsetSET key value EX 3600 NXINCRINCRSET key value EX 3600 NXNXhashlib.sha256(body.encode()).hexdigest()lru_cache503 Service Unavailable