GET因HTTP规范定义为安全幂等方法,只读无副作用;POST语义为创建或触发行为,天然非幂等,需通过Redis SetNX+唯一业务键+数据库唯一索引等主动实现幂等控制。

为什么 POST 请求必须加幂等性,而 GET 不用?
因为 HTTP 规范明确要求 GET 是安全且可缓存的,不产生副作用;但 POST 没有这个约束,重复提交(比如用户连点、网络重试、前端未禁用按钮)会真实执行多次业务逻辑——比如扣款两次、创建两个订单。Go 的 HTTP handler 本身不感知请求是否重复,得靠你自己拦住。
- 幂等性不是“防止并发”,而是“相同输入 → 相同结果”,哪怕执行十次,数据库状态和响应也一致
- 不要依赖客户端传来的
idempotency-key值做简单去重:它可能被篡改、过期、或服务重启后丢失 - 真实场景中,
POST /orders和POST /payments都需要独立的幂等控制,不能共用一套存储
用 Redis 实现 SetNX + 过期时间是最简可行方案
核心是:收到请求时,先用 idempotency-key 尝试写入一个带 TTL 的唯一标记,成功才继续处理;失败则直接返回上次的结果(需提前存好)。
func handleOrder(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("Idempotency-Key")
if key == "" {
http.Error(w, "missing Idempotency-Key", http.StatusBadRequest)
return
}
<pre class="brush:php;toolbar:false;">// 尝试加锁,TTL 设为业务最大处理时间 + 缓冲(如 10 分钟)
ok, err := redisClient.SetNX(r.Context(), "idemp:"+key, "processing", 10*time.Minute).Result()
if err != nil {
http.Error(w, "service unavailable", http.StatusServiceUnavailable)
return
}
if !ok {
// 已存在,查缓存结果
result, _ := redisClient.Get(r.Context(), "idemp:result:"+key).Result()
w.Header().Set("X-Idempotent-Response", "true")
w.Write([]byte(result))
return
}
// 执行业务逻辑(创建订单等)
order, err := createOrder(r)
if err != nil {
// 注意:这里不能直接 return 错误,要先清理标记 or 存失败态
redisClient.Del(r.Context(), "idemp:"+key)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 存结果(成功态),并清理临时标记
data, _ := json.Marshal(order)
redisClient.Set(r.Context(), "idemp:result:"+key, data, 24*time.Hour)
redisClient.Del(r.Context(), "idemp:"+key)}
-
SetNX是原子操作,避免竞态;TTL 必须设,否则失效 key 永久卡住 - 不要用
SET key value EX 3600 NX手写命令,Go Redis 客户端的SetNX方法已封装正确逻辑 - 失败路径里一定要
Del或写失败标记,否则下次请求会永远走“已存在”分支,掩盖真实错误
idempotency-key 的生成不能只靠 UUID
客户端生成的 idempotency-key 如果只是随机 UUID,就无法保证“相同业务意图 → 相同 key”。比如用户重复提交同一笔支付,两次请求 body 几乎一样,但 key 不同,照样扣两次款。
- 推荐服务端从请求体 + 路径 + 关键参数(如
amount,currency,payee_id)计算哈希:sha256(fmt.Sprintf("%s:%s:%d:%s", r.URL.Path, body, amount, payeeID)) - 若前端必须生成,应强制其按规范拼接字段(如
order-create-{user_id}-{timestamp_ms}-{amount}),服务端校验格式 - 绝对不要把敏感字段(如银行卡号)直接进哈希,先脱敏或用 HMAC 加盐
幂等结果缓存必须区分成功与失败态,且带明确过期策略
只缓存成功响应是常见错误。如果第一次请求因库存不足失败,第二次重试仍应返回“库存不足”,而不是重新尝试扣减。
- 在 Redis 中用不同 key 区分:
idemp:result:xxx(成功)idemp:error:xxx(失败,含 status code 和 message) - 失败态也要设 TTL(比如 1 小时),避免永久阻塞重试
- 不要缓存整个
http.ResponseWriter,只缓存业务数据和状态码;响应头(如Set-Cookie)不能复用,每次需重新生成
实际部署时最容易漏的是清理机制:Redis key 不会自动感知业务逻辑变更。比如你改了订单创建逻辑,旧的幂等结果可能已不适用,但还缓存着。上线前得清掉对应前缀的 key,或给 key 加版本号(如 idemp:v2:result:xxx)。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











