記一次RocketMQ消費非順序訊息引起的線上事故

[傾盡伊人]發表於2024-06-30

應用場景

C端使用者提交工單、工單建立完成之後、會發布一條工單建立完成的訊息事件(非同步訊息)、MQ消費者收到訊息之後、會通知各處理器處理該訊息、各處理器處理完後都會發布一條將該工單寫入搜尋引擎的訊息、最終該工單出現在搜尋引擎、被工單處理人檢索和處理。

事故異常體現

1、異常體現

從工單的流轉記錄發現、工單的狀態從A->C->B、理論上 工單的狀態只能從A->B->C。此處能得出兩個結論、1、各處理器處理完工單之後、狀態有誤;2、寫入到搜尋引擎的工單資料被本該更早寫入引擎的資料覆蓋了。

從監控資料發現、結論1排除。

2、背景解釋

一個工單建立完之後、會經歷

  • 工單建立完城-->狀態新建、將新工單資訊更新至ES(搜尋引擎)
  • 工單內容稽核-->狀態已稽核、將新工單資訊更新至ES
  • 工單分配給指定工作人員-->狀態已分配、將最新工單資訊更新至ES
  • 其他操作-->狀態改變-->將最新工單資訊更新至ES

說明:
所有工單的操作都是非同步的、沒有固定順序。
保證點:
寫到ES之前從資料庫所獲取的工單資訊都是最新的工單資訊、無誤。
案例中異常情況:
工單實際已經分配了工作人員(已分配狀態)、可以查詢到被分配的人、但是工單的狀態顯示是新建狀態。

3、事故異常分析

1、創單的順序是優先於派單的
2、創單之後丟擲一個寫ES的MQ訊息--訊息A
3、派單之後也會丟擲一個寫ES的MQ訊息--訊息B
4、如果MQ是有順序的、按照順序消費訊息、MQ消費者消費第一個訊息(訊息A)肯定是比第二份訊息(訊息B)要快的、正常情況、工單是絕對沒有問題。
(非正常情況、可能是ES自身寫訊息到叢集、有快慢之分、第二個訊息會先寫完、此種情況基本忽略不計)

5、此處異常是、MQ訊息消費無序

  正常拿到訊息A、查詢資料庫的狀態正確、可以推送ES訊息、再接著拿到訊息B、查詢資料庫的狀態正確、可以推送ES訊息、因為推送ES沒有加分散式鎖、導致訊息B那個時刻的工單資料被先寫入ES、訊息A那個時刻的工單資料後寫入ES、導致ES的資料被覆蓋、最終ES的最新版本的工單狀態和資料庫的工單狀態不一致。

4、解決方式

背景說明(續):寫ES的MQ訊息可以理解為併發出現、在工單建立的那一刻、所有改動工單狀態的訊息基本會在ms級別時間內到達。
前提:在寫ES的MQ訊息中、攜帶更新完工單之後該工單的時間戳、作為工單的版本號。

方式一:

  暴力方式、直接在寫ES的介面新增分散式鎖、透過對比訊息的版本號和工單資料庫中的版本號、即可判定訊息要捨棄還是寫入ES(不採取、會嚴重降低寫ES的效率)
方式二:
  僅對工單使用分散式鎖、同時、在一定時間內(秒級)收集寫ES的訊息、並且對訊息進行排序過濾、僅處理最新版本的工單訊息寫入ES。

5、感想

1、有序的MQ訊息資源會比較貴,還是要程式碼層保證資料穩定性。
2、驗證異常論斷的方式是、第一個大膽猜想、第二個必須保證有日誌可追蹤查詢論證。

相關文章