計算機科學

什麼是冪等性?為什麼連按兩次付款按鈕也安全

冪等性是指多次送出相同要求時,結果仍然相同的特性。本文整理網路失敗與重試時需要它的原因、GET・PUT・DELETE與POST的差異,以及Idempotency-Key的實作方式。

閱讀 4 分鐘
什麼是冪等性?為什麼連按兩次付款按鈕也安全 封面圖

按下付款按鈕後,畫面卻卡了好一陣子,難免讓人不安。你會想再按一次。

但是按兩次,不就會付款兩次嗎?

設計良好的系統不會。能保證這件事的特性有一個名稱。

就是冪等性。

這個概念會出現在後端文件、HTTP規格和付款API指南中,但定義本身一行就能說完。真正困難的是「為什麼它如此重要」以及「要如何實作」。

本文就來補上這兩個答案。

先看重點整理。

  1. 冪等性:相同要求送出一次或多次,結果都相同的特性
  2. 重要原因:網路會失敗,失敗就需要重試,而要讓重試安全,操作就必須具備冪等性
  3. 在HTTP中,GET・PUT・DELETE具冪等性,POST則不具冪等性
  4. 讓POST安全的實務機制是Idempotency-Key

定義:做很多次,等同於做一次

冪等性一詞源自數學。若某個運算f滿足f(f(x)) = f(x),就稱為冪等。

絕對值函數就是很好的例子。 |−5| = 5,而且 |5| = 5。

套用一次或一百次,結果都相同。

套用到API上,就是這樣。

相同要求送出1次或N次,伺服器狀態都相同。

注意:不代表回應必須相同,而是伺服器狀態必須相同。即使第二次DELETE要求回傳404,只要「該資源不存在」的伺服器狀態相同,就仍具冪等性。

日常例子是電梯按鈕。即使按五次5樓按鈕,電梯也只會前往5樓一次。

反過來,如果是「再上升5層」按鈕,每按一次結果都會不同。前者具冪等性,後者不具冪等性。


為什麼重要:網路一定會失敗

冪等性為什麼重要,用一個情境就能說明。

用戶端送出付款要求,伺服器完成付款處理。

但回應傳回途中,網路中斷了。

用戶端無從得知:要求是在抵達伺服器前失敗(未付款),還是處理完成後只有回應遺失(已付款)。

因為兩種情況看起來都一樣,都是逾時。

使用相同冪等性金鑰重試後仍只處理一次付款的時序圖
回應遺失後,你無法知道付款是否成功。因此需要冪等性。

選擇只有兩個:放棄重試(付款可能未完成),或進行重試(若已完成就會重複付款)。兩個都很糟。

冪等性消除了這個兩難。如果要求具冪等性,「不確定就再送一次」永遠是安全策略。

自動重試、訊息佇列的至少一次傳遞(at-least-once),以及用戶端不斷重新整理,都不再需要害怕。

這就是為什麼每份分散式系統設計文件都反覆寫著:「只對具冪等性的操作設定重試。」


從HTTP方法看冪等性

HTTP規格(RFC 9110)明確定義了各方法的冪等性。

方法 具冪等性? 原因
GET O 查詢不會改變狀態
PUT O 「替換成這個值」做幾次都是相同的值
DELETE O 「刪除它」做幾次都是已刪除狀態
POST X 「建立新的」每呼叫一次就增加一個
PATCH 有條件 「將此欄位設為X」具冪等性,「加1」不具冪等性

PUT與POST的對比是關鍵。PUT指定「結果狀態」,因此具冪等性;POST指示「行為」,因此不具冪等性。

PATCH則取決於內容。

瀏覽器按上一頁回到POST頁面時會警告「要重新提交表單嗎?」,也是因為瀏覽器知道重新送出POST並不安全。


實務機制:冪等性金鑰

然而,註冊會員、建立訂單和付款本質上都是POST。無法完全避免不具冪等性的工作。

因此出現了讓不具冪等性的要求具備冪等性的機制:Idempotency-Key。

運作方式很簡單。

  1. 用戶端為每個要求產生唯一金鑰(通常是UUID),並放入標頭送出
  2. 伺服器第一次看到該金鑰時正常處理,並將回應與金鑰一併儲存
  3. 再次收到相同金鑰時不再處理,直接回傳已儲存的回應

無論是重試還是連按兩次,只要使用相同金鑰,伺服器就只處理一次。Stripe與Toss Payments等付款API實際支援這個標頭,付款整合指南也將它列為必備項目。

用戶端有一個細節要注意:「重試時要使用相同金鑰」。

如果每次重試都產生新金鑰,伺服器就會把它視為不同要求。金鑰應以「相同意圖的要求」為單位建立。

付款終端只處理兩個貼有相同金鑰的信封其中之一,並列印一張收據
收到相同冪等性金鑰時,伺服器只處理一次,並回傳已儲存的回應

總結

  • 冪等性是指無論相同要求送出幾次,伺服器狀態都相同的特性(f(f(x)) = f(x))
  • 網路失敗造成「不知道是否完成」時,只有具備冪等性,重試才會安全
  • HTTP:GET・PUT・DELETE具冪等性,POST不具冪等性,PATCH取決於內容
  • PUT指定結果狀態,因此具冪等性;POST指示行為,因此不具冪等性
  • 使用Idempotency-Key讓不具冪等性的POST具備冪等性——相同金鑰只處理一次
  • 重試時務必重用相同金鑰。金鑰以「相同意圖的要求」為單位