目錄
接口冪等性的含義
冪等性原本是數學中的含義,表達式的是N次變換與1次變換的結果相同。
而RESTFul API中的冪等性是指調用某個方法1次或N次對資源產生的影響結果都是相同的,需要特別注意的是:這里冪等性指的是對資源產生的影響結果,而不是調用HTTP方法的返回結果。
舉個例子,RESTFul API中的GET方法是查詢資源信息,不會對資源產生影響,所以它是符合冪等性的,但是每次調用GET方法返回的結果有可能不同(可能資源的某個屬性在調用GET方法之前已經被其他方法修改了)。
實際上,在分布式架構中的API冪等性不僅僅針對RESTFul接口,而是對所有類型的接口適用,目的是為了確保調用1次或N次接口時對資源的影響結果都是相同的。
接口符合冪等性有什么用處
接口的冪等性確保了無論調用1次還是N次對資源的影響都是相同的,這在某些場合下是非常有用的。
舉個例子,有這樣一個接口方法:pay(long account, int money),該方法用於銀行卡扣款支付,參數account為賬戶ID,money為需要扣除的錢數。當用戶從網頁上點擊支付按鈕時,在該方法的實現邏輯中需要從指定賬戶中扣除對應的商品價錢。如果支付操作已經成功執行,但是響應消息因為某種原因未能及時返回給客戶端,這時候給用戶的體驗是可能是未支付成功,如果此時再次點擊支付按鈕,那么將再一次執行該方法,結果可能會導致用戶只買了一件商品卻花了雙份的錢,這當然是不合理的。整個流程如下圖所示:
當然,就上述例子的場景,為了避免用戶重復支付,是可以通過別的方式解決的,比如:分布式事務,或者根據支付狀態提示給予用戶進行提示等等。
但是,如果引入了分布式事務,那么將帶來實現上的復雜性,而且會影響到接口性能;而采取提示信息的方式並不能百分之百確保用戶不會重復支付,存在一定的風險。而如果接口符合冪等性,即:對同一個訂單無論是執行一次支付還是多次支付,在服務端都確保只會扣一次款,那么既不需要引入分布式事務的復雜性,也能從根本上解決重復支付的問題,這也就是接口符合冪等性的價值所在。
總而言之,接口符合冪等性在可以降低系統實現的復雜性,並能保證資源狀態的一致性。
HTTP方法的冪等性與安全性
RESTFul風格的接口設計本質上使用的是HTTP協議方法,因此,RESTFul接口方法的冪等性指的就是HTTP方法的冪等性。
常用的HTTP方法有:OPTIONS(獲取服務器信息),HEAD(請求資源首部信息),GET(獲取資源),POST(創建資源),PUT(更新資源全部信息),PATCH(更新資源部分信息),DELETE(刪除資源)。那么,這些HTTP方法的冪等性又是什么樣的呢?
除了冪等性之外,HTTP方法的安全性是指不對資源產生修改。
如下是常用HTTP方法的冪等性和安全性總結:
HTTP方法名稱 | 是否冪等 | 是否安全 |
---|---|---|
OPTIONS | Y | Y |
HEAD | Y | Y |
GET | Y | Y |
PUT | Y | N |
DELETE | Y | N |
POST | N | N |
PATCH | N | N |
從上述表格中可以看出,HTTP方法的冪等性和安全性並不是同一個概念,如下是對個各個方法的冪等性和安全性解釋:
- OPTIONS方法常常用於獲取服務器信息,不會對資源產生影響,也不會對資源進行修改,因此它是冪等的也是安全的;OPTIONS方法最常見的場景是在瀏覽器的跨域請求中,如果瀏覽器發起的是一個跨域訪問的API(不論是GET方法還是POST方法),再真正發送業務的GET或POST方法之前會發送一個OPTIONS方法從服務端獲取信息,從服務器返回的信息中得知該請求是否支持跨域訪問,從而決定下一步是否能成功發送真正的業務請求。
- HEAD方法用於請求資源的頭部信息,不會資源產生影響,也不會對資源進行修改,因此它是冪等的也是安全的。
- GET方法用於獲取資源信息,雖然可能每次返回的結果都不相同,但是GET方法本身不會對資源產生影響,在RESTFul語義里GET方法也不會修改資源,因此它是冪等的,也是安全的。
- PUT方法在RESTFul語義里表示對資源進行全量更新,因此調用1次或N次的結果都是一致的,所以它是冪等的,但不是安全的。
- DELETE方法用於刪除資源,調用1次或N次的結果都是相同的,因此是冪等的,但不是安全的。
- POST方法在RESTFul語義里表示新建資源,顯然調用1次與調用N次的結果不同(調用1次新建1個資源,調用N次新建N個資源),因此不是冪等的,同時也不是安全的。
- PATCH方法在RESTFul語義里表示對資源的局部更新,因此不能保證調用1次與調用N次的結果相同(如:被更新的資源某個屬性隨着不同的調用次數在變化),所以不是冪等的,同時也不是安全的。
如何設計符合冪等性的接口
設計冪等性接口的關鍵在於保證接口不論是被調用1次還是N次,它對資源所產生的影響都是相同的。
從上述HTTP方法的冪等性總結中可以得知,HTTP協議的POST和PATCH方法都不是冪等性的(但是我們卻經常會在RESTFul接口中使用到它們),那是否就意味中無法將POST和PATCH方法設計為冪等性接口了呢?答案顯然是否定的。在上述例子中,可以將訂單ID也作為方法參數之一,如:pay(long account, int money, long order),這樣在服務端確保一個訂單只會被支付一次(訂單號是全局唯一的),那么無論該方法被調用1次還是N次結果都是一樣的,也就保證了接口的冪等性。當然,在哪些沒有訂單號的場景,可以為接口操作生成一個全局唯一的處理號ID,並把該處理號ID作為方法參數之一,這樣在服務端確保一個處理號ID只會被執行一次就保證了接口的冪等性。
符合冪等性的接口調用流程描述如下圖所示:
寫在最后
雖然說設計符合冪等性的接口在某些場合可以降低系統的復雜性(如:可以不用引入分布式事務),但是並非在所有場合的問題都能通過冪等性接口解決,在必要的時候依然需要引入分布式事務處理這樣的框架。我們不要也不能把接口冪等性作為萬能的解決辦法,但是,我們在設計接口時盡量考慮符合冪等性處理是非常有價值的。
【參考】
http://blog.720ui.com/2016/restful_idempotent/ 如何理解RESTful的冪等性
https://www.cnblogs.com/weidagang2046/archive/2011/06/04/idempotence.html 理解HTTP冪等性
https://sofish.github.io/restcookbook/http methods/idempotency/ RESTful 手冊