mq要如何處理訊息丟失、重複消費?

風起塵落發表於2020-11-05

如果要你實現一個支付寶向餘額寶轉賬的功能,比如:賬戶a從支付寶轉出5000餘額寶轉入5000,該怎麼做呢?

可能有些人會說,這還不簡單,直接上圖

支付寶先給賬戶a減5000,呼叫餘額寶的介面給餘額寶的賬號b加5000。

用這種方式正常情況下是可以的,如果出現以下問題該怎麼辦呢?

  1. 呼叫餘額寶api時網路失敗了
  2. 呼叫餘額寶api時網路超時了
  3. 如果餘額寶api業務邏輯比較複雜,耗時比較長,使用者需要長時間的等待才有結果,使用者體驗不好

有人說:如果呼叫餘額寶api時網路失敗了,對介面進行重試不就可以解決問題了。

:你是用同步重試,還是非同步重試呢?

       如果用同步重試,即在呼叫餘額寶api時獲取返回值,如果發現失敗立刻重試3次。呼叫一次餘額寶api的耗時為n秒,重試3次的耗時則為3n秒,介面響應時間增加了兩倍,增加了介面超時的風險。如果重試3次之後,還是失敗該怎麼處理?

        如果用非同步重試,第一次呼叫餘額寶api時,不管是成功還是失敗,都直接給使用者返回成功。如果是失敗,後臺開啟一個執行緒,不斷重試一直到成功為止。如果在不斷重試的過程中伺服器重啟了,該怎麼辦?

又有人說:如果呼叫餘額寶api時網路超時了,不知道上次請求是成功還是失敗,再重試一下不行嗎?

:不是不,第一.餘額寶必須做冪等性設計,不然餘額寶這邊多轉入5000怎麼辦?餘額寶肯定不會犯這種錯誤。第二.同樣會面臨如果呼叫餘額寶api時網路失敗了的問題。

再有人說:如果餘額寶api業務邏輯比較複雜,耗時比較長,使用者需要長時間的等待才有結果,使用者體驗不好。改成非同步就可以解決這個問題了。

答:改成非同步可以提前告知使用者結果,然後在後臺通過補償機制不斷的重試,讓資料達成最終一致性,這種方式對使用者體驗可能確實要好一些。非同步處理又分為:開啟執行緒  和 使用mq。執行緒處理有比較致命的弊端,如果伺服器重啟,執行緒裡的資料會丟失。

接下來,我們的重點放在mq上。

餘額寶給賬戶a減了5000之後,給指定topic1發一條訊息,然後餘額寶從topic1消費這條訊息,給賬戶b加5000。

對於問題1,如果餘額寶處理失敗了,比如像rocketmq這類訊息處理框架會把訊息放入重試佇列重試16次,不需要業務程式碼做額外的工作。

對於問題2,如果伺服器重啟了,由於訊息儲存在服務端的磁碟上,不會丟失,客戶端可以通過offset從服務端重新獲取訊息,它能夠保證訊息至少被餘額寶消費一次。

對於問題3,支付寶給賬戶a減了5000傳送完訊息之後,可以直接返回成功,然後餘額寶作為消費者在後臺默默執行,一直到成功為止。

那麼問題又來了:

如果餘額寶消費了訊息,業務處理失敗了怎麼辦?這個就是所謂的訊息丟失。

要解決訊息丟失就需要建一張訊息傳送表,如圖:

       支付寶從賬戶a減5000,接著往本地訊息表中寫入一條訊息記錄,confirm_status為待確認,然後傳送mq訊息。注意,支付寶這邊的扣款和寫本地訊息表要在同一事務中。

       餘額寶消費訊息給賬戶b加5000之後,呼叫支付寶訊息確認api,修改confirm_status為已確認。

        如果餘額寶這邊訊息丟失了,支付寶有個job會每個5分鐘掃描一次本地訊息表中confirm_status為待確認狀態的記錄,重新傳送一次訊息,這樣餘額寶又可以重新處理了。

那麼還有個問題:

餘額寶這邊處理成功,但是由於呼叫 支付寶訊息確認api失敗,導致支付寶的job重新傳送訊息,餘額寶重複消費了。這個就是所謂的重複訊息。

重複消費要如何解決呢?

        餘額寶也增加一個本地訊息表,記錄業務處理成功的訊息。當然餘額寶的賬號操作和本地訊息表也要在同一個事務中。    

        餘額寶消費訊息之後,先從餘額寶的本地訊息表中查一下,該訊息有沒有消費過,如果已經消費過了,則直接呼叫支付寶訊息確認api,修改confirm_status為已確認,避免下次支付寶的job重複發訊息。如果從餘額寶的本地訊息表中查到沒有消費,則給賬戶b增加5000,同時往本地訊息表寫一條記錄,然後呼叫支付寶訊息確認api。

       總結:通過在mq的生產者和消費者兩端分別增加本地訊息表,並且在生成者端增加定時job掃描待確認狀態的記錄,重新傳送訊息,可以解決:訊息丟失 和 重複消費 問題。當然實際的支付寶向餘額寶轉賬的場景更復雜,在高併發的情況下,可能需要用分散式鎖,防止金額異常

相關文章