簡單易懂的冪等(附示例)

什麼是冪等性?

冪等性是分佈式系統和微服務中的一個關鍵概念,尤其是在處理重試和防止重複操作時非常重要。

簡單來說,冪等性意味着無論你發送相同的請求多少次,結果都是一樣的,系統不會執行重複的操作。

對於網絡失敗或超時等情況下非常重要,因爲客戶端可能會重試請求,但我們希望避免同一操作被重複執行。

冪等性的關鍵概念

1. 冪等操作

操作是冪等的,表示重複執行不會產生額外的影響。例如,用相同數據更新資源,或調用創建記錄的接口,不會導致重複記錄的生成。

2. 冪等鍵 (Idempotency Key)

在許多系統中,請求中包含一個唯一標識符(冪等鍵),以確保如果請求被重試,系統能夠識別爲重複操作並跳過執行。冪等鍵通常由客戶端生成。

示例場景

假設在電商系統中進行支付。你發送了一次支付請求,但由於網絡問題,客戶端不確定支付是否成功。

實現冪等性的步驟

  1. 生成冪等鍵

客戶端爲每個請求生成唯一的冪等鍵(如 UUID),並將其包含在請求頭中。示例:

POST /payments
Content-Type: application/json
Idempotency-Key: abc123xyz
  1. 服務端存儲冪等鍵

服務端需要存儲每個冪等鍵及其對應的處理結果,可以使用 Redis、數據庫或其他持久化存儲。

  1. 檢查冪等鍵是否存在
  1. 設置冪等鍵過期時間

冪等鍵應設置合理的過期時間(如 24 小時),避免存儲無限增長。

用 go-zero 實現冪等性

下面以支付服務爲例,用 go-zero 實現冪等性功能。

示例代碼:冪等支付服務

package main

import (
 "errors"
 "fmt"
 "net/http"

 "github.com/zeromicro/go-zero/core/logx"
 "github.com/zeromicro/go-zero/core/stores/redis"
 "github.com/zeromicro/go-zero/core/stringx"
 "github.com/zeromicro/go-zero/rest"
 "github.com/zeromicro/go-zero/rest/httpx"
)

type PaymentRequest struct {
 IdempotencyKey string  `header:"Idempotency-Key"`
 Amount         float64 `json:"amount"`
}

type PaymentResponse struct {
 Message string `json:"message"`
}

type PaymentHandler struct {
 redisClient *redis.Redis
}

func NewPaymentHandler(redisClient *redis.Redis) *PaymentHandler {
 return &PaymentHandler{redisClient: redisClient}
}

func (h *PaymentHandler) Handle(w http.ResponseWriter, r *http.Request) {
 var req PaymentRequest
 if err := httpx.Parse(r, &req); err != nil {
  httpx.Error(w, err)
  return
 }

 // 檢查冪等鍵是否存在
 val, err := h.redisClient.GetCtx(r.Context(), req.IdempotencyKey)
 if err == nil && len(val) > 0 {
  // 如果存在,返回之前的結果
  httpx.OkJson(w, PaymentResponse{Message: val})
  return
 }
 if err != nil {
  logx.Errorf("redis error: %v", err)
  httpx.Error(w, errors.New("internal server error"))
  return
 }

 // 模擬支付處理邏輯
 result := fmt.Sprintf("Payment Successful, amount: %.2f, transaction-id: %s",
  req.Amount, stringx.RandId())

 // 將冪等鍵及結果存入 Redis,設置過期時間爲 24 小時
 err = h.redisClient.Setex(req.IdempotencyKey, result, 24*60*60)
 if err != nil {
  logx.Errorf("Failed to store idempotency key: %v", err)
  httpx.Error(w, errors.New("internal server error"))
  return
 }

 // 返回結果
 httpx.OkJson(w, PaymentResponse{Message: result})
}

func main() {
 // 初始化 Redis 客戶端
 redisClient := redis.New("localhost:6379")

 server := rest.MustNewServer(rest.RestConf{
  Host: "localhost",
  Port: 9999,
 })
 defer server.Stop()

 // 註冊路由和支付處理邏輯
 handler := NewPaymentHandler(redisClient)
 server.AddRoute(rest.Route{
  Method:  http.MethodPost,
  Path:    "/payment",
  Handler: handler.Handle,
 })

 server.Start()
}

代碼關鍵點

  1. Redis 存儲:使用 go-zero 的 redis 客戶端存儲冪等鍵及其對應結果,避免重複執行。

  2. 冪等檢查:接收到請求時,先查詢 Redis 中是否存在冪等鍵,存在則直接返回結果。

  3. 過期時間:爲冪等鍵設置 24 小時過期時間,減少存儲開銷。

測試冪等性功能

1. 首次請求:

curl -X POST http://localhost:9999/payment \
-H "Content-Type: application/json" \
-H "Idempotency-Key: abc123xyz" \
-d '{"amount": 100}'

響應:

{"message":"Payment Successful, amount: 100.00, transaction-id: 1970cd852d278182"}

2. 重複請求:

curl -X POST http://localhost:9999/payment \
-H "Content-Type: application/json" \
-H "Idempotency-Key: abc123xyz" \
-d '{"amount": 100}'

響應:

{"message":"Payment Successful, amount: 100.00, transaction-id: 1970cd852d278182"}

3. 不帶冪等 key 的請求

curl -i -X POST http://localhost:9999/payment \
-H "Content-Type: application/json" \
-d '{"amount": 100}'

響應

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sun, 19 Jan 2025 15:17:50 GMT
Content-Length: 35

field "Idempotency-Key" is not set

總結

冪等性是分佈式系統中處理網絡重試和防止重複執行操作的關鍵。

項目地址

https://github.com/zeromicro/go-zero

歡迎使用 go-zerostar 支持我們!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pZbiIvrtxGNSp7sJyc1Tqg