Apache RocketMQ 5.0 消息進階:如何支撐複雜的業務消息場景?

一致性

首先來看 RocketMQ 的第一個特性-事務消息,事務消息是 RocketMQ 與一致性相關的特性,也是 RocketMQ 有別於其他消息隊列的最具區分度的特性。

以大規模電商系統爲例,付款成功後會在交易系統中訂單數據庫將訂單狀態更新爲已付款。然後交易系統再發送一條消息給 RocketMQ,RocketMQ 將訂單已付款的事件通知給所有下游應用,保障後續的履約環節。

但上述流程存在一個問題,交易系統寫數據庫與發消息互相分開,它不是一個事務,會出現多種異常情況,比如數據庫寫成功但消息發失敗,這個訂單的狀態下游應用接收不到,對於電商業務來說,可能造成大量用戶付款但賣家不發貨的情況;而如果先發消息成功再寫數據庫失敗,會造成下游應用認爲訂單已付款,推進賣家發貨,但是實際用戶未付款成功。這些異常都會對電商業務造成大量髒數據,產生災難性業務後果。

而 RocketMQ 事務消息的能力可以保障生產者的本地事務(如寫數據庫)、發消息事務的一致性,最後通過 Broker at least once 的消費語義,保證消費者的本地事務也能執行成功,最終實現生產者、消費者對同一業務的事務狀態達到最終一致。

一致性:事務消息-原理

如下圖所示,事務消息主要通過兩階段提交+事務補償機制結合實現。

首先生產者會發送 half 消息,也就是 prepare 消息,broker 會把 half 存到隊列中。接下來生產者執行本地事務,一般是寫數據庫,本地事務完成後,會往 RocketMQ 發送 commit 操作,RocketMQ 會把 commit 操作寫入 OP 隊列,並進行 compact,把已提交的消息寫到 ConsumeQueue 對消費者可見。反過來如果是 rollback 操作,則會跳過對應的 half 消息。

面對異常的情況,比如生產者在發送 commit 或者 rollback 之前宕機了,RocketMQ broker 還會有補償檢查機制,定期回查 Producer 的事務狀態,繼續推進事務。

無論是 Prepare 消息、還是 Commit/Rollback 消息、或者是 compact 環節,在存儲層面都是遵守 RocketMQ 以順序讀寫爲主的設計理念,達到最優吞吐量。

一致性:事務消息 demo

接下來來看一個事務消息的簡單示例。使用事務消息需要實現一個事務狀態的查詢器,這也是和普通消息一個最大的區別。如果我們是一個交易系統,這個事務回查器的實現可能就是根據訂單 ID 去查詢數據庫來確定這個訂單的狀態到底是否是提交,比如說創建成功、已付款、已退款等。主體的消息生產流程也有很多不同,需要開啓分佈式事務,進行兩階段提交,先發一個 prepare 的消息,然後再去執行本地事務。這裏的本地事務一般就是執行數據庫操作。然後如果本地事務執行成功的話,就整體 commit,把之前的 prepare 的消息提交掉。這樣一來消費者就可以消費這條消息的。如果本地事務出現異常的話,那麼就把整個事務 rollback 掉,之前的那條 prepare 的消息也會被取消掉,整個過程就回滾了。事務消息的用法變化主要體現在生產者代碼,消費者使用方式和普通消息一致,demo 裏面就不展示了。

一致性:順序消息場景+原理

RocketMQ 的第二個高級特性是順序消息,也是特色能力之一。它解決了順序一致性的問題,保障同一業務的消息,生產與消費的順序保持一致。

阿里曾有一個場景是買賣家數據庫複製,由於阿里訂單數據庫採用分庫分表技術,面向買賣家不同的業務場景,會分別按照買家主鍵與賣家主鍵拆分爲買賣家數據庫。兩個數據庫的同步採用 Binlog 順序分發的機制,通過使用順序消息,將買家庫的 Binlog 變更按照嚴格順序在賣家庫回放,以此達到訂單數據庫的一致性。如果沒有順序保障,則可能出現數據庫級別的髒數據,會帶來嚴重的業務錯誤。

順序消息的實現原理如下圖所示,充分利用 Log 天然順序讀寫的特點高效實現。

在 Broker 存儲模型中,每個 Topic 都會有固定的 ConsumeQueue,可以理解爲 Topic 的分區。生產者爲發送消息加上業務 Key,在這個 case 裏面可以用訂單 ID,同一訂單 ID 的消息會順序發送到同一個 Topic 分區,每個分區在某個時刻只會被一個消費者鎖定,消費者順序讀取同一個分區的消息串行消費,以此來達到順序一致性。

一致性:順序消息 demo

接下來來看順序消息的一個簡單 demo。對於順序消息而言,生產者與消費者都有需要注意的地方。

在生產階段,首先要定義消息的 group。每條消息都可以選擇業務 ID 作爲消息 Group,業務 ID 儘量離散、隨機。因爲同一業務 ID 會分配到同一數據存儲分片,生產與消費都在該數據分片上串行,如果業務 ID 有熱點,會造成嚴重的數據傾斜與局部消息堆積。

比如在電商交易的場景,選擇訂單 ID、買家 ID 會比較好,比較離散。如果選擇的是賣家 ID,則可能會出現熱點,熱點賣家的流量會遠大於普通賣家。

消費階段也與常規的消息收發有所區別,主要有兩種模式,分別是全託管的 push consumer 模式和半托管主動獲取消息的 simple consumer 模式。RocketMQ SDK 會保障同一分組的消息串行進入業務消費邏輯。需要注意,自身的業務消費代碼也要串行進行,然後同步返回消費成功確認。不要將同一分組的消息放到另外的線程池消併發費,會破壞順序語義。

複雜業務

複雜業務:SQL 過濾場景

RocketMQ 的第三個高級特性是 SQL 消費模式,也是複雜業務場景的剛需。

如上圖,阿里的電商業務圍繞着交易展開,有數百個不同的業務在訂閱交易消息。業務基本面向某個細分領域,都只需要交易 Topic 下的部分消息。按照傳統的模式,一般是全量訂閱交易 Topic,在消費者本地過濾即可,但這樣會消耗大量計算、網絡資源,特別是在雙十一,該方案的成本是無法接受的。

複雜業務:SQL過濾原理

爲了解決上述問題,RocketMQ 提供了 SQL 消費模式。在交易場景下,每筆訂單消息都會帶有不同維度的業務屬性,包括賣家 ID、買家 ID、類目、省市、價格、訂單狀態等屬性,而 SQL 過濾能讓消費者通過 SQL 語句過濾消費目標消息。比如,某個消費者只想關注某個價格區間內的訂單創建消息,創建訂閱關係 Topic=Trade SQL:status=ordercreate and(Price between 50 and 100),Broker 會在服務端運行 SQL 計算,只返回有效數據給消費者。

爲了提高性能,Broker 還引入了布隆過濾器模塊。在消息寫入分發時刻提前計算結果,寫入位圖過濾器,減少無效 IO。

總體而言,其本質爲將過濾鏈路不斷前置,從消費端本地過濾,到服務端寫時過濾,達到最優性能。

複雜業務:SQL 過濾 demo

接下來看一個 SQL 訂閱的示例。目前 RocketMQ SQL 過濾支持屬性非空判斷、屬性大小比較、屬性區間過濾、集合判斷與邏輯計算,能滿足絕大部分的過濾需求。

在消息生產階段,除了設置 Topic、Tag 之外,還能添加多個自定義屬性。比如在這案例裏,設置了一個 region 的屬性,表示該條消息從杭州 region 發出。消費時可根據自定義屬性來進行 SQL 過濾訂閱。第一個 case 是用了一個 filter expression,判斷 region 這個字段不爲空且等於杭州才消費。第二個 case 添加更多的條件,如果這是一筆訂單消息,還可以同時判斷 region 條件和價格區間來決定是否消費。第三個 case 是全接收模式,表達式直接爲 True,這個訂閱方式會接收某一個主題下面的全量消息,不進行任何過濾。

複雜業務:定時消息場景+原理

RocketMQ 的第四個高級特性是定時消息。

生產者可以指定某條消息在發送後經過一定時間後纔對消費者可見。有不少業務場景需要大規模的定時事件觸發,比如典型的電商場景基本都有訂單創建 30 分鐘未付款自動關閉訂單的邏輯,定時消息能爲上述場景帶來極大的便利性。

RocketMQ 的定時消息基於時間輪(TimerWheel)來實現。通過模擬錶盤轉動來達到對時間進行排序的目的。

TimerWheel 中的每一格代表最小的時間刻度,稱爲 Tick。RocketMQ 裏,每一個 Tick 爲一秒,同一時刻的消息會寫入到同一格子裏。由於每個時刻可能會同時觸發多條消息,並且每條消息的寫入時刻都不一樣,因此 RocketMQ 也同時引入了 Timerlog 的數據結構,Timerlog 按照順序 append 的方式寫入數據,每個元素都包含消息的物理索引以及指向同一時刻的前一條消息,組成邏輯鏈表。TimeWheel 的每個格子都維護該時刻的消息鏈表的頭尾指針。

TimerWheel 會有指針,代表當前時刻,繞着 TimerWheel 循環轉動,指針所指之處代表該 Tick 到期,所有內容一起彈出,寫到 ConsumeQueue,對消費者可見。

目前 RocketMQ 的定時消息性能已經遠超 RabbitMQ 與 ActiveMQ。

全局高可用

接下來再講一下 RocketMQ 的全局高可用技術解決方案。RocketMQ 的高可用架構主要指 RocketMQ 集羣內的數據多副本與服務高可用。而本文的高可用是全局的、業界常說的同城容災、兩地三中心、異地多活等架構。

目前,螞蟻支付與阿里交易均採用異地多活的架構,異地多活相對於冷備、同城容災、兩地三中心模式具備更多優點,可以應對城市級別的災難,如地震、斷電等事件。除此之外,針對一些因爲人爲操作引起的問題,比如某個基礎系統變更引入新的 bug,導致整個機房級別的不可用,異地多活架構可以直接將流量切到可用機房,優先保障業務連續性,再定位具體的問題。

另一方面,異地多活還能實現機房級別的擴容,單一機房的計算存儲資源有限,而異地多活架構可以將業務流量按照比例分散在全國各地機房。同時,多活架構實現了所有機房都提供業務服務,而不是冷備狀態,資源利用率大幅度提升。得益於多活狀態,面對極端場景的切流,可用性更有保障,信心更足。

在異地多活的架構中,RocketMQ 承擔的是基礎架構的多活能力。多活的架構分爲幾個模塊:

  • 接入層:通過統一接入層按照業務 ID 將用戶請求分散到多個機房,業務 ID 一般可採用用戶 ID。
  • 應用層:應用層一般無狀態,當請求進入某個機房後,需要儘量保障該請求的整個鏈路都在單元內封閉,包括 RPC、數據庫訪問、消息讀寫,可降低訪問延遲,保障系統性能不會因爲多活架構衰退。
  • 數據層:包括數據庫、消息隊列等有狀態系統。RocketMQ 通過 connector 組件實現按 topic 粒度實時同步消息的數據,按照 Consumer 與 Topic 的組合粒度實時同步消費狀態。
  • 全局的管控層:需要維護全局的單元化規則,分配哪些流量走到哪些機房;還需要管理多活元數據配置,哪些應用需要多活、哪些 Topic 需要多活;另外,在切流時刻需要協調所有系統的切流過程,控制切流順序。

總結

本篇文章介紹了很多 RocketMQ 的高階特性。首先是一致性的特性,這裏面就包括了順序的一致性、分佈式業務的一致性;RocketMQ 在應對大規模複雜業務的特性有 2 個,一個是 SQL 過濾訂閱,可以應對那種單一超大業務大量消費者過濾需求;還有一個是定時消息,這也是很多互聯網交易業務常見的場景。最後,介紹了 RMQ 在高階的容災能力方面的建設,提供了一個異地多活的解決方案。

作者:隆基

點擊立即免費試用雲產品 開啓雲上實踐之旅!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章