騰訊電商部門二面:如何保證冪等性?
在日常開發中,我們經常聽到xxx要保證冪等性,在這篇文章中,我們將分析一道騰訊電商部門的二面題:如何保證冪等性?通過本文,我們將揭開冪等性概念的神秘面紗,以及在系統中實施冪等性的最佳做法。
什么是冪等性?
在數學中,若對于某個運算?? 總存在f(f(x))=f(x),則稱該運算為冪等操作,例如,絕對值函數f(x)=∣x∣就是冪等的,下面為兩個冪等性的簡單數學示例:
x + 0;
x = 6;
在第一個示例中,無論執行多少次,加零操作都不會改變結果,在第二個示例中,無論執行該操作多少次,x始終為6,這兩個示例都描述了冪等操作。
在計算機科學中,冪等性(Idempotence)主要用于描述某些操作或函數在多次執行后的效果,具體來說,冪等操作在重復執行多次后,其結果與執行一次的結果相同。這一原則在分布式系統和 API中尤為關鍵,有助于在網絡問題、請求重試或重復請求等情況下保持一致性和可預測性。
為什么需要冪等性?
冪等性在 API中很重要,因為如果網絡中斷,資源可能會被多次調用,在這種情況下,非冪等操作可能會創建額外的資源或意外更改它們,從而導致重大的意外副作用,如果要求數據的準確性時,非冪等性會帶來重大風險。
我們可以想象一下:用戶發送請求,將 1000元從賬戶A轉移到賬戶B,由于網絡延遲或其他因素導致該請求被發送兩次,如果沒有冪等性,API將處理這兩個請求,從而導致 2000元被轉出,這種轉賬體驗肯定是很糟糕的。
因此,冪等性對于確保以下三點至關重要:
- 一致性:即使面臨請求重復或重試,系統也能保持可預測的狀態。
- 錯誤處理:冪等操作簡化了錯誤處理,因為客戶端可以安全地重試請求,而不會產生意外的副作用。
- 容錯:冪等 API 可以更好地應對網絡問題和其他中斷,確保更可靠的用戶體驗。
冪等與安全
“冪等方法”和“安全方法”這兩個概念經常被混淆,安全方法只會讀取,從不寫入,因此它不會更改返回的值,回到我們之前的例子:
x + 0;
x = 6;
- 第一個方法,加零操作,每次都返回相同的值,所以它是冪等的,加零操作對該值本身沒有影響,因此它也是安全的;
- 第二個示例每次都會返回相同的值,因此它是冪等的,但并不安全,因為如果 x在操作運行之前不是 6,它將更改 x的值;
因此,所有安全方法都是冪等的,但并非所有冪等方法都是安全的。
REST中的冪等方法
在 REST API中,常常使用GET、HEAD、PUT、DELETE、OPTIONS 和 TRACE 等方法進行交互,那么它們中間哪些是冪等的?哪些是非冪等的?下面我們將一一介紹。
(1) POST 方法
POST用于將數據提交到指定資源,通常用于在服務器上創建或更新內容,POST 本身并不具有冪等性,這意味著多次發送相同的 POST 請求可能會導致不同的結果或創建提交數據的多個實例。
因此,開發人員在處理 POST請求、實施冪等性密鑰或其他保護措施等機制時需要謹慎,以防止在提交或重試重復請求的情況下產生意外的副作用。
(2) GET方法
GET方法是用于從 Web上獲取特定資源,當客戶端向服務器發送 GET請求時,它要求服務器檢索并返回與指定資源(如網頁、圖像或文檔)關聯的信息。GET請求被設計為只讀,因此它具備冪等性,這意味著對同一資源發出多個相同的 GET請求將產生相同的結果,而不會引起任何額外的更改。
(3) HEAD方法
HEAD用于檢索有關特定資源的元數據,而無需實際下載資源的內容,與 GET 方法類似,HEAD方法用于請求有關資源的信息,但它僅返回 HTTP標頭,其中包括內容類型、內容長度和上次修改日期等信息。當客戶端想要檢查資源是否存在或檢索其元數據而不產生下載整個資源的成本時,此方法特別有用。
HEAD 方法是冪等的,這意味著在同一資源上使用 HEAD的多個請求將產生相同的結果,而不會產生任何副作用。
(4) PUT方法
PUT用于使用客戶端提供的新數據更新或替換服務器上的現有資源。該請求包含一個唯一標識符,例如 URI,服務器使用該標識符來查找目標資源。客戶端還提供一個有效負載,其中包含要應用于資源的更新數據。
PUT 本質上是冪等的,這意味著發出多個相同的 PUT請求將與發出單個請求具有相同的效果。如果指定的 URI中已存在資源,則服務器將用新數據替換它。如果資源不存在,服務器可能會創建它,具體取決于實現。
(5) PATCH方法
PATCH用于部分更新服務器上的資源,與 PUT不同,PATCH僅更改某些資源,它的冪等性取決于操作的實現和語義。
雖然如果以一致和確定性的方式應用更改,則 PATCH 可以是冪等的,但它本質上并不像 GET、PUT 和 DELETE 那樣具有冪等性。在實踐中,通過仔細設計 API和底層操作,可以使 PATCH具有冪等性,確保重復的 PATCH請求產生與單個請求相同的結果。
(6) DELETE方法
DELETE用于從服務器中刪除指定的資源,DELETE方法被認為是冪等的,因為多次執行同一請求會導致相同的結果。在初始成功刪除后,對同一 URI的任何后續 DELETE請求都不應對服務器的狀態產生額外影響,因為資源不再存在。
(7) TRACE方法
TRACE允許客戶端檢索服務器上特定資源的請求和響應消息的診斷表示形式,當客戶端發送 TRACE請求時,服務器會返回整個 HTTP請求(包括標頭)作為響應正文。此方法主要用于調試目的,使開發人員能夠檢查和診斷請求和響應周期中的潛在問題。
TRACE 被認為是冪等的,因為根據設計,它不會更改服務器上的資源或其狀態。當發出多個 TRACE 請求時,服務器將始終如一地返回相同的請求數據,使其成為一個安全且可預測的操作。但是,出于安全考慮,TRACE通常會在 Web 服務器上禁用,以防止潛在的信息泄露。
從上述介紹可以看出:REST API中使用的大多數方法都是冪等的。例外情況是 POST。但是,值得記住的是,如果使用不當,GET 和 HEAD 等其他方法仍然可以以非冪等方式調用。開發人員可以通過遵循 REST原則來避免這種情況。
冪等性的實現方案
實現冪等性的方法因具體應用場景而異,以下是一些常見的冪等性實現方案:
(1) 使用唯一標識符
使用唯一標識符,這種方法常用于API設計中,尤其是金融交易和訂單系統中。
如何使用唯一標識符來保證冪等?下面給出了一個通用的步驟:
- 客戶端在發出請求時生成一個唯一標識符(Idempotency Key);
- 服務器在處理請求時檢查這個唯一標識符是否已經處理過;
- 如果已經處理過,則返回之前的響應,否則,處理請求并存儲響應結果與唯一標識符;
優點:
- 使用唯一標識符,確保每個請求只被處理一次,即使客戶端因為網絡原因重復發送請求。
(2) 數據庫約束
數據庫的唯一約束或主鍵,也是日常開發中用來確保操作冪等性的一種手段。下面也給出了其使用步驟:
- 對于需要冪等的操作,如插入數據,確保插入的數據具有唯一的標識符(如主鍵)。
- 數據庫會自動保證相同的插入操作只執行一次。
優點:
- 數據庫約束,利用數據庫的特性保證冪等性,因此實現比較簡單。
(3) 樂觀鎖
在更新操作中,通常使用樂觀鎖來確保只有在特定條件下才執行更新。使用樂觀鎖保證冪等的步驟為:
- 每次讀取數據時,讀取其版本號。
- 在更新數據時,檢查版本號是否與之前讀取的一致。
- 如果一致,則執行更新并增加版本號,否則,更新失敗,提示重試。
優點:
- 使用樂觀鎖,可以防止并發更新導致的數據不一致問題。
(4) 冪等操作設計
通過設計操作本身的邏輯,使其具有冪等性,其步驟為:
- 確保操作本身是冪等的,例如,將數據更新為特定值而不是增加或減少值。
- 對于刪除操作,可以先檢查資源是否存在,再執行刪除操作。
優點:
- 通過人為設計邏輯確保操作冪等性,簡化了實現過程。
(5) 緩存機制
使用緩存機制存儲請求結果,以防止重復處理,其步驟為:
- 在處理請求之前,檢查緩存中是否已有結果。
- 如果有,直接返回緩存結果;否則,處理請求并將結果存入緩存。
優點:
- 減少重復計算,提升系統性能。
冪等性在實踐中的案例
冪等性在實際應用中有很多成功的案例,以下是幾個典型的應用場景:
(1) 電商系統中的訂單處理
在電商系統中,訂單處理通常包括創建訂單、支付訂單、更新訂單狀態等多個步驟。為了確保系統的一致性和可靠性,這些操作通常設計為冪等的。例如,在支付訂單時,每個支付請求都帶有唯一的交易ID,支付服務通過檢查交易ID來確保每個訂單只被支付一次。
(2) 分布式數據庫的事務處理
在分布式數據庫中,事務處理通常需要跨多個節點進行協調。冪等性可以確保即使同一個事務被多次提交,數據庫的最終狀態也是一致的。例如,在使用分布式事務協議(如二階段提交)時,冪等性可以確保事務的提交和回滾操作在多次執行時不會引起數據的不一致。
(3) 微服務架構中的服務調用
在微服務架構中,服務之間的調用通常通過網絡進行,網絡的不可靠性可能導致請求失敗或超時。通過設計服務的API為冪等的,可以確保服務的調用在重試時不會引起副作用。例如,在用戶服務中更新用戶信息時,通過使用PUT方法并檢查用戶ID,確保更新操作是冪等的。
總結
本文分析了什么是冪等性以及保證冪等的一些常用方法,對于冪等性,一個核心的判斷方式:操作多次和操作一次的結果是不是一樣,如果一樣就冪等,不一樣就不冪等。
在日常開發中,我們一定要注意業務場景是否需要保證冪等,以免增加不必要的副作用。