幂等性
发布: (2025年12月19日 GMT+8 03:39)
5 min read
原文: Dev.to
Source: Dev.to
请提供您希望翻译的正文内容,我将为您翻译成简体中文。
什么是幂等性?
如果一个操作可以多次执行而不会改变首次执行后的结果,则该操作是幂等的。在 API 的上下文中,这意味着多次进行相同的调用不会对业务层产生任何副作用。
| 方法 | 幂等 | 描述 |
|---|---|---|
| GET | 是 | 不应改变状态;多次读取返回相同的资源。 |
| PUT | 是 | 替换资源;重复替换会得到相同的状态。 |
| DELETE | 是 | 删除资源两次会得到相同的结果(资源已不存在)。 |
| POST | 否 | 通常用于创建新资源。如果不加干预,重复的 POST 会导致重复的资源。 |
为什么系统在没有幂等性的情况下会失败

幂等键
强制事务一次性处理的标准模式是使用 幂等键。
实现工作流
- 密钥生成 – 客户端为操作生成唯一标识符(例如 UUID)。
- 请求头 – 将该密钥放入自定义请求头(例如
X-Idempotency-Key)。 - 服务器端校验 – 如果该密钥已存在于“已处理”缓存中,服务器立即返回缓存的响应。
- 处理新密钥 – 若密钥是新的,服务器获取锁,处理请求,并在提交前存储结果。
客户端
// 第一次尝试时生成 UUID
const idempotencyKey = uuidv4(); // "abc-123-def-456"
// 在请求头中携带密钥发送请求
fetch('/api/orders', {
method: 'POST',
headers: {
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ productId: 42, quantity: 1 })
});
// 重试时(超时/错误),使用相同的密钥
// 该密钥在操作成功或达到最大重试次数之前不会改变
Go 语言服务器端
func HandleCreateOrder(w http.ResponseWriter, r *http.Request) {
idempotencyKey := r.Header.Get("Idempotency-Key")
// 拒绝没有密钥的请求
if idempotencyKey == "" {
http.Error(w, "Idempotency-Key required", http.StatusBadRequest)
return
}
// 检查是否已处理过
cachedResponse, found := checkKeyAlreadyProcessed(idempotencyKey)
if found {
// 返回缓存的响应
w.WriteHeader(http.StatusCreated)
w.Write(cachedResponse)
return
}
// 处理订单
order, err := createOrder(r.Body)
if err != nil {
http.Error(w, "Failed to create order", http.StatusInternalServerError)
return
}
// 存储密钥以供后续重试使用
storeProcessedKey(idempotencyKey, order, 24*time.Hour)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(order)
}
通过实现幂等性,使用相同密钥的多次请求不会产生任何副作用。
战略实施最佳实践
持久层选择
- Redis – 适用于性能。设置 24–48 小时的生存时间(TTL),防止内存增长。
- 关系型数据库 – 适用于严格的一致性。将键与业务逻辑在同一事务中存储,以确保原子性。
确定性响应
幂等重试应返回原始状态码。如果第一次请求返回 201 Created,使用相同键的后续重试也应返回 201 Created,而不是 200 OK 或 409 Conflict。这使客户端逻辑保持简单且一致。
键的作用域
键应以用户或账户为作用域。这可以防止两个不同用户意外生成相同 UUID 的冲突(虽然不太可能,但在多租户环境中仍有可能)。
读‑改‑写问题
一个常见错误是未考虑并发。在高流量系统中,两个相同的请求可能在同一毫秒到达你的 API。
// ❌ CRITICAL BUG: Race Condition
const record = await db.idempotency.find(key);
if (!record) {
// Both Request A and Request B can reach this line simultaneously
await service.processTransaction();
}
通过数据库级别的原子性来修复:
- SQL – 在幂等键列上使用
UNIQUE约束并处理冲突错误。 - Redis – 使用
SET NX命令确保只有一个工作者可以处理该键。 - NoSQL – 使用条件更新或原子 “set‑if‑not‑exists” 操作。
Conclusion
幂等性是构建弹性、容错分布式系统的主要支柱。通过将去重责任从业务逻辑转移到结构化的架构模式,我们可以消除与双重支付和数据损坏相关的整类错误。