幂等性

发布: (2025年12月19日 GMT+8 03:39)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文。

什么是幂等性?

如果一个操作可以多次执行而不会改变首次执行后的结果,则该操作是幂等的。在 API 的上下文中,这意味着多次进行相同的调用不会对业务层产生任何副作用。

方法幂等描述
GET不应改变状态;多次读取返回相同的资源。
PUT替换资源;重复替换会得到相同的状态。
DELETE删除资源两次会得到相同的结果(资源已不存在)。
POST通常用于创建新资源。如果不加干预,重复的 POST 会导致重复的资源。

为什么系统在没有幂等性的情况下会失败

Failure diagram

幂等键

强制事务一次性处理的标准模式是使用 幂等键

实现工作流

  1. 密钥生成 – 客户端为操作生成唯一标识符(例如 UUID)。
  2. 请求头 – 将该密钥放入自定义请求头(例如 X-Idempotency-Key)。
  3. 服务器端校验 – 如果该密钥已存在于“已处理”缓存中,服务器立即返回缓存的响应。
  4. 处理新密钥 – 若密钥是新的,服务器获取锁,处理请求,并在提交前存储结果。

客户端

// 第一次尝试时生成 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 OK409 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

幂等性是构建弹性、容错分布式系统的主要支柱。通过将去重责任从业务逻辑转移到结构化的架构模式,我们可以消除与双重支付和数据损坏相关的整类错误。

Back to Blog

相关文章

阅读更多 »