冪等性定義 程序員
本文所要探討的正是HTTP協議涉及到的一種重要性質:冪等性(Idempotence)。在HTTP/1.1規範中冪等性的定義是: 瀏覽器
Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. 服務器
從定義上看,HTTP方法的冪等性是指一次和屢次請求某一個資源應該具備一樣的反作用。冪等性屬於語義範疇,正如編譯器只能幫助檢查語法錯誤同樣,HTTP規範也沒有辦法經過消息格式等語法手段來定義它,這多是它不太受到重視的緣由之一。但實際上,冪等性是分佈式系統設計中十分重要的概念,而HTTP的分佈式本質也決定了它在HTTP中具備重要地位。 網絡
分佈式事務 vs 冪等設計 架構
爲何須要冪等性呢?咱們先從一個例子提及,假設有一個從帳戶取錢的遠程API(能夠是HTTP的,也能夠不是),咱們暫時用類函數的方式記爲: 分佈式
bool withdraw(account_id, amount) ide
withdraw的語義是從account_id對應的帳戶中扣除amount數額的錢;若是扣除成功則返回true,帳戶餘額減小amount;若是扣除失敗則返回false,帳戶餘額不變。值得注意的是:和本地環境相比,咱們不能輕易假設分佈式環境的可靠性。一種典型的狀況是withdraw請求已經被服務器端正確處理,但服務器端的返回結果因爲網絡等緣由被掉丟了,致使客戶端沒法得知處理結果。若是是在網頁上,一些不恰當的設計可能會使用戶認爲上一次操做失敗了,而後刷新頁面,這就致使了withdraw被調用兩次,帳戶也被多扣了一次錢。如圖1所示: 函數
圖1 工具
這個問題的解決方案一是採用分佈式事務,經過引入支持分佈式事務的中間件來保證withdraw功能的事務性。分佈式事務的優勢是對於調用者很簡單,複雜性都交給了中間件來管理。缺點則是一方面架構過重量級,容易被綁在特定的中間件上,不利於異構系統的集成;另外一方面分佈式事務雖然能保證事務的ACID性質,而但卻沒法提供性能和可用性的保證。 性能
另外一種更輕量級的解決方案是冪等設計。咱們能夠經過一些技巧把withdraw變成冪等的,好比:
int create_ticket()bool idempotent_withdraw(ticket_id, account_id, amount)
create_ticket的語義是獲取一個服務器端生成的惟一的處理號ticket_id,它將用於標識後續的操做。idempotent_withdraw和withdraw的區別在於關聯了一個ticket_id,一個ticket_id表示的操做至多隻會被處理一次,每次調用都將返回第一次調用時的處理結果。這樣,idempotent_withdraw就符合冪等性了,客戶端就能夠放心地屢次調用。
基於冪等性的解決方案中一個完整的取錢流程被分解成了兩個步驟:1.調用create_ticket()獲取ticket_id;2.調用idempotent_withdraw(ticket_id, account_id, amount)。雖然create_ticket不是冪等的,但在這種設計下,它對系統狀態的影響能夠忽略,加上idempotent_withdraw是冪等的,因此任何一步因爲網絡等緣由失敗或超時,客戶端均可以重試,直到得到結果。如圖2所示:
圖2
和分佈式事務相比,冪等設計的優點在於它的輕量級,容易適應異構環境,以及性能和可用性方面。在某些性能要求比較高的應用,冪等設計每每是惟一的選擇。
HTTP的冪等性
HTTP協議自己是一種面向資源的應用層協議,但對HTTP協議的使用實際上存在着兩種不一樣的方式:一種是RESTful的,它把HTTP當成應用層協議,比較忠實地遵照了HTTP協議的各類規定;另外一種是SOA的,它並無徹底把HTTP當成應用層協議,而是把HTTP協議做爲了傳輸層協議,而後在HTTP之上創建了本身的應用層協議。本文所討論的HTTP冪等性主要針對RESTful風格的,不過正如上一節所看到的那樣,冪等性並不屬於特定的協議,它是分佈式系統的一種特性;因此,不管是SOA仍是RESTful的Web API設計都應該考慮冪等性。下面將介紹HTTP GET、DELETE、PUT、POST四種主要方法的語義和冪等性。
HTTP GET方法用於獲取資源,不該有反作用,因此是冪等的。好比:GET http://www.bank.com/account/123456,不會改變資源的狀態,不論調用一次仍是N次都沒有反作用。請注意,這裏強調的是一次和N次具備相同的反作用,而不是每次GET的結果相同。GET http://www.news.com/latest-news這個HTTP請求可能會每次獲得不一樣的結果,但它自己並無產生任何反作用,於是是知足冪等性的。
HTTP DELETE方法用於刪除資源,有反作用,但它應該知足冪等性。好比:DELETE http://www.forum.com/article/4231,調用一次和N次對系統產生的反作用是相同的,即刪掉id爲4231的帖子;所以,調用者能夠屢次調用或刷新頁面而沒必要擔憂引發錯誤。
比較容易混淆的是HTTP POST和PUT。POST和PUT的區別容易被簡單地誤認爲「POST表示建立資源,PUT表示更新資源」;而實際上,兩者都可用於建立資源,更爲本質的差異是在冪等性方面。在HTTP規範中對POST和PUT是這樣定義的:
The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. ...... If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.
POST所對應的URI並不是建立的資源自己,而是資源的接收者。好比:POST http://www.forum.com/articles的語義是在http://www.forum.com/articles下建立一篇帖子,HTTP響應中應包含帖子的建立狀態以及帖子的URI。兩次相同的POST請求會在服務器端建立兩份資源,它們具備不一樣的URI;因此,POST方法不具有冪等性。而PUT所對應的URI是要建立或更新的資源自己。好比:PUT http://www.forum/articles/4231的語義是建立或更新ID爲4231的帖子。對同一URI進行屢次PUT的反作用和一次PUT是相同的;所以,PUT方法具備冪等性。
在介紹了幾種操做的語義和冪等性以後,咱們來看看如何經過Web API的形式實現前面所提到的取款功能。很簡單,用POST /tickets來實現create_ticket;用PUT /accounts/account_id/ticket_id&amount=xxx來實現idempotent_withdraw。值得注意的是嚴格來說amount參數不該該做爲URI的一部分,真正的URI應該是/accounts/account_id/ticket_id,而amount應該放在請求的body中。這種模式能夠應用於不少場合,好比:論壇網站中防止意外的重複發帖。
總結
上面簡單介紹了冪等性的概念,用冪等設計取代分佈式事務的方法,以及HTTP主要方法的語義和冪等性特徵。其實,若是要追根溯源,冪等性是數學中的一個概念,表達的是N次變換與1次變換的結果相同,有興趣的讀者能夠從Wikipedia上進一步瞭解。