1. 程式人生 > >介面冪等性的解決方案

介面冪等性的解決方案

在程式設計中,冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。冪等函式指的是那些使用相同引數重複執行也能獲得相同結果的函式。這些函式不會影響系統狀態,也不用擔心重複執行會對系統造成改變。比如說getIdCard()函式和setTrue()函式就是冪等函式。

冪等在我的理解裡就是,一個操作不論被執行多少次,產生的效果和返回的結果都是一樣的。

一個冪等的操作典型如:把編號為5的記錄的A欄位設定為0這種操作不管執行多少次都是冪等的。

一個非冪等的操作典型如:把編號為5的記錄的A欄位增加1這種操作顯然就不是冪等的。

冪等的方案

1.查詢操作:Select是天然的冪等操作。

查詢一次和查詢多次,在資料不變的情況下,查詢的結果都是一樣的。

2.刪除操作:刪除操作也是冪等的,刪除一次和刪除多次都是把資料刪除。

因為刪除操作通常是定向的,比如通過id去刪除資料,如果該id在資料庫中存在對應記錄,則刪除該記錄;如果該id在資料庫中不存在對應記錄,也是執行的刪除記錄操作,只是沒有實質性地刪除到記錄而已,卻也不會有其他的副作用。

但是如果刪除操作具有返回值的話,可能返回的結果會不一樣,比如刪除一條記錄之後返回這條記錄中的某個值,如果刪除的資料不存在(已經在第一次的刪除請求中被刪除了),返回的就是空值了。

3.唯一索引:通過在資料庫表的一個欄位上建立唯一索引可以有效防止新增髒資料。

比如有一個特殊訂單表,這個特殊訂單表關聯了一個使用者表,業務設定是每一個使用者只能建立一個特殊訂單,也就意味著在這個特殊訂單表中只能有一條使用者關聯的記錄。那麼這時候就可以在這個特殊訂單表上針對這個使用者關聯的欄位做一個唯一索引,通過資料庫的唯一約束來限制往特殊訂單表中插入多條一個使用者關聯的記錄。這樣,當第二次請求往特殊訂單表中插入一個使用者關聯的特殊訂單記錄的時候,資料庫就會報錯並回滾插入操作,也就保證了冪等。

4.Token校驗機制:操作前先校驗Token,以防止頁面重複提交。

原理上是通過Session Token來實現的,當然也可以通過Redis來實現。當客戶端請求頁面時,伺服器會先生成一個全域性唯一的Token,然後將該Token放置到Session或Redis當中,然後將Token傳送給客戶端(一般通過構造Hidden表單或放在瀏覽器快取中)。等下次客戶端提交請求時,Token就會隨著表單一起提交到伺服器端。當伺服器端第一次驗證通過之後,就會將Session中的Token值更新或刪除,若使用者重複提交,第二次的驗證判斷就是失敗,請求的操作也不會被重複執行。這是因為使用者提交的表單中的Token沒變,但伺服器端的Session中的Token已經改變了或不存在了。

5.悲觀鎖:獲取資料的時候加鎖獲取。

select * from yanggb where id = 'huangq' for update;

悲觀鎖使用時一般伴隨事務一起使用,資料鎖定時間可能會很長,需要根據實際情況選用。

另外要注意的是,id欄位一定是主鍵或者唯一索引,不然可能造成鎖表的結果,處理起來會非常麻煩。

6.樂觀鎖:通過版本號或其他狀態欄位做更新限制。

與悲觀鎖長時間鎖表不一樣,樂觀鎖只是在更新資料那一刻鎖表,其他時間不鎖表。所以樂觀鎖相對於悲觀鎖,在大部分場景中效率會更高一些。樂觀鎖的實現方式多種多樣,可以通過version或者其他狀態條件。

id name age version
1 yanggb 18 1

比如給業務表內新增一個版本號的欄位,如果要呼叫一個介面去更新年齡之前,就需要先查一下他的版本號是多少,然後呼叫介面的時候帶上版本號。

在接口裡保證分散式介面的冪等性(在更新的SQL中新增version的條件判斷):

update user set age = 21, version = version + 1 where id = 1 and version = 1;

這樣,多次提交的請求,因為版本號(version)都一樣,因為第一次請求執行成功之後version已經+1了,則後面的請求因為version對應不上,都不會被執行。

7.分散式鎖:另一個角度的Token校驗。

如果是分散式系統的話,構建全域性唯一索引會比較困難,比如唯一性的欄位就沒有辦法確定。這時候可以引入分散式鎖,通過第三方的系統(Redis或Zookeeper),在業務系統插入資料或者更新資料前,需要先獲取分散式鎖,然後才能做操作,操作完成之後就釋放鎖。這樣其實是把單機系統裡面多執行緒併發鎖的思路引入了多個系統的場景,也就是分散式系統中的解決思路。

要點:某個長流程處理過程要求不能併發執行,可以在流程執行之前根據某個標誌(使用者ID+字尾等)獲取分散式鎖,其他流程執行時獲取鎖就會失敗,也就是同一時間該流程只能有一個能執行成功,執行完成後,釋放分散式鎖(分散式鎖要第三方系統提供)。

8.select + insert:一種簡單卻比較笨的方式。

對於一些併發不高的後臺系統,或者一些任務JOB,為了支援冪等,支援重複執行,可以採取的一種簡單處理方法是,先根據一些關鍵資料到表中查詢記錄,以此來判斷是否已經執行過,判斷後再進行業務處理就可以了。

要注意的是,核心高併發流程不要用這種方法,因為要查詢一遍資料(你想想為什麼要會把資料放到Redis中),效能太低了。

9.狀態機冪等:另一個角度的樂觀鎖。

在設計單據相關的業務,或者是任務相關的業務,肯定會涉及到狀態機(狀態變更圖)。簡單理解,就是業務單據上面有個狀態的欄位,狀態在不同的情況下會發生變更,一般情況下存在有限狀態機。這時候,如果狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變更,理論上是不能夠變更的,這樣的話,保證了有限狀態機的冪等。注意:訂單等單據類業務,存在很長的狀態流轉,一定要深刻理解狀態機,對業務系統設計能力提高有很大幫助。

總結

冪等的概念與分散式、高併發或JavaEE的概念都沒有關係,其只關心操作被多次執行產生的影響是否與一次執行是一致的。

事實上,要做到冪等性,只要從介面的設計上出發,不設計出任何非冪等的操作即可。譬如說有一個需求是,當用戶點選贊同時,將答案的贊同數量+1。直接修改使用者點贊表(答案id,點贊數)的點贊數(+1)顯然不是冪等的。這種場景就可以改為:當用戶點選贊同時,往使用者點贊表(答案id,使用者id)中新增一條記錄,然後通過在使用者id欄位上建立唯一索引來確保在答案贊同表中只存在一條一個使用者點讚的記錄,最後的贊同數量由答案贊同表通過count去統計出來。當然了,實際的系統也不會這麼設計,這裡只是我沒有想到更好的例子。說起來簡單,做起來真的太難了,哈哈哈。

總之冪等性應該是合格程式設計師的一個基因,在設計系統時,是首要考慮的問題,尤其是在像支付寶,銀行或網際網路金融公司等涉及的都是金錢錢的系統,既要高效,也要保證資料準確,不能出現多扣款,多打款等問題,這樣會很難處理,使用者體驗也不好。

 

"他們就像星星一樣那麼亮,我永遠都夠不著。"