冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。編程
在編程中.一個冪等操做的特色是其任意屢次執行所產生的影響均與一次執行的影響相同。冪等函數,或冪等方法,是指可使用相同參數重複執行,並能得到相同結果的函數。這些函數不會影響系統狀態,也不用擔憂重複執行會對系統形成改變。例如,「getUsername()和setTrue()」函數就是一個冪等函數.更復雜的操做冪等保證是利用惟一交易號(流水號)實現.後端
——百度百科瀏覽器
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/1.1規範中冪等性的定義
從定義上看,HTTP方法的冪等性是指一次和屢次請求某一個資源應該具備一樣的反作用。說白了就是,同一個請求,發送一次和發送N次效果是同樣的!冪等性是分佈式系統設計中十分重要的概念,而HTTP的分佈式本質也決定了它在HTTP中具備重要地位。下面將以HTTP中的冪等性作例子加以介紹。安全
假設有一個從帳戶取錢的遠程API(能夠是HTTP的,也能夠不是),咱們暫時用類函數的方式記爲:服務器
bool withdraw(account_id, amount)
withdraw的語義是從account_id對應的帳戶中扣除amount數額的錢;若是扣除成功則返回true,帳戶餘額減小amount;若是扣除失敗則返回false,帳戶餘額不變。網絡
值得注意的是:和本地環境相比,咱們不能輕易假設分佈式環境的可靠性。架構
因此問題來了,一種典型的狀況是withdraw請求已經被服務器端正確處理,但服務器端的返回結果因爲網絡等緣由被掉丟了,致使客戶端沒法得知處理結果。若是是在網頁上,一些不恰當的設計可能會使用戶認爲上一次操做失敗了,而後刷新頁面,這就致使了withdraw被調用兩次,帳戶也被多扣了一次錢。如圖所示:異步
解決方案一:採用分佈式事務,經過引入支持分佈式事務的中間件來保證withdraw功能的事務性。分佈式事務的優勢是對於調用者很簡單,複雜性都交給了中間件來管理。缺點則是一方面架構過重量級,容易被綁在特定的中間件上,不利於異構系統的集成;另外一方面分佈式事務雖然能保證事務的ACID性質,而但卻沒法提供性能和可用性的保證。分佈式
解決方案二:冪等設計。咱們能夠經過一些技巧把withdraw變成冪等的,好比:ide
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是冪等的,因此任何一步因爲網絡等緣由失敗或超時,客戶端均可以重試,直到得到結果。如圖所示:
和分佈式事務相比,冪等設計的優點在於它的輕量級,容易適應異構環境,以及性能和可用性方面。在某些性能要求比較高的應用,冪等設計每每是惟一的選擇。
本文主要以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方法用於建立資源,所對應的URI並不是建立的資源自己,而是去執行建立動做的操做者,有反作用,不知足冪等性。好比:POST http://www.forum.com/articles的語義是在http://www.forum.com/articles下建立一篇帖子,HTTP響應中應包含帖子的建立狀態以及帖子的URI。兩次相同的POST請求會在服務器端建立兩份資源,它們具備不一樣的URI;因此,POST方法不具有冪等性。
HTTP 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 POST 操做既不是安全的,也不是冪等的(至少在HTTP規範裏沒有保證)。當咱們由於反覆刷新瀏覽器致使屢次提交表單,屢次發出一樣的POST請求,致使遠端服務器重複建立出了資源。
因此,對於電商應用來講,第一對應的後端 WebService 必定要作到冪等性,第二服務器端收到 POST 請求,在操做成功後必須302跳轉到另一個頁面,這樣即便用戶刷新頁面,也不會重複提交表單。
電商的不少業務,考慮更多的是 BASE(即Basically Available、Soft state、和Eventually consistent),而不是 ACID(Atomicity、Consistency、Isolation和 Durability)。即爲了知足高負載的用戶訪問,咱們能夠容忍短暫的數據不一致。那怎麼作呢?
第一,不作分佈式事務,代價太大。
第二,不必定須要實時一致性,只須要保證最終的一致性便可。
第三,「經過狀態機和嚴格的有序操做,來最大限度地下降不一致性」。
第四,最終一致性(Eventually Consistent)經過異步事件作到。
若是消息具備操做冪等性,也就是一個消息被應用屢次與應用一次產生的效果是同樣的話,那麼把不須要同步執行的事務交給異步消息推送和訂閱者集羣來處理便可。假如消息處理失敗,那麼就消息重播,因爲冪等性,應用屢次也能產生正確的結果。
實際狀況下,消息很難具備冪等性,解決方法是使用另外一個表記錄已經被成功應用的消息,即消息隊列和消息應用狀態表一塊兒來解決問題。
上面簡單介紹了冪等性的概念,用冪等設計取代分佈式事務的方法,以及HTTP主要方法的語義和冪等性特徵。其實,若是要追根溯源,冪等性是數學中的一個概念,表達的是N次變換與1次變換的結果相同,有興趣的讀者能夠從Wikipedia上進一步瞭解。
轉自:http://www.i3geek.com/archives/841