分佈式系統中實現冪等性的幾種方式

在微服務架構下,我們在完成一個訂單流程時經常遇到下面的場景:

  • 一個訂單創建接口,第一次調用超時了,然後調用方重試了一次
  • 在訂單創建時,我們需要去扣減庫存,這時接口發生了超時,調用方重試了一次
  • 當這筆訂單開始支付,在支付請求發出之後,在服務端發生了扣錢操作,接口響應超時了,調用方重試了一次
  • 一個訂單狀態更新接口,調用方連續發送了兩個消息,一個是已創建,一個是已付款。但是你先接收到已付款,然後又接收到了已創建
  • 在支付完成訂單之後,需要發送一條短信,當一臺機器接收到短信發送的消息之後,處理較慢。消息中間件又把消息投遞給另外一臺機器處理

為了解決以上問題,就需要保證接口的冪等性,接口的冪等性實際上就是接口可重複調用,在調用方多次調用的情況下,接口最終得到的結果是一致的。有些接口可以天然的實現冪等性,比如查詢接口,對於查詢來說,你查詢一次和兩次,對於系統來說,沒有任何影響,查出的結果也是一樣。

除了查詢功能具有天然的冪等性之外,增加、更新、刪除都要保證冪等性。那麼如何來保證冪等性呢?

分佈式系統中實現冪等性的幾種方式

常見用來保證冪等的手段

1.MVCC方案

多版本併發控制,該策略主要使用update with condition(更新帶條件來防止)來保證多次外部請求調用對系統的影響是一致的。在系統設計的過程中,合理的使用樂觀鎖,通過version或者updateTime(timestamp)等其他條件,來做樂觀鎖的判斷條件,這樣保證更新操作即使在併發的情況下,也不會有太大的問題。例如

select * from tablename where condition=#condition# //取出要跟新的對象,帶有版本versoin

update tableName set name=#name#,version=version+1 where version=#version#

在更新的過程中利用version來防止,其他操作對對象的併發更新,導致更新丟失。為了避免失敗,通常需要一定的重試機制。

2.去重表

在插入數據的時候,插入去重表,利用數據庫的唯一索引特性,保證唯一的邏輯。

這種方法適用於在業務中有唯一標的插入場景中,比如在以上的支付場景中,如果一個訂單隻會支付一次,所以訂單ID可以作為唯一標識。這時,我們就可以建一張去重表,並且把唯一標識作為唯一索引,在我們實現時,把創建支付單據和寫入去去重表,放在一個事務中,如果重複創建,數據庫會拋出唯一約束異常,操作就會回滾。

3.悲觀鎖

select for update,整個執行過程中鎖定該訂單對應的記錄。注意:這種在DB讀大於寫的情況下儘量少用。

4. select + insert

併發不高的後臺系統,或者一些任務JOB,為了支持冪等,支持重複執行,簡單的處理方法是,先查詢下一些關鍵數據,判斷是否已經執行過,在進行業務處理,就可以了。注意:核心高併發流程不要用這種方法。

5.狀態機冪等

在設計單據相關的業務,或者是任務相關的業務,肯定會涉及到狀態機,就是業務單據上面有個狀態,狀態在不同的情況下會發生變更,一般情況下存在有限狀態機,這時候,如果狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變更,理論上是不能夠變更的,這樣的話,保證了有限狀態機的冪等。

分佈式系統中實現冪等性的幾種方式

這種方法適合在有狀態機流轉的情況下,比如就會訂單的創建和付款,訂單的付款肯定是在之前,這時我們可以通過在設計狀態字段時,使用int類型,並且通過值類型的大小來做冪等,比如訂單的創建為0,付款成功為100。付款失敗為99

在做狀態機更新時,我們就這可以這樣控制

update `order` set status=#{status} where id=#{id} and status

6. token機制,防止頁面重複提交

業務要求:頁面的數據只能被點擊提交一次

發生原因:由於重複點擊或者網絡重發,或者nginx重發等情況會導致數據被重複提交

解決辦法:

  • 集群環境:採用token加redis(redis單線程的,處理需要排隊)
  • 單JVM環境:採用token加redis或token加jvm內存

處理流程:

  • 數據提交前要向服務的申請token,token放到redis或jvm內存,token有效時間
  • 提交後後臺校驗token,同時刪除token,生成新的token返回

token特點:要申請,一次有效性,可以限流

7. 對外提供接口的api如何保證冪等

如銀聯提供的付款接口:需要接入商戶提交付款請求時附帶:source來源,seq序列號。source+seq在數據庫裡面做唯一索引,防止多次付款,(併發時,只能處理一個請求)

總結: 冪等性應該是合格程序員的一個基因,在設計系統時,是首要考慮的問題,尤其是在像支付寶,銀行,互聯網金融公司等涉及的都是錢的系統,既要高效,數據也要準確,所以不能出現多扣款,多打款等問題,這樣會很難處理,用戶體驗也不好 。

8.全局唯一ID

如果使用全局唯一ID,就是根據業務的操作和內容生成一個全局ID,在執行操作前先根據這個全局唯一ID是否存在,來判斷這個操作是否已經執行。如果不存在則把全局ID,存儲到存儲系統中,比如數據庫、redis等。如果存在則表示該方法已經執行。

從工程的角度來說,使用全局ID做冪等可以作為一個業務的基礎的微服務存在,在很多的微服務中都會用到這樣的服務,在每個微服務中都完成這樣的功能,會存在工作量重複。另外打造一個高可靠的冪等服務還需要考慮很多問題,比如一臺機器雖然把全局ID先寫入了存儲,但是在寫入之後掛了,這就需要引入全局ID的超時機制。

使用全局唯一ID是一個通用方案,可以支持插入、更新、刪除業務操作。但是這個方案看起來很美但是實現起來比較麻煩,下面的方案適用於特定的場景,但是實現起來比較簡單。

參考原文:

http://www.cnblogs.com/wxgblogs/p/6639272.html

https://www.cnblogs.com/jack87224088/p/8688948.html


分享到:


相關文章: