小夥伴們有沒有遇到過生產環境常常出現太重複的數據?在排查問題的時候,數據又是正常的。這個是何解呢?怎麼會出現這種狀況,並且還很難排查問題。今天我給你們分享一下這裏的緣由,以及解決方案。redis
產生重複數據或數據不一致(假定程序業務代碼沒問題),絕大部分就是發生了重複的請求,重複請求是指同一個請求由於某些緣由被屢次提交。致使這個狀況會有幾種場景:sql
1)微服務場景,在咱們傳統應用架構中調用接口,要麼成功,要麼失敗。可是在微服務架構下,會有第三個狀況【未知】,也就是超時。若是超時了,微服務框架會進行重試。2)用戶交互的時候屢次點擊。如:快速點擊按鈕屢次。3)MQ消息中間件,消息重複消費4)第三方平臺的接口(如:支付成功回調接口),由於異常也會致使屢次異步回調 5)其餘中間件/應用服務根據自身的特性,也有可能進行重試。數據庫
咱們知道了發生的緣由,本質就是屢次請求了,那如何解決呢?緩存
有些小夥伴們會想到冪等這個詞,是的,就是咱們在設計某些接口時,要考慮如何保證接口冪等,那什麼是接口冪等呢?bash
網上是這樣介紹的【接口的冪等性實際上就是接口可重複調用,在調用方屢次調用的狀況下,接口最終獲得的結果是一致的】服務器
網上的說法定義,有點不是太正確,咱們看下怎麼不正確架構
如一個線程請求用戶列表接口:select * from user,返回用戶表中的數據,而另外一個線程往用戶表插入數據。那請求用戶列表的線程返回的數據每次都不同,那按照上面的說法,查詢用戶列表的接口就不是冪等的,這顯然是不正確的。框架
老顧的理解應該是屢次調用對系統的產生的影響是同樣的,即對資源的做用是同樣的,可是返回值容許不一樣。異步
咱們來看一下SQL相關業務是否冪等?jvm
1、查詢,select * from user where xxx,不會對數據產生任何變化,具有冪等性。
2、新增,insert into user(userid,name) values(1,'a'),
如userid爲惟一主鍵,即重複操做上面的業務,只會插入一條用戶數據,具有冪等性。如userid不是主鍵,能夠重複,那上面業務屢次操做,數據都會新增多條,不具有冪等性。
3、修改,區分直接賦值和計算賦值。
一、直接賦值,update user set point = 20 where userid=1,無論執行多少次,point都同樣,具有冪等性。二、計算賦值,update user set point = point + 20 where userid=1,每次操做point數據都不同,不具有冪等性。
4、刪除,delete from user where userid=1,屢次操做,結果同樣,具有冪等性。
上面場景中,咱們發現新增沒有惟一主鍵約束的數據,和修改計算賦值型操做都不具有冪等性
那怎麼去解決呢?
網上介紹不少,但介紹的太簡單了,且關鍵點都沒有介紹到。老顧這裏只介紹經常使用的方案
token方式的流程,上一張圖,比較清晰
上圖就是token+redis的冪等方案,適用絕大部分場景。主要思想:
一、服務端提供了發送token的接口。咱們在分析業務的時候,哪些業務是存在冪等問題的,就必須在執行業務前,先去獲取token,服務器會把token保存到redis中。(微服務確定是分佈式了,若是單機就適用jvm緩存)。二、而後調用業務接口請求時,把token攜帶過去,通常放在請求頭部。三、服務器判斷token是否存在redis中,存在表示第一次請求,能夠繼續執行業務,執行業務完成後,最後須要把redis中的token刪除。四、若是判斷token不存在redis中,就表示是重複操做,直接返回重複標記給client,這樣就保證了業務代碼,不被重複執行。
這種方案是比較經常使用的方案,也是網上常常介紹的,可是有一點不一樣的地方:
網上方案:檢驗token存在(表示第一次請求)後,就馬上刪除token,再進行業務處理上面方案:檢驗token存在(表示第一次請求)後,先進行業務處理,再刪除token
關鍵點就是 先刪除token,仍是後刪除token。
1、網上方案缺點
咱們看下網上方案,先刪除token,這是出現系統問題致使業務處理出現異常,業務處理沒有成功,接口調用方也沒有獲取到明確的結果,而後進行重試,但token已經刪除掉了,服務端判斷token不存在,認爲是重複請求,就直接返回了,沒法進行業務處理了。
2、上面方案缺點
後刪除token也是會存在問題的,若是進行業務處理成功後,刪除redis中的token失敗了,這樣就致使了有可能會發生重複請求,由於token沒有被刪除
小夥伴們有沒有發現,其實上面的問題就是數據庫和緩存redis數據不一致的問題。以前老顧分享了一篇文章,裏面詳細介紹了如何解決數據庫和緩存redis數據不一致的問題。小夥伴們可自行查閱。
其實根據這個場景的業務,能夠有個簡單的處理方式。老顧推薦是網上方案先刪除token,先保證不會由於重複請求,業務數據出現問題。頂多再讓用戶處理一次。
出現業務異常,可讓調用方配合處理一下,從新獲取新的token,再次由業務調用方發起重試請求就ok了。
小夥伴們有沒有發現,業務請求每次請求,都會有額外的請求(一次獲取token請求、判斷token是否存在的業務)。其實真實的生產環境中,1萬請求也許只會存在10個左右的請求會發生重試,爲了這10個請求,咱們讓9990個請求都發生了額外的請求。(固然redis性能很好,耗時不會太明顯)
關於樂觀鎖老顧以前也講過,你們能夠去查閱。樂觀鎖這裏解決了計算賦值型的修改場景。咱們對以前的sql語句進行修改。
update user
set point = point + 20, version = version + 1
where
userid=1
and
version=1
複製代碼
加上了版本號後,就讓此計算賦值型業務,具有了冪等性。
就是在操做業務前,須要先查詢出當前的version版本
這個機制是利用了數據庫的主鍵惟一約束的特性,解決了在insert場景時冪等問題。但主鍵的要求不是自增的主鍵,這樣就須要業務生成全局惟一的主鍵,以前老顧的文章也介紹過分佈式惟一主鍵ID的生成,可自行查閱。若是是分庫分表場景下,路由規則要保證相同請求下,落地在同一個數據庫和同一表中,要否則數據庫主鍵約束就不起效果了,由於是不一樣的數據庫和表主鍵不相關。由於對主鍵有必定的要求,這個方案就跟業務有點耦合了,沒法用自增主鍵了。
這個方案業務中要有惟一主鍵,這個去重表中只要一個字段就行,設置惟一主鍵約束,固然根據業務自行添加其餘字段。主要流程上圖
上面的主要流程就是 把惟一主鍵插入去重表,再進行業務操做,且他們在同一個事務中。這個保證了重複請求時,由於去重表有惟一約束,致使請求失敗,避免了冪等問題。
這裏要注意的是,去重表和業務表應該在同一庫中,這樣就保證了在同一個事務,即便業務操做失敗了,也會把去重表的數據回滾。這個很好的保證了數據一致性。
這個方案也是比較經常使用的,去重表是跟業務無關的,不少業務能夠共用同一個去重表,只要規劃好惟一主鍵就好了。
上面介紹了一些冪等方案,小夥伴們根據自身的業務進行選擇,儘可能不要讓系統變的複雜,因此推薦惟一主鍵和樂觀鎖方式,由於實現比較簡單。好了,今天就介紹到這裏,謝謝你們!!!