互聯網那些事 | MQ數據丟失

来源:https://www.cnblogs.com/evan-liang/archive/2020/01/23/12233902.html
-Advertisement-
Play Games

互聯網那些事 | MQ數據丟失 本系列故事的所有案例和解決方案只是筆者以前在互聯網工作期間的一些事例,僅供大家參考,實際操作應該根據業務和項目情況設計,歡迎大家留言提出寶貴的意見 背景 小王和小明分別維護分散式系統中A、b兩個服務,有一個場景是 A服務會向B服務通過MQ發送事件並且推送用戶信息,然後 ...


 

互聯網那些事 | MQ數據丟失

本系列故事的所有案例和解決方案只是筆者以前在互聯網工作期間的一些事例,僅供大家參考,實際操作應該根據業務和項目情況設計,歡迎大家留言提出寶貴的意見

背景

小王和小明分別維護分散式系統中A、b兩個服務,有一個場景是 A服務會向B服務通過MQ發送事件並且推送用戶信息,然後B服務保存用戶信息。
在這裡插入圖片描述

有一天,小王和小明因為一件事討論得熱火朝天、互不相讓,事情由來如下:

  • 風控部的童鞋找小明說在B服務的資料庫找不到一些用戶資料
  • 小明經過排查,B服務表裡確實沒有這批用戶的數據,在日誌里偶爾看到了一些Redis連接超時異常,小明想小王手動幫忙重推試試
  • 小王經過排查,確保自己已經成功推送了那幾個用戶的數據,並且推送的時候A服務並沒有發現MQ異常,覺得自己沒有義務去幫忙重推,應該小明自己解決

這時候,在一旁掃地的清潔工老梁過來調解,並幫忙排查分析,導致這個問題的主要原因如下:

  • B服務在接受MQ的處理類捕獲了異常,因為異常並沒有拋出,所以框架預設自動回覆了ACK,MQ認為已經消費者處理成功,就不再重覆投放到隊列,但此時方法體內因為工具包出現Redis連接超時,拋出異常,導致消息並沒有被正常處理

偽代碼如下:

    @RabbitHandler
    public void handle(byte[] message) {
        try {
            t = parseBody(messageStr);
        } catch (Exception e) {
            log.error("消費消息失敗", e.getCause());
        }
    }

    private void handleMessage(T t) throws MQHandleException {
	    //唯一標識
        String key = t.getLockedId();
        //獲取鎖
        DistributedLock lock = DistributedLockFactory.getLock(key);
        try {
            // 解決分散式服務提交相同資料併發問題
            lock.lock(CacheConstants.LOCK_WAIT_TIME, CacheConstants.LOCK_LEASE_TIME, CacheConstants.DEFAULT_CACHE_UNIT);
            // 處理業務邏輯
            handleBusinessLogic(t);
        } catch (LockException e) {
            throw new MQHandleException(e);
        } finally {
            // 釋放鎖
            lock.unLock();
        }
    }

  • 頻繁Redis超時是因為A、B服務共用一個Redis,A服務Key太多把Redis記憶體資源占滿了(也可能連接占滿),導致了B服務經常出現連接超時(該故障不是本章主要關註目標)

  • B服務在已經成功接受到消息後,沒有把消息先保存起來,所以也導致了自身並沒有能力重跑

清潔工老梁跟小王和小明進行一番詳談後,瞭解到他們主要需求有兩個:

  • B服務儘可能自己重新消費信息,而不是一昧依賴A服務手動重推
  • B服務對已接收到的消息,能自己重新消費,當然,這裡指的是有意義的消息,如果一些本身A服務推送過來的消息就是有問題的,例如格式錯誤之類的,這些B服務可以要求A重推

解決思路

經過上面的分析,老梁的解題思路主要分為兩個方向:

  • B服務建立自己的本地異常消息事件表。
  • B服務做異常分類,只對可以重跑的消息事件進行重跑

本地異常消息事件表

一般來說,常見的微服務架構實現最終一致性有三種模式:可靠事件模式、業務補償模式、TCC模式。這裡AB服務是通過業務補償模式實現最終一致性,但這裡又跟我們一般的分散式架構的事務問題不同,這裡我們只需要保證B服務能最終把正常消息事件消費成功即可。

實現思路:

  • 建立一張本地異常消息事件表,為了避免太多資料庫IO操作,這裡只會記錄異常事件
  • 提取一個通用消息處理層,統一保存異常消息事件,併進行狀態更新
  • 提取一個事件恢復模塊,統一對失敗事件進行追蹤
  • 對於重跑仍失敗消息事件,設置一個重跑次數上限,進行自動重跑,可以通過調度任務去做(事件恢復模塊),當重跑多次仍然失敗(像網路異常和資料庫異常之類,短時間不會被修複),則後期進行人工重跑

表設計


針對於B服務,對於收到的MQ信息沒有進行有效的記錄,而且MQ信息處理之後,存在修改錯誤,沒法進行對應信息補充修複的功能,增加通用消息處理層,進行消息體的記錄和回溯。 在獲取消息之後進行一次記錄,進行冪等操作和對應的狀態更新, 消息狀態在業務相關操作完成後,標記為處理完成,認為對應消息狀態結束。

這裡hash_value是對請求體進行hash計算得出來的一個值,例如:MD5、SHA-2,保證每個不同請求的hash碼不一樣,相同的請求hash碼相同,可以用於冪等控制。

表大致操作流程:

異常消息狀態設計

異常消息有4個狀態

  • 待處理 當系統消費失敗時,會對特定的異常插入異常事件表,初始狀態為 待處理
  • 處理中 當失敗恢復模塊開始執行任務時會把當前異常事件狀態設置為 處理中
  • 處理完成 當失敗事件重跑成功後,會把當前異常事件狀態設置為 處理完成
  • 異常 當失敗事件重跑超過上限次數後,會把當前異常事件狀態設置為 異常,等待後期人工重跑

事件恢復模塊

失敗事件隊列在這裡是採用資料庫表代替


異常分類

因為並非所有的異常都能重跑就能解決問題,我們只能針對可以修複的異常進行重試,這裡把異常分為兩大類:

  • 可修複異常:可修複異常指的是可以通過重跑解決的異常,如:資料庫超時、資料庫缺少欄位、Redis獲取鎖失敗、處理邏輯有問題導致信息缺失、系統升級導致消費失敗、網路問題、伺服器不穩定等引起。
    • 可立即修複異常:指一些可以通過立即重試就能恢復的異常。例如短暫的網路中斷引起的異常,一般可以在功能代碼級進行立即重試,可以使用spring-retry等組件
    • 延遲修複異常:指一些短時間內不能立即恢復的異常,需要延遲執行,等待故障修複。例如依賴的下游系統正在升級,導致一段時間服務介面中斷不可以用,需要等待服務啟動才能使用,一般通過定時任務設定一定時間間隔或者重跑次數去解決
    • 人工修複異常:指系統沒辦法直接修複,出現了一些未知異常或者短時間內不可解決的異常,例如Redis宕掉無法預知修複時間、上線時腳本遺漏導致表裡缺少欄位等,需要人工干預進行重跑,一般通過後臺管理頁面操作
  • 不可修複異常:不可修複異常指不能通過重跑就能解決的異常。如:上游系統傳輸格式有問題、消息事件內容本身有誤等引起的異常,這些即使重跑也解決不了問題,應該要從上游系統或者根源去解決。

B服務異常處理流程

最後小明負責的B服務按照老梁的思路,重新調整了代碼,異常處理流程如下:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在我還沒開始吧學習記錄記錄在博客上時,我曾做過一張關於前端工程師的腦圖 當時我還是一個連數據結構都沒學過的小萌新,空有一腔熱血,如今我不僅學完了c語言版的數據結構(感覺收穫很大,考的還湊合), 還把JavaScript的數據結構與演算法學了一遍,再看這張圖時,已經不像當初好多都看不懂,現在下麵這張圖已 ...
  • 面向對象和麵向過程是兩種不同描述世界的方法。 面向過程:世界視為過程,世界由一個個相互關聯的小程式構建來的,是精密的。 但是構成一個系統的因素太多,要把所有可能的因素都考慮到,把所有因素的因果分析都分析清楚,再把這個過程模擬出來實在是太困難了。 面向對象:世界視為對象,世界由一個個相互獨立、相互之間 ...
  • 代理模式 使用者無權訪問目標對象 中間加代理,通過代理授權和控制 傳統 UML 類圖 JavaScript 中的代理模式 應用場景 網頁代理事件 jQuery \$.proxy es6 Proxy 明星和經紀人的關係 設計原則驗證 代理類和目標類分離,隔離開目標類和使用者 符合開放封閉原則 代理模式 ...
  • 裝飾器模式 為對象添加新功能 不改變其原有的結構和功能 傳統 UML 類圖 javascript 中的裝飾器 裝飾類 裝飾器原理 裝飾類 mixin 示例 裝飾方法 one javascript function readonly(target, name, descriptor) { // des ...
  • 適配器模式 舊介面格式和使用者不相容 中間加一個適配轉換介面 傳統的UML類圖 javascripy中的UML類圖 javascript class Adaptee { specificRequest(){ return '德國標準的插頭' } } class Target { constructo ...
  • 單例模式 系統中被唯一使用 一個類只有一個實例 傳統的UML類圖 + 單例模式需要用到java的特性(private) + es6中沒有(typescript除外) + 只能用Java來寫純粹的UML類圖上的單例模式 javascript中的單例模式 應用場景 jQuery 模擬登錄框 設計原則驗證 ...
  • 工廠模式 + 將new操作單獨封裝 + 遇到new時,就要考慮該是否使用工廠模式 示例 當你去購買漢堡,直接點餐,取餐,不會自己親手做 商店要“封裝”做漢堡的工作,做好直接賣給買者 工廠模式應用場景 jQuery $('div') vue非同步組件 設計原則驗證 + 構造函數和創建者分離 + 符合開飯 ...
  • 面向對象 why? 1.程式執行:順序,判斷,迴圈, 結構化 2.面向對象 數據結構化 3.面向電腦,結構化的才是最簡單的 4.變成應該 簡單&抽象 一個基本的類 javascript class People { constructor(name, age) { this.name = name ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...