问题POST /payments 发起一笔支付,网络抖动,客户端 timeout,实际扣款成功但客户端没收到响应。客户端重试 → 扣两次款。GET 天然幂等(查多次结果一样),POST 不是。解决:幂等键 (Idempotency Key)客户端每次请求带一个唯一 key,服务端在某个时间窗口内(比如 24h)看到重复 key 就返回之前的响应,不重复执行。POST /payments Headers: Idempotency-Key: 7c4e8a3b-1234-5678-90ab-cdef12345678 Body: {amount: 100, currency: USD}服务端实现importhashlib,json,redisdefhandle_payment(request):keyrequest.headers.get(Idempotency-Key)ifnotkey:returnerror(Idempotency-Key required for POST /payments),400cache_keyfidem:payments:{key}# 1. 看之前是否处理过cachedredis.get(cache_key)ifcached:cachedjson.loads(cached)returncached[response],cached[status]# 2. 加锁,防止并发重复(同一 key 两次同时到)lock_keyfidem:lock:{key}ifnotredis.set(lock_key,1,ex60,nxTrue):# 锁被别人拿了,等一会儿返回time.sleep(0.5)cachedredis.get(cache_key)ifcached:returnjson.loads(cached)[response],json.loads(cached)[status]returnerror(Conflict, retry),409# 3. 处理请求try:response,statusprocess_payment(request.json)redis.setex(cache_key,86400,json.dumps({response:response,status:status,}))returnresponse,statusfinally:redis.delete(lock_key)5 个关键设计key 必须由客户端生成,不能服务端生成。如果服务端生成,客户端重试时拿到不同的 key,失效。key 跟 request body 绑定。同一 key 不同 body → 409 拒绝(防止 key 复用)。缓存窗口要够长。支付场景至少 24h,让客户端有时间 retry。并发要加锁。同一 key 两个请求同时到,不锁会双扣。副作用已经发生的要明确语义。“已扣款” 的请求 retry 时,返回成功还是已处理?Stripe 选的是后者(更安全)。什么时候需要幂等不是所有 POST 都需要。判断标准:操作有副作用 副作用不可逆。操作需幂等?POST /payments(扣款)✓ 必须POST /refunds(退款)✓ 必须POST /users(创建用户)✓ 建议POST /search(查询)✗ 不需要(查询天然幂等)POST /send-email(发邮件)✓ 必须(防止重发)跟其他机制的关系幂等 vs 重试幂等是重试安全,重试是调用方决定重试。两者配合:客户端 → POST Idempotency-Key 服务端 → 检查 key,执行,缓存响应 客户端 → 超时,自动重试(同一 key) 服务端 → 看到重复 key,直接返回缓存幂等 vs 事务事务保证全做或全不做,幂等保证做一次和做 N 次结果一样。两个不同维度:跨表转账:事务(要么两边都改,要么都不改)重复扣款:幂等(扣一次和扣多次结果一样)一个常见错误:把 Idempotency-Key 当 trace ID两者完全不一样:trace ID:用于日志关联,可以重复,没业务语义Idempotency-Key:用于业务去重,客户端生成,不能复用混用会导致:trace ID 复用 → 业务去重失败 → 重复扣款。我们怎么处理做 SERP API 时,POST /search 接口不需要幂等(查询天然幂等)。但客户后台有批量创建监控任务 这种 POST 接口,我们要求 Idempotency-Key,24h 缓存窗口。不强制也行(返回 warning),但跟金融相关的接口必须强制。小结幂等性的成本:服务端多一个 Redis 查询 一把锁。回报:重试 100% 安全,客户端不用纠结是不是已经处理了。做 API 服务的第一步:把哪些 endpoint 需要幂等列出来。然后强制(关键路径)或建议(辅助路径)使用 Idempotency-Key header。不做幂等的 POST 接口,等于把双扣风险留给客户端。这种接口在生产环境必出事故。SerpBase 实践SerpBase 的 POST 接口都支持 Idempotency-Key header:缓存窗口:24h并发锁:Redis SETNX,60s 过期key 跟 body 绑定:同 key 不同 body 返 409效果:99% 重复请求自动 dedup,客户批处理 自动 retry 安全。*本文来自 SerpBase 工程团队。