介紹
? 什么是等冪(Idempotency)?
等冪
無論這個操作被執行多少次,結果都是一樣的,不會因為多次執行而產生副作用。
通俗一點說:“點一次和點一百次,效果是一樣的。”
? 在接口中,什么是等冪操作?
在 Web / API 開發中,一個 等冪操作的接口,意味著客戶端(用戶、服務、瀏覽器)多次請求同一個接口,結果不變,也不會影響系統的狀態或數據重復修改。
操作 | 等冪性 | 原因 |
---|---|---|
GET /user/123 | ? 是 | 多次獲取用戶信息不改變任何東西 |
DELETE /user/123 | ? 是 | 刪除一次和刪除多次效果一樣,用戶都不存在 |
PUT /user/123 {name: “Tom”} | ? 是 | 每次更新為相同數據,結果一樣 |
POST /user | ? 否 | 每次都會新建一個用戶,重復多次會創建多個資源 |
? PUT 是等冪的,因為它是“更新為某個狀態”
? POST 不是等冪的,因為它每次都是“創建新的資源”
? 為什么等冪很重要?
? 防止重復扣款、重復下單、重復刪除等問題;
? 支持客戶端/中間代理的自動重試;
? 提高系統容錯能力。
? 如何實現接口等冪?
常見做法有:
-
冪等鍵(Idempotency Key):客戶端每次請求都帶上一個唯一 key,服務端緩存這個 key,避免重復處理。例如支付場景就常用這個機制。
-
根據業務設計邏輯保證冪等:比如數據庫 INSERT 改為 UPSERT(存在則更新,不存在則插入)。
-
冪等性中間件 / 請求鎖定機制:防止重復請求在短時間內被處理多次。
處理PUT的等冪
多次調用會修改 update_time、產生日志、觸發 webhook、更新緩存等等副作用。
理論上:
? PUT /resource/123 的語義是:把這個資源更新成某個固定狀態。
? 所以連續多次執行 PUT(用相同數據),最終資源狀態是一致的 —— 這是“等冪”。
實際上:
? 即使數據一樣,每次 PUT 可能都會執行:
? 自動更新時間戳(update_time)
? 寫數據庫變更日志
? 寫操作審計表
? 發送消息到 MQ
? 清理或更新緩存
? 這些副作用就
破壞了等冪性
👉 方式一:判斷數據是否變更
if new_data != old_data:do_update()
? 如果數據一樣,直接跳過寫入、跳過更新時間戳等。
? 這種方式最簡單,適合“頻繁重復 PUT”的場景。
👉 方式二:允許更新,但保持副作用冪等
? 比如:
? update_time 只在數據真正變更時更新;
? 日志、MQ 消息僅在內容變更時才觸發;
? 或使用冪等鎖 + 緩存處理。
👉 方式三:使用冪等 key(更適合 POST)
比如前端在 10s 內重試,帶上冪等 key,服務端只處理一次。
? 針對 update_time 的建議做法:
def update_user(user_id, new_data):old_data = db.get_user(user_id)if new_data == old_data:return # 數據沒變,不做更新new_data["update_time"] = now()db.update_user(user_id, new_data)
冪等鎖+緩存處理
這是用于更復雜、可能存在并發請求或前端重復請求場景的策略。
📌 場景:
假設你的接口會被短時間內 多次調用(并發 or 重試):
PUT /order/123/status {"status": "paid"}
應該避免:
? 用戶一不小心連點了 2 次;
? 前端接口設置了“自動重試機制”;
? 網關/中間層產生了重復調用。
? 解決方式:使用「冪等鎖」
-
給每次請求生成一個冪等 key(比如前端傳遞一個唯一 idempotent-key);
-
使用緩存(Redis)記錄這個 key 的處理狀態;
-
判斷是否已經處理過,如果是,就跳過執行邏輯。
# 接收到請求
key = f"idempotent:{user_id}:{operation_id}"
if redis.exists(key):return "已處理,直接返回"# 設置鎖,有效期60秒
redis.set(key, "processing", ex=60)try:# 執行更新操作db.update_order_status("paid")mq.send("order.paid")write_log("用戶付款成功")
finally:redis.delete(key)
等冪鎖流程
? 冪等鎖的完整流程設計(前后端協作)
🔸 適用場景:
? 用戶發起重要操作,如:下單、支付、扣積分、修改狀態
? 你希望避免:
? 用戶手抖點兩下
? 前端接口自動重試
? 網關中轉多次
? 并發執行相同邏輯導致“重復創建 / 重復扣款”
? 正確的冪等鎖邏輯應該是:
? 「令牌模式」冪等方案
后端生成 冪等 key,前端持有這個 key,之后帶著這個 key 去執行冪等請求。
模式 | 說明 | 適合場景 |
---|---|---|
令牌模式 | 前端先拿一個冪等 key(令牌),再帶著 key 去調接口 | 用戶主動操作型,如“提交訂單” |
前端生成UUID | 前端自己生成 UUID,當做冪等 key 發請求 | 自動重試 / 服務間調用 場景 |
令牌方式:
? 冪等 key 生命周期完全由后端掌控 ?;
? 安全性高,不依賴前端生成 uuid 的正確性 ?;
? 能配合業務類型(如創建訂單、支付、退款)做細粒度控制 ?;
? 可以直接緩存執行結果,實現「重復請求 → 直接返回結果」 ?;
維度 | 你說的方式(后端生成) | 之前的方式(前端生成) |
---|---|---|
控制權 | 在后端 ? | 在前端 ? |
安全性 | 更高 ? | 依賴前端或外部系統 |
結果緩存 | 容易實現 ? | 實現麻煩 ? |
實現復雜度 | 多一步(key申請) | 略簡單 |
應用場景 | 提交表單、支付類接口 | 微服務調用、冪等補償邏輯 |
冪等令牌機制”,非常適合處理「用戶主動操作 + 嚴格控制重復」的接口,強烈推薦在 支付、下單、扣款等業務中使用。
注意:
? 前端生成 UUID 并不是簡單「純隨機」,它要遵循 可識別性 或 可復用性 的規則,才能讓后端識別同一個操作。
? 正確的前端 UUID 冪等 key 設計方案
🔸 前提場景適用:
? 后端不參與冪等 key 生成(例如微服務架構中,一些服務沒有共享 Redis)
? 前端或調用方能控制 key 生成(如 App / 網關 / API 調用方)
? 通常用于:接口重試、任務去重、上傳文件避免重復入庫等
需要生成的 UUID 實際是“結構化唯一 key”,并不是完全隨機,比如:
idempotent:<業務類型>:<用戶ID>:<業務ID或時間戳>
業務場景 | 冪等 key 示例 |
---|---|
下單 | idemp:order:uid123:order456 |
修改用戶信息 | idemp:user:update:uid123 |
提交問卷 | idemp:survey:uid123:survey_20240330 |
提交任務(時間窗口內) | idemp:task:uid123:20240330T10 |
Key 設計 | 是否推薦 | 原因 |
---|---|---|
完全隨機 UUID | ? 不推薦 | 后端無法判斷是否是重復操作 |
帶業務結構的 key | ? 推薦 | 可以判斷“是不是相同操作” |
后端統一分發 key | ? 更推薦 | 更安全、更集中控制 |
?
與其在前端搞業務 ID / 時間戳構造 key,不如直接用令牌機制:后端統一生成并下發冪等 key,前端拿著用。
對比點 | 時間戳方案(前端方案) | 令牌機制(后端) |
---|---|---|
冪等 key 來源 | 前端生成時間戳 | 后端統一生成 |
是否真正冪等 | ? 多次執行都不同 | ? 同一個 key 保證只執行一次 |
是否能緩存結果 | ? 難以復用 | ? 可直接返回上次執行結果 |
實現復雜度 | 中(要設計 key 格式) | 高一點(需要額外接口) |
安全性 & 可控性 | ? 前端失控 | ? 后端控制 |