訊息佇列的七種經典應用場景

勇哥编程游记發表於2024-03-28

在筆者心中,訊息佇列快取分庫分表是高併發解決方案三劍客。

在職業生涯中,筆者曾經使用過 ActiveMQ 、RabbitMQ 、Kafka 、RocketMQ 這些知名的訊息佇列 。

這篇文章,筆者結合自己的真實經歷,和大家分享訊息佇列的七種經典應用場景。

1 非同步&解耦

筆者曾經負責某電商公司的使用者服務,該服務提供使用者註冊,查詢,修改等基礎功能。使用者註冊成功之後,需要給使用者傳送簡訊。

圖中,新增使用者傳送簡訊都揉在使用者中心服務裡,這種方式缺點非常明顯:

  1. 簡訊渠道不夠穩定,傳送簡訊會達到 5 秒左右,這樣使用者註冊介面耗時很大,影響前端使用者體驗;

  2. 簡訊渠道介面發生變化,使用者中心程式碼就必須修改了。但使用者中心是核心系統。每次上線都必要謹小慎微。這種感覺很彆扭,非核心功能影響到核心系統了。

為了解決這個問題,筆者採用了訊息佇列進行了重構。

  • 非同步

    使用者中心服務儲存使用者資訊成功後,傳送一條訊息到訊息佇列 ,立即將結果返回給前端,這樣能避免總耗時比較長,從而影響使用者的體驗的問題。

  • 解耦

    任務服務收到訊息呼叫簡訊服務傳送簡訊,將核心服務與非核心功能剝離,顯著的降低了系統間的耦合度。

2 消峰

高併發場景下,面對突然出現的請求峰值,非常容易導致系統變得不穩定,比如大量請求訪問資料庫,會對資料庫造成極大的壓力,或者系統的資源 CPU 、IO 出現瓶頸。

筆者曾服務於神州專車訂單團隊,在訂單的載客生命週期裡,訂單的修改操作先修改訂單快取,然後傳送訊息到 MetaQ ,訂單落盤服務消費訊息,並判斷訂單資訊是否正常(比如有無亂序),若訂單資料無誤,則儲存到資料庫中。

當面對請求峰值時,由於消費者的併發度在一個閾值範圍內,同時消費速度相對均勻,因此不會對資料庫造成太大的影響,同時真正面對前端的訂單系統生產者也會變得更穩定。

3 訊息匯流排

所謂匯流排,就是像主機板裡的資料匯流排一樣, 具有資料的傳遞和互動能力,各方不直接通訊,使用匯流排作為標準通訊介面

筆者曾經服務於某彩票公司訂單團隊,在彩票訂單的生命週期裡,經過建立,拆分子訂單,出票,算獎等諸多環節。
每一個環節都需要不同的服務處理,每個系統都有自己獨立的表,業務功能也相對獨立。假如每個應用都去修改訂單主表的資訊,那就會相當混亂了。

因此,公司的架構師設計了排程中心的服務,排程中心維護訂單的資訊,但它不與子服務通訊,而是透過訊息佇列和出票閘道器,算獎服務等系統傳遞和交換資訊。

訊息匯流排這種架構設計,可以讓系統更加解耦,同時也可以讓每個系統各司其職。

4 延時任務

使用者在美團 APP 下單,假如沒有立即支付,進入訂單詳情會顯示倒數計時,如果超過支付時間,訂單就會被自動取消。

非常優雅的方式是:使用訊息佇列的延時訊息

訂單服務生成訂單後,傳送一條延時訊息到訊息佇列。訊息佇列在訊息到達支付過期時間時,將訊息投遞給消費者,消費者收到訊息之後,判斷訂單狀態是否為已支付,假如未支付,則執行取消訂單的邏輯。

RocketMQ 4.X 生產者傳送延遲訊息程式碼如下:

Message msg = new Message();
msg.setTopic("TopicA");
msg.setTags("Tag");
msg.setBody("this is a delay message".getBytes());
//設定延遲level為5,對應延遲1分鐘
msg.setDelayTimeLevel(5);
producer.send(msg);

RocketMQ 4.X 版本預設支援 18 個 level 的延遲訊息, 透過 broker 端的 messageDelayLevel 配置項確定的。

RocketMQ 5.X 版本支援任意時刻延遲訊息,客戶端在構造訊息時提供了 3 個 API 來指定延遲時間或定時時間。

5 廣播消費

廣播消費:當使用廣播消費模式時,每條訊息推送給叢集內所有的消費者,保證訊息至少被每個消費者消費一次。

廣播消費主要用於兩種場景:訊息推送快取同步

01 訊息推送

下圖是專車的司機端推送機制,使用者下單之後,訂單系統生成專車訂單,派單系統會根據相關演算法將訂單派給某司機,司機端就會收到派單推送訊息。

推送服務是一個 TCP 服務(自定義協議),同時也是一個消費者服務,訊息模式是廣播消費。

司機開啟司機端 APP 後,APP 會透過負載均衡和推送服務建立長連線,推送服務會儲存 TCP 連線引用 (比如司機編號和 TCP channel 的引用)。

派單服務是生產者,將派單資料傳送到 MetaQ , 每個推送服務都會消費到該訊息,推送服務判斷本地記憶體中是否存在該司機的 TCP channel , 若存在,則透過 TCP 連線將資料推送給司機端。

02 快取同步

高併發場景下,很多應用使用本地快取,提升系統效能 。

本地快取可以是 HashMap 、ConcurrentHashMap ,也可以是快取框架 Guava Cache 或者 Caffeine cache 。

如上圖,應用A啟動後,作為一個 RocketMQ 消費者,訊息模式設定為廣播消費。為了提升介面效能,每個應用節點都會將字典表載入到本地快取裡。

當字典表資料變更時,可以透過業務系統傳送一條訊息到 RocketMQ ,每個應用節點都會消費訊息,重新整理本地快取。

6 分散式事務

以電商交易場景為例,使用者支付訂單這一核心操作的同時會涉及到下游物流發貨、積分變更、購物車狀態清空等多個子系統的變更。

1、傳統XA事務方案:效能不足

為了保證上述四個分支的執行結果一致性,典型方案是基於 XA 協議的分散式事務系統來實現。將四個呼叫分支封裝成包含四個獨立事務分支的大事務。基於 XA 分散式事務的方案可以滿足業務處理結果的正確性,但最大的缺點是多分支環境下資源鎖定範圍大,併發度低,隨著下游分支的增加,系統效能會越來越差。

2、基於普通訊息方案:一致性保障困難

該方案中訊息下游分支和訂單系統變更的主分支很容易出現不一致的現象,例如:

  • 訊息傳送成功,訂單沒有執行成功,需要回滾整個事務。
  • 訂單執行成功,訊息沒有傳送成功,需要額外補償才能發現不一致。
  • 訊息傳送超時未知,此時無法判斷需要回滾訂單還是提交訂單變更。

3、基於 RocketMQ 分散式事務訊息:支援最終一致性

上述普通訊息方案中,普通訊息和訂單事務無法保證一致的原因,本質上是由於普通訊息無法像單機資料庫事務一樣,具備提交、回滾和統一協調的能力。

而基於 RocketMQ 實現的分散式事務訊息功能,在普通訊息基礎上,支援二階段的提交能力。將二階段提交和本地事務繫結,實現全域性提交結果的一致性。

RocketMQ 事務訊息是支援在分散式場景下保障訊息生產和本地事務的最終一致性。互動流程如下圖所示:

1、生產者將訊息傳送至 Broker 。

2、Broker 將訊息持久化成功之後,向生產者返回 Ack 確認訊息已經傳送成功,此時訊息被標記為"暫不能投遞",這種狀態下的訊息即為半事務訊息

3、生產者開始執行本地事務邏輯

4、生產者根據本地事務執行結果向服務端提交二次確認結果( Commit 或是 Rollback ),Broker 收到確認結果後處理邏輯如下:

  • 二次確認結果為 Commit :Broker 將半事務訊息標記為可投遞,並投遞給消費者。
  • 二次確認結果為 Rollback :Broker 將回滾事務,不會將半事務訊息投遞給消費者。

5、在斷網或者是生產者應用重啟的特殊情況下,若 Broker 未收到傳送者提交的二次確認結果,或 Broker 收到的二次確認結果為 Unknown 未知狀態,經過固定時間後,服務端將對訊息生產者即生產者叢集中任一生產者例項發起訊息回查

  1. 生產者收到訊息回查後,需要檢查對應訊息的本地事務執行的最終結果。
  2. 生產者根據檢查到的本地事務的最終狀態再次提交二次確認,服務端仍按照步驟4對半事務訊息進行處理。

7 資料中轉樞紐

近10多年來,諸如 KV 儲存(HBase)、搜尋(ElasticSearch)、流式處理(Storm、Spark、Samza)、時序資料庫(OpenTSDB)等專用系統應運而生。這些系統是為單一的目標而產生的,因其簡單性使得在商業硬體上構建分散式系統變得更加容易且價效比更高。

通常,同一份資料集需要被注入到多個專用系統內。

例如,當應用日誌用於離線日誌分析時,搜尋單個日誌記錄同樣不可或缺,而構建各自獨立的工作流來採集每種型別的資料再匯入到各自的專用系統顯然不切實際,利用訊息佇列 Kafka 作為資料中轉樞紐,同份資料可以被匯入到不同專用系統中。

日誌同步主要有三個關鍵部分:日誌採集客戶端,Kafka 訊息佇列以及後端的日誌處理應用。

  1. 日誌採集客戶端,負責使用者各類應用服務的日誌資料採集,以訊息方式將日誌“批次”“非同步”傳送Kafka客戶端。
    Kafka客戶端批次提交和壓縮訊息,對應用服務的效能影響非常小。
  2. Kafka 將日誌儲存在訊息檔案中,提供持久化。
  3. 日誌處理應用,如 Logstash,訂閱並消費Kafka中的日誌訊息,最終供檔案搜尋服務檢索日誌,或者由 Kafka 將訊息傳遞給 Hadoop 等其他大資料應用系統化儲存與分析。


如果我的文章對你有所幫助,還請幫忙點贊、在看、轉發一下,你的支援會激勵我輸出更高質量的文章,非常感謝!

相關文章