接口的幂等设计
接口冪等
什么是接口冪等?就是一個接口,被重復調用多次,卻能夠保證對系統內部產生的影響是一致的,也就是調用多次和調用一次,數據的變化是一樣的,是相同的,不會因為調用多次而出現任何數據問題。分布式系統中,接口冪等性是系統可行性論證的第一個步驟。很多地方需要把接口設計成冪等。
思路基本上是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
?
posted on 2018-12-16 16:38 CanntBelieve 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/FlyAway2013/p/10126940.html
總結
- 上一篇: 纸牌游戏CardBattle的设计与开发
- 下一篇: 搭建流媒体服务器(1)