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