1. 程式人生 > >寫一個通用的冪等元件,我覺得很有必要

寫一個通用的冪等元件,我覺得很有必要

**本文目錄** 1. 背景 1. 簡單冪等實現 2.1 資料庫記錄判斷 2.2 併發問題解決 1. 通用冪等實現 3.1 設計方案 3.1.1 通用儲存 3.1.2 使用簡單 3.1.3 支援註解 3.1.4 多級儲存 3.1.5 併發讀寫 3.1.6 執行流程 3.2 冪等介面 3.3 冪等註解 3.4 自動區分重複請求 3.5 儲存結構 3.6 原始碼地址 ## 背景 回答群友的問題:**冪等有沒有什麼通用的方案和實踐?** 關於什麼是冪等,本文就不再闡述了。相信大家都知道,並且也都遇到過類似的問題以及有自己的一套解決方案。 基本上所有業務系統中的冪等都是各自進行處理,也不是說不能統一處理,統一處理的話需要考慮的內容會比較多。 我個人認為核心的業務還是適合業務方自己去處理,比如訂單支付,會有個支付記錄表,一個訂單隻能被支付一次,通過支付記錄表就可以達到冪等的效果。 還有一些不是核心的業務,但是也有冪等的需求。比如網路問題,多次重試。使用者點選多次等場景。這種場景下還是需要一個通用的冪等框架來處理,會讓業務開發更加簡單。 ## 簡單冪等實現 冪等的實現其實並不複雜,方案也有很多種,首先介紹下基於資料庫記錄的方案來實現,後面再介紹通用方案。 ### 資料庫記錄判斷 以文章開頭講的支付場景來舉例。業務場景是一個訂單隻能支付一次,所以我們在支付之前會判斷這個訂單有沒有支付過,如果沒有支付過則進行支付,如果支付過了,就反正支付成功,冪等。 這種方式需要有一個額外的表來儲存做過的動作,才能判斷之前有沒有做過這件事情。 就好比你年齡大了,然後還是單身的技術宅。這個時候你家裡著急了呀,你老媽天天給你介紹小姐姐。你每個週末都要打扮的非常帥氣,去見你老媽給你介紹的小姐姐。 去之前你得記錄下吧,8 月第一週我見的 XXX, 第二週我見的 YYY, 如果第三週又讓你去見 XXX, 如果這個時候你不喜歡 XXX, 你會翻出你的小本本看下,這個之前見過了,沒必要再見了,不然見了多尷尬啊。 ### 併發問題解決 通過查詢支付記錄,判斷能否進行支付在業務邏輯上沒一點問題。但是在併發場景就會有問題。 1001 的訂單發起了兩次支付請求,當前兩個請求同時查詢支付記錄,都沒有查詢到,然後都開始走支付的邏輯,最後發現同一個訂單支付了兩次,這就是併發導致的冪等問題。 併發解決的方案也有很多種,簡單點的直接用資料庫的唯一索引解決,稍微麻煩點的都會用分散式鎖來對同一個資源進行加鎖。 比如我們對訂單 1001 進行加鎖,如果同時發起了兩次支付請求,那麼同一時間只能有一個請求可以獲取鎖,另一個請求獲取不到鎖可以直接失敗,也可以等待前面的請求執行完成。 如果等待前面的請求執行完成,接著往下處理,就能查到 1001 已經支付過了,直接返回支付成功了。 ## 通用冪等實現 為了能夠讓大家更專注於業務功能的開發,簡單場景的冪等操作我認為可以進行統一封裝來處理,下面介紹一下通用冪等的實現。 ![](https://img2020.cnblogs.com/blog/1618095/202009/1618095-20200909122240333-849599937.png) ### 設計方案 #### 通用儲存 一般我們在程式內部做冪等的話都是先查詢,然後根據查詢的結果做對應的操作。同時會對相同的資源進行加鎖來避免併發問題。 加鎖是通用的,不通用的部分就是判斷這個操作之前有沒有操作過,所以我們需要有一個通用的儲存來記錄所有的操作。 #### 使用簡單 提供通用的冪等元件,注入對應的類即可實現冪等,遮蔽加鎖,記錄判斷等邏輯。 #### 支援註解 除了通過程式碼的方式來進行冪等的控制,同時為了讓使用更加簡單,還需要提供註解的方式來支援冪等,使用者只需要在對應的業務方法上增加對應的註解,即可實現冪等。 #### 多級儲存 需要支援多級儲存,比如一級儲存可以用 Redis 來實現,優點是效能高,適用於 90%的場景。因為很多場景都是為了防止短時間內請求重複導致的問題,通過設定一定的失效時間,讓 Key 自動失效。 二級儲存可以支援 Mysql, Mongo 等資料庫,適用於時間長或者永久儲存的場景。 可以通過配置指定一級儲存用什麼,二級儲存用什麼。**這個場景非常適合用策略模式來實現。** #### 併發讀寫 引入多級儲存勢必會涉及到併發讀寫的場景,可以支援兩種方式,順序和併發。 順序就是先寫一級儲存,再寫二級儲存,讀也是一樣。這樣的問題在於效能會有點損耗。 併發就是多執行緒同時寫入,同時讀取,提高效能。 #### 冪等執行流程 ![](https://img2020.cnblogs.com/blog/1618095/202009/1618095-20200909122250817-55372339.png) ### 冪等介面 **冪等介面定義** ```plain public interface DistributedIdempotent { /** * 冪等執行 * @param key 冪等Key * @param lockExpireTime 鎖的過期時間 * @param firstLevelExpireTime 一級儲存過期時間 * @param secondLevelExpireTime 二級儲存過期時間 * @param timeUnit 儲存時間單位 * @param readWriteType 讀寫型別 * @param execute 要執行的邏輯 * @param fail Key已經存在,冪等攔截後的執行邏輯 * @return */