新冠疫情結束在即,各位小夥伴想必也開始工作了吧......
2023年伊始,世界彷彿開了乙個大玩笑。好在天佑中華,武漢也解封了,一切都在向好的地方發展。希望小夥伴們的工作和生活沒有受到太大的影響。
據我7年以來的開發經驗,工業級別的**,幾乎三分之二都是在處理異常情況。而且我們去面試,面試官考察應試者的最好的方法,就是考察他是否能夠思考周全,想到所有異常情況的處理方案。
相信大家都使用過訊息mq,他可以很好地進行系統解耦,減低變成的複雜度,又可以進行削峰,增加系統在高併發的穩定性。那麼使用mq有哪些注意事項呢?是不是mq就是萬無一失呢?一條mq訊息從產生到消費,有沒有可能失敗?在哪些環節可能失敗,如何處理?
一般來說,從生產者到mq中介軟體是通過網路呼叫的,是網路呼叫就有可能存在失敗。下面這些原因,都有可能造成mq生產失敗,例如網路波動,儘管生產者到mq伺服器之間是內網呼叫,並不意味著網路呼叫的成功率就是百分之百,內網呼叫也會遇到網路波動,造成呼叫超時或者失敗。又如呼叫的mq機器瞬間crash掉,這也是有可能造成呼叫失敗的。面對生產者呼叫mq的失敗,我們是容易比較容易處理的,我們只要簡單地進行重試即可,如果重試2-3次失敗,那麼非常有可能是出現大問題,這個時候再重試意義不大,需要進行告警並處理。
訊息到達訊息中介軟體之後,通常是會被儲存起來的,只有被寫入到磁碟中,訊息才是真正地被儲存,不會丟失。但是,大部分mq中介軟體並不是收到訊息就立馬寫入磁碟的,只是由於磁碟的寫入速度相對於記憶體,現得慢得多得多,所以,像kafka這樣的訊息系統,是會把訊息寫到緩衝區中,非同步寫入磁碟,如果機器在中途突然斷電,是有可能會丟失訊息的。為了解決這個問題,大部分的mq都是採用分布式部署,訊息會在多台機器上寫入快取中成功才會返回給業務方成功,由於多台機器同時斷電的可能性較低,我們可以認為這是比較低成本又可靠的方案。
一般的mq都有mq重試機制,如果處理失敗,就會嘗試重複消費這個mq。這個帶來的問題就是,mq可能已經成功消費了,但是在通知mq中介軟體的時候失敗了,這個時候帶來的結果就是訊息重複消費。同理,在生產者重試的時候,也會遇到訊息重複消費的問題。這個時候,就要求我們盡量把介面設計得有冪等性,這個時候即便是重複消費,也不用擔心什麼問題了。基本上做好這三點,我們就能夠大大地提高我們地系統地可用性了!
這裡需要關注幾個重點:
冪等不僅僅只是一次(或多次)請求對資源沒有***(比如查詢資料庫操作,沒有增刪改,因此沒有對資料庫有任何影響)。
冪等還包括第一次請求的時候對資源產生了***,但是以後的多次請求都不會再對資源產生***。
冪等性是系統服務對外一種承諾(而不是實現),承諾只要呼叫介面成功,外部多次呼叫對系統的影響是一致的。宣告為冪等的服務會認為外部呼叫失敗是常態,並且失敗之後必然會有重試。
業務開發中,經常會遇到重複提交的情況,無論是由於網路問題無法收到請求結果而重新發起請求,或是前端的操作抖動而造成重複提交情況。 在交易系統,支付系統這種重複提交造成的問題有尤其明顯,比如:
向支付系統發起支付請求,由於網路問題或系統bug重發,支付系統應該只扣一次錢。很顯然,宣告冪等的服務認為,外部呼叫者會存在多次呼叫的情況,為了防止外部多次呼叫對系統資料狀態的發生多次改變,將服務設計成冪等。
上面例子中遇到的問題,只是重複提交的情況,和服務冪等的初衷是不同的。重複提交是在第一次請求已經成功的情況下,人為的進行多次操作,導致不滿足冪等要求的服務多次改變狀態。而冪等更多使用的情況是第一次請求不知道結果(比如超時)或者失敗的異常情況下,發起多次請求,目的是多次確認第一次請求成功,卻不會因多次請求而出現多次的狀態變化。
以sql為例,有下面三種場景,只有第三種場景需要開發人員使用其他策略保證冪等性:
select col1 from tab1 wher col2=2
,無論執行多少次都不會改變狀態,是天然的冪等。
update tab1 set col1=1 where col2=2
,無論執行成功多少次狀態都是一致的,因此也是冪等操作。
update tab1 set col1=col1+1 where col2=2
,每次執行的結果都會發生變化,這種不是冪等的。
冪等可以使得客戶端邏輯處理變得簡單,但是卻以服務邏輯變得複雜為代價。滿足冪等服務的需要在邏輯中至少包含兩點:
首先去查詢上一次的執行狀態,如果沒有則認為是第一次請求
在服務改變狀態的業務邏輯前,保證防重複提交的邏輯
冪等是為了簡化客戶端邏輯處理,卻增加了服務提供者的邏輯和成本,是否有必要,需要根據具體場景具體分析,因此除了業務上的特殊要求外,盡量不提供冪等的介面。
增加了額外控制冪等的業務邏輯,複雜化了業務功能;
把並行執行的功能改為序列執行,降低了執行效率。
冪等需要通過唯一的業務單號來保證。也就是說相同的業務單號,認為是同一筆業務。使用這個唯一的業務單號來確保,後面多次的相同的業務單號的處理邏輯和執行效果是一致的。 下面以支付為例,在不考慮併發的情況下,實現冪等很簡單:
①先查詢一下訂單是否已經支付過,
②如果已經支付過,則返回支付成功;如果沒有支付,進行支付流程,修改訂單狀態為『已支付』。
上述的保證冪等方案是分成兩步的,第②步依賴第①步的查詢結果,無法保證原子性的。在高併發下就會出現下面的情況:第二次請求在第一次請求第②步訂單狀態還沒有修改為『已支付狀態』的情況下到來。既然得出了這個結論,餘下的問題也就變得簡單:把查詢和變更狀態操作加鎖,將並行操作改為序列操作。
如果只是更新已有的資料,沒有必要對業務進行加鎖,設計表結構時使用樂觀鎖,一般通過version來做樂觀鎖,這樣既能保證執行效率,又能保證冪等。例如:update tab1 set col1=1,version=version+1 where version=#version#
不過,樂觀鎖存在失效的情況,就是常說的aba問題,不過如果version版本一直是自增的就不會出現aba的情況。
使用訂單號orderno做為去重表的唯一索引,每次請求都根據訂單號向去重表中插入一條資料。第一次請求查詢訂單支付狀態,當然訂單沒有支付,進行支付操作,無論成功與否,執行完後更新訂單狀態為成功或失敗,刪除去重表中的資料。後續的訂單因為表中唯一索引而插入失敗,則返回操作失敗,直到第一次的請求完成(成功或失敗)。可以看出防重表作用是加鎖的功能。
這裡使用的防重表可以使用分布式鎖代替,比如redis。訂單發起支付請求,支付系統會去redis快取中查詢是否存在該訂單號的key,如果不存在,則向redis增加key為訂單號。查詢訂單支付已經支付,如果沒有則進行支付,支付完成後刪除該訂單號的key。通過redis做到了分布式鎖,只有這次訂單訂單支付請求完成,下次請求才能進來。相比去重表,將放併發做到了快取中,較為高效。思路相同,同一時間只能完成一次支付請求。
這種方式分成兩個階段:申請token階段和支付階段。 第一階段,在進入到提交訂單頁面之前,需要訂單系統根據使用者資訊向支付系統發起一次申請token的請求,支付系統將token儲存到redis快取中,為第二階段支付使用。 第二階段,訂單系統拿著申請到的token發起支付請求,支付系統會檢查redis中是否存在該token,如果存在,表示第一次發起支付請求,刪除快取中token後開始支付邏輯處理;如果快取中不存在,表示非法請求。 實際上這裡的token是乙個信物,支付系統根據token確認,你是你媽的孩子。不足是需要系統間互動兩次,流程較上述方法複雜。
把訂單的支付請求都快速地接下來,乙個快速接單的緩衝管道。後續使用非同步任務處理管道中的資料,過濾掉重複的待支付訂單。優點是同步轉非同步,高吞吐。不足是不能及時地返回支付結果,需要後續監聽支付結果的非同步返回。
訊息佇列 保證訊息消費的冪等
昨天業務反饋了乙個問題,乙個使用者的月流水賬單重複了,拿到userid,開始定位問題之路。檢視資料庫記錄,如下圖,使用者月流水資料確實重複了 taskid同乙個批次,每個月資料都有二條 1.首先,看外部資料 商是否重複推送業務資料給我,我程式中是會設定攔截重複訊息 2.檢視訊息接收,以及訊息推送到m...
訊息佇列三 訊息重複消費問題(冪等性)
其實這個很常見的乙個問題,這倆問題基本可以連起來問。既然是消費訊息,那肯定要考慮考慮會不會重複消費?能不能避免重複消費?或者重複消費了也別造成系統異常可以嗎?這個是mq領域的基本問題,其實本質上還是問你使用訊息佇列如何保證冪等性,這個是你架構裡要考慮的乙個問題。要考慮的實際生產上的系統設計問題。首先...
伺服器虛擬機器啟動失敗經驗總結
由於公司機房於凌晨意外停電,導致機房伺服器全部意外重啟,但是我們的伺服器上都是執行著多個虛擬機器,於是早上一上班就趕緊逐個啟動虛擬機器,以往經驗都是很快就全部重啟好的,but天有不測風雲,今天的兩個虛擬機器始終啟動失敗,客戶那邊一直在催著系統為什麼還未恢復,真是急啊!根據提示資訊,度娘了半天,也沒乙...