如何應對 RocketMQ 訊息堆積

碼農談IT發表於2023-12-25

來源:https://mp.weixin.qq.com/s/FBBJGsMevCOg22q0psXh9A

這篇文章,我們聊聊如何應對 RocketMQ 訊息堆積。

如何應對 RocketMQ 訊息堆積

1 基礎概念

消費者在消費的過程中,消費的速度跟不上服務端的傳送速度,未處理的訊息會越來越多,訊息出現堆積進而會造成訊息消費延遲。

雖然筆者經常講:RocketMQ 、Kafka 具備堆積的能力,但是以下場景需要重點關注訊息堆積和延遲的問題:

  1. 業務系統上下游能力不匹配造成的持續堆積,且無法自行恢復。

  2. 業務系統對訊息的消費實時性要求較高,即使是短暫的堆積造成的訊息延遲也無法接受。

2 消費原理

如何應對 RocketMQ 訊息堆積

客戶端使用 Push 模式 啟動後,消費訊息時,分為以下兩個階段:

  • 階段一:拉取訊息

    客戶端透過長輪詢批次拉取的方式從 Broker 服務端獲取訊息,將拉取到的訊息快取到本地緩衝佇列中。

    客戶端批次拉取訊息,常見內網環境下都會有很高的吞吐量,例如:1個單執行緒單分割槽的低規格機器(4C8GB)可以達到幾萬 TPS ,如果是多個分割槽可以達到幾十萬 TPS 。所以這一階段一般不會成為訊息堆積的瓶頸。

  • 階段二:消費訊息

    提交消費執行緒,客戶端將本地快取的訊息提交到消費執行緒中,使用業務消費邏輯進行處理。

    此時客戶端的消費能力就完全依賴於業務邏輯的複雜度(消費耗時)和消費邏輯併發度了。如果業務處理邏輯複雜,處理單條訊息耗時都較長,則整體的訊息吞吐量肯定不會高,此時就會導致客戶端本地緩衝佇列達到上限,停止從服務端拉取訊息。

透過以上客戶端消費原理可以看出,訊息堆積的主要瓶頸在於本地客戶端的消費能力,即消費耗時消費併發度

想要避免和解決訊息堆積問題,必須合理的控制消費耗時和訊息併發度,其中消費耗時的優先順序高於消費併發度,必須先保證消費耗時的合理性,再考慮消費併發度問題。

3 消費瓶頸

3.1 消費耗時

影響消費耗時的消費邏輯主要分為 CPU 記憶體計算和外部 I/O 操作,通常情況下程式碼中如果沒有複雜的遞迴和迴圈的話,內部計算耗時相對外部 I/O 操作來說幾乎可以忽略。

外部 I/O 操作通常包括如下業務邏輯:

  • 讀寫外部資料庫,例如 MySQL 資料庫讀寫。
  • 讀寫外部快取等系統,例如 Redis 讀寫。
  • 下游系統呼叫,例如 Dubbo 呼叫或者下游 HTTP 介面呼叫。

這類外部呼叫的邏輯和系統容量需要提前梳理,掌握每個呼叫操作預期的耗時,這樣才能判斷消費邏輯中I/O操作的耗時是否合理。

通常消費堆積都是由於這些下游系統出現了服務異常、容量限制導致的消費耗時增加。

例如:某業務消費邏輯中需要呼叫下游 Dubbo 介面 ,單次消費耗時為 20 ms,平時訊息量小未出現異常。業務側進行大促活動時,下游 Dubbo 服務未進行最佳化,消費單條訊息的耗時增加到 200 ms,業務側可以明顯感受到消費速度大幅下跌。此時,透過提升消費並行度並不能解決問題,需要大幅提高下游 Dubbo 服務效能才行。

3.2 消費併發度

絕大部分訊息消費行為都屬於 IO 密集型,即可能是運算元據庫,或者呼叫 RPC,這類消費行為的消費速度在於後端資料庫或者外系統的吞吐量,透過增加消費並行度,可以提高總的消費吞吐量,但是並行度增加到一定程度,反而會下降。

所以,應用必須要設定合理的並行度。如下有幾種修改消費並行度的方法:

  • 同一個 ConsumerGroup 下,透過增加 Consumer 例項數量來提高並行度(需要注意的是超過訂閱佇列數的 Consumer 例項無效)。可以透過加機器,或者在已有機器啟動多個程式的方式。
  • 提高單個 Consumer 例項的消費並行執行緒,透過修改引數 consumeThreadMin、consumeThreadMax 實現。

4 解決策略

當面對訊息堆積問題時,我們需要明確到底哪個環節出現問題了,不要慌張,也不要貿然動手。

4.1 確認訊息的消費耗時是否合理

首先,我們需要檢視消費耗時,確認訊息的消費耗時是否合理。檢視消費耗時一般來講有兩種方式:

1、列印日誌

public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
     try {
        for (MessageExt messageExt : msgs) {
           long start = System.currentTimeMillis();
           // TODO 業務邏輯
          logger.info("MessageId:" + messageExt.getMsgId() + " costTime:" + (System.currentTimeMillis() - start));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
     } catch (Exception e) {
        logger.error("consumeMessage error:", e);
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
     }
}

2、檢視訊息軌跡

如何應對 RocketMQ 訊息堆積

當確定好消費耗時後,可以根據耗時大小,採取不同的措施。

  • 若檢視到消費耗時較長,則需要檢視客戶端 JVM 堆疊資訊排查具體業務邏輯,並最佳化消費邏輯。
  • 若檢視到消費耗時正常,則有可能是因為消費併發度不夠導致訊息堆積,需要逐步調大消費執行緒或擴容節點來解決。

4.2 檢視客戶端 JVM 的堆疊

假如消費耗時非常高,需要檢視 Consumer 例項 JVM 的堆疊 。

  1. 透過 jps -m 或者 ps -ef | grep java 命令獲取當前正在執行的 Java 程式,透過啟動主類即可獲得應用的程式 pid ;

  2. 透過 jstack pid > stack.log 命令獲取執行緒的堆疊。

  3. 執行以下命令,檢視 ConsumeMessageThread 的資訊 。

cat stack.log | grep ConsumeMessageThread -A 10 --color

常見的異常堆疊資訊如下:

  • 示例1:空閒無堆積的堆疊

    消費空閒情況下消費執行緒都會處於 WAITING 狀態等待從消費任務隊裡中獲取訊息。

如何應對 RocketMQ 訊息堆積
  • 示例2:消費邏輯有搶鎖休眠等待等情況

    消費執行緒阻塞在內部的一個睡眠等待上,導致消費緩慢。

如何應對 RocketMQ 訊息堆積
  • 示例3:消費邏輯運算元據庫等外部儲存卡住

    消費執行緒阻塞在外部的 HTTP 呼叫上,導致消費緩慢。

    如何應對 RocketMQ 訊息堆積

5 總結

客戶端使用 Push模式 啟動後,消費訊息時,分為以下兩個階段:拉取訊息消費訊息

客戶端消費原理可以看出,訊息堆積的主要瓶頸在於本地客戶端的消費能力,即消費耗時消費併發度

首先分析消費耗時,然後根據耗時大小,採取不同的措施。

  • 若檢視到消費耗時較長,則檢視客戶端堆疊資訊排查具體業務邏輯,並最佳化消費邏輯。
  • 若檢視到消費耗時正常,則有可能是因為消費併發度不夠導致訊息堆積,需要逐步調大消費執行緒或擴容節點來解決。

參考文件:

阿里雲官方文件:

https://help.aliyun.com/zh/apsaramq-for-rocketmq/cloud-message-queue-rocketmq-4-x-series/use-cases/message-accumulation-and-latency#concept-2004064

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024924/viewspace-3001170/,如需轉載,請註明出處,否則將追究法律責任。

相關文章