什麼是接口冪等?就是一個接口,被重複調用屢次,卻可以保證對系統內部產生的影響是一致的,也就是調用屢次和調用一次,數據的變化是同樣的,是相同的,不會由於調用屢次而出現任何數據問題。分佈式系統中,接口冪等性是系統可行性論證的第一個步驟。不少地方須要把接口設計成冪等。sql
思路基本上是3種:數據庫
1 當第N(N>1)次請求過來時,系統要能知道,這個業務咱們已經處理過了,相同的請求咱們忽略掉就行了
2 當第N(N>1)次請求過來時,無論三七二十一,執行執行之,底層的數據接口層面保證其冪等就行了
3 從源頭上避免請求重複提交。固然,這個有必定的限制。對於用戶重複點擊,那麼容易避免,代理端能夠進行各類過濾,去重。但對於mq的狀況等,可能沒法避免。緩存
雖然概念上很接近,咱們也很容易混爲一談,但服務接口的冪等和數據接口的冪等 ,細分開來仍是有所不一樣的。 服務接口(咱們的service層)可能包括了 對數據的操做,對文件的操做,對網絡的操做,對cpu、內存的計算,還有對其餘服務的操做; 而數據接口(咱們的dao層)經常限於對數據庫表的CRUD(這裏不討論廣義的「數據」的定義,而是內存、緩存、文件、數據庫分開討論),及其複合操做。服務器
全部的數據接口均可以歸結爲增刪改查四大類;固然,下面咱們對這四大類接口進行分析;網絡
查詢和刪除
查詢和刪除業務,自然的具備冪等的特性;架構
1. 查詢
在數據不變的狀況下,查詢一次和查詢屢次,查詢結果是同樣的;併發
2. 刪除
刪除一次和屢次刪除的結果都是把數據刪除;(若是第二次刪除返回0 rows affected之類的,那麼忽略便可)分佈式
3. 新增
咱們能夠把關鍵的 業務id 設置成惟一索引,這樣,第二次會失敗,惟一索引約束錯誤的話,調用方忽略便可。不然就出現了多條數據,一條正確的,其他的是髒數據。高併發
主要就是經過業務的相關的字段組成的一個 惟一性的約束。執行消息處理以前能夠先根據這個惟一約束是否存在,若是存在,說明已經執行過了,忽略便可,不然把這個惟一約束以某種方式 保存起來。大部分狀況 都會存在 至少一個 業務的惟一性的約束, 好比用戶的 郵箱不能重複吧。 測試
4. 更新
若是是冪等的更新操做,好比update table1 set f1 = v1,咱們能夠不用管。由於這些操做自己是冪等的。 不然咱們可能須要對update 語句進行稍稍的改寫,增長where 條件,也就是樂觀鎖的方式。好比 update table1 set f1 = v1 where f1 = v0 (v0 是初始值)。 這樣第一次update會成功,後面的 就會失敗。咱們須要儘可能的把那些非冪等的update sql改寫。 可是這樣,有必定限制就是咱們須要 更多的參數,好比這個初始值。 並且,咱們不能排除某些操做是 不能改寫的, 好比給用戶增長積分等等, 原始積分就是限制的參數, 若是不能提供,那麼沒法改寫。
查詢業務
咱們能夠首先把數據加載到緩存,而後儘可能保證一個高可用的緩存,同時在 新增、更新、刪除的時候維護緩存。
新增業務
新增業務類接口,咱們要解決以下兩個問題
1. 同一個用戶用一樣的數據屢次請求同一個接口(不論是什麼緣由屢次提交,他應該只請求一次)
2. 不一樣用戶的提交一樣的數據請求同一個接口;
第一個問題能夠經過防重複提交來解決;業務數據連同Token,一塊兒提交給接口,同一個Token,只能被處理一次(這裏要注意,只能被處理一次,應該改爲只能被正確的處理一次,也就是說,咱們應該緩存某次新增業務處理的結果,若是上一次請求時出現某些異常,好比數據庫鏈接失敗,用戶再次提交的時候,咱們應該放行用戶的此次請求,固然有些異常就不須要放行了,好比提交的業務數據不對等);
第二個問題是沒法解決的,一個開放的系統,不能杜絕兩個不一樣的客戶端(用戶)同時請求;可是能夠交給數據的最後防線,存儲層;經過惟一索引或惟一組合索引能夠防止新增數據存在髒數據 (當表存在惟一索引,併發時新增報錯時,再查詢一次就能夠了,數據應該已經存在了,返回結果便可) ;
注意:
Token防重複提交,只須要網關這層控制便可;Token的處理機制,還須要緩存調用的處理結果,以判斷是否須要放行後續的重試請求;
更新業務
系統中的大部分業務均可以歸屬到更新業務,好比禁用用戶、電商秒殺等等,只要是有更新操做的,不論是不是還有其餘的操做,都歸結到更新業務;
更新業務接口,不只須要有表單防重複提交的驗證,還須要有下面這些更精細的控制,以防止高併發環境中出現髒讀,幻讀等引發錯誤的數據更新結果;
更新業務接口冪等性解決方案通常是經過各類層面的鎖和CAS機制;
悲觀鎖
悲觀鎖,select for update,整個執行過程當中鎖定要操做的記錄;
樂觀鎖
更新業務的接口,好比訂單付款等,須要綜合使用盡量多的信息來逐步驗證逐步減小直至杜絕重複消息重複處理的機率;基本思路是CAS(Compare And Set);
能夠參考下面的兩篇文章體會一下:
1. 《架構師之路-庫存扣多了,到底怎麼整》
2. 訂單操做,利用訂單編號和訂單的狀態機(序列號)
測試用例
經過下面的方法能夠初步驗證接口冪等性的健壯性:
1. 同一個請求,屢次提交到同一臺節點,屢次提交到不一樣的節點
2. 同一個請求,同時到達同一個節點,同時到達到不一樣的節點
3. 有邏輯前後順序的消息、請求亂序的處理,好比建立訂單的請求和支付訂單的請求,不能保證第一個請求先於第二個請求到達服務器;
--------------------- 摘抄至 https://blog.csdn.net/xichenguan/article/details/78085801-------------
消息的消費確認流程中,任何一個環節均可能會出問題!
被動方應用接收到消息,業務處理完成後應用出問題,消息中間件不知道消息處理結果,會從新投遞消息。
被動方應用接收到消息,業務處理完成後網絡出問題,消息中間件收不到消息處理結果,會從新投遞消息。
被動方應用接收到消息,業務處理時間過長,消息中間件因消息超時未確認,會再次投遞消息。
被動方應用接收到消息,業務處理完成,消息中間件問題致使收不到消息處理結果,消息會從新投遞。
被動方應用接收到消息,業務處理完成,消息中間件收到了消息處理結果,但因爲消息存儲故障致使消息沒能成功確認,消息會再次投遞。
1 根據業務來實現冪等,前面已經說過。
2 增長消息表來實現冪等,主要就是說,若是沒法經過業務信息判斷是否已經消費過,那麼咱們經過mq的消息的id,存放到消息表,而後消費的時候先判斷是否已經存在。經過消息ID,或生成一個惟一ID標記每一條消息,將消息處理成功和去重日誌(也就是「消息表」)經過事物的形式寫入去重表, 若是以前沒有處理成功,那麼去重日誌確定是沒有記錄的,那麼就消費,不然就不消費。 若是mq 會刪除 肯定消費完了的數據,咱們能夠先經過消息ID 去mq peek一下是否存在,存在則表示還未消費,而後消費方進行處理,不然就忽略。
其實這兩種方式道理都差很少,能夠按照具體狀況來作。
有時候,咱們還能夠 經過mq自己的消息超時機制,好比根據業務需求給消息 ID 設置一個 TTL, 或者是直接用 Redis 等緩存機制來保證在合理的時間範圍內不會重複消費。
實現這些冪等是有成本的,我能夠考慮業務狀況,若是都每個消費方的操做都作冪等設計, 有時候可能成本過高,得不償失。咱們須要權衡去重所花的代價決定是否須要實現冪等性,如:購物會員卡成功,向用戶發送通知短信,發送一次或者屢次影響不大。不作冪等性能夠省掉寫去重日誌的操做。
參考:
https://blog.csdn.net/xichenguan/article/details/78085801
https://blog.csdn.net/qq_27384769/article/details/79307340