跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Python 幂等性设计的常见模式

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

相关文章