RocketMQ消息發送常見錯誤與解決方案

文將結合本身使用RocketMQ的經驗,對消息發送常見的問題進行分享,基本會遵循出現,分析問題、解決問題。web

一、No route info of this topic


沒法找到路由信息,其完整的錯誤堆棧信息以下:
緩存

且不少讀者朋友會說Broker端開啓了自動建立主題也會出現上述問題。

RocketMQ的路由尋找流程以下圖所示:
服務器

上面的核心關鍵點以下:
  • 若是Broker開啓了自動建立Topic,在啓動的時候會默認建立主題:TBW102,並會隨着Broker發送到Nameserver的心跳包彙報給Nameserver,繼而從Nameserver查詢路由信息時能返回路由信息。微信

  • 消息發送者在消息發送時首先會查本地緩存,若是本地緩存中存在,直接返回路由信息。網絡

  • 若是緩存不存在,則向Nameserver查詢路由信息,若是Nameserver存在該路由信息,就直接返回。併發

  • 若是Nameserver不存在該topic的路由信息,若是沒有開啓自動建立主題,則拋出 No route info of this topic。異步

  • 若是開啓了自動建立主題,則使用默認主題向Nameserver查詢路由信息,並使用默認Topic的路由信息爲本身的路由信息,將不會拋出 No route info of this topic。高併發

一般狀況下 No route info of this topic 這個錯誤通常是在剛搭建RocketMQ,剛入門 RocketMQ遇到的比較多,一般的排查思路以下:性能

  • 能夠經過rocketmq-console查詢路由信息是否存在,或使用以下命令查詢路由信息:學習

    cd ${ROCKETMQ_HOME}/bin
    sh ./mqadmin topicRoute -n 127.0.0.1:9876 -t dw_test_0003

    其輸出結果以下所示:

  • 若是經過命令沒法查詢到路由信息,則查看Broker是否開啓了自動建立topic,參數爲:autoCreateTopicEnable,該參數默認爲true。但在生產環境不建議開啓。

  • 若是開啓了自動建立路由信息,但仍是拋出這個錯誤,這個時候請檢查客戶端(Producer)鏈接的Nameserver地址是否與Broker中配置的nameserver地址是否一致。

通過上面的步驟,基本就能解決該錯誤。

二、消息發送超時


消息發送超時,一般客戶端的日誌以下:

客戶端報消息發送超時,一般第一懷疑的對象是RocketMQ服務器,是否是Broker性能出現了抖動,沒法抗住當前的量。

那我們如何來排查RocketMQ當前是否有性能瓶頸呢?

首先咱們執行以下命令查看RocketMQ 消息寫入的耗時分佈狀況:

cd /${USER.HOME}/logs/rocketmqlogs/
grep -n 'PAGECACHERT' store.log | more

輸出結果以下所示:

RocketMQ會每一分鐘打印前一分鐘內消息發送的耗時狀況分佈,咱們從這裏就能窺探RocketMQ消息寫入是否存在明細的性能瓶頸,其區間以下:
  • [<=0ms] 小於0ms,即微妙級別的。

  • [0~10ms] 小於10ms的個數。

  • [10~50ms] 大於10ms小

  • 於50ms的個數

其餘區間顯示,絕大多數會落在微妙級別完成,按照筆者的經驗若是100-200ms及以上的區間超過20個後,說明Broker確實存在必定的瓶頸,若是隻是少數幾個,說明這個是內存或pagecache的抖動,問題不大。

一般狀況下超時一般與Broker端的處理能力關係不大,還有另一個佐證,在RocketMQ broker中還存在快速失敗機制,即當Broker收到客戶端的請求後會將消息先放入隊列,而後順序執行,若是一條消息隊列中等待超過200ms就會啓動快速失敗,向客戶端返回[TIMEOUT_CLEAN_QUEUE]broker busy,這個在本文的第3部分會詳細介紹。

在RocketMQ客戶端遇到網絡超時,一般能夠考慮一些應用自己的垃圾回收,是否因爲GC的停頓時間致使的消息發送超時,這個我在測試環境進行壓力測試時遇到過,但生產環境暫時沒有遇到過,你們稍微留意一下。

在RocketMQ中一般遇到網絡超時,一般與網絡的抖動有關係,但因爲我對網絡不是特別擅長,故暫時沒法找到直接證據,但能找到一些間接證據,例如在一個應用中同時鏈接了kafka、RocketMQ集羣,發如今出現超時的同一時間發現鏈接到RocketMQ集羣內全部Broker,鏈接到kafka集羣都出現了超時。

但出現網絡超時,咱們總得解決,那有什麼解決方案嗎?

咱們對消息中間件的最低指望就是高併發低延遲,從上面的消息發送耗時分佈狀況也能夠看出RocketMQ確實符合咱們的指望,絕大部分請求都是在微妙級別內,故我給出的方案時,減小消息發送的超時時間,增長重試次數,並增長快速失敗的最大等待時長。具體措施以下:

  • 增長Broker端快速失敗的時長,建議爲1000,在broker的配置文件中增長以下配置:

    maxWaitTimeMillsInQueue=1000

    主要緣由是在當前的RocketMQ版本中,快速失敗致使的錯誤爲SYSTEM_BUSY,並不會觸發重試,適當增大該值,儘量避免觸發該機制,詳情能夠參考本文第3部份內容,會重點介紹system_busy、broker_busy。

  • 若是RocketMQ的客戶端版本爲4.3.0如下版本(不含4.3.0)
    將超時時間設置消息發送的超時時間爲500ms,並將重試次數設置爲6次(這個能夠適當進行調整,儘可能大於3),其背後的哲學是儘快超時,並進行重試,由於發現局域網內的網絡抖動是瞬時的,下次重試的是就能恢復,而且RocketMQ有故障規避機制,重試的時候會盡可能選擇不一樣的Broker,相關的代碼以下:

    DefaultMQProducer producer = new DefaultMQProducer("dw_test_producer_group");
    producer.setNamesrvAddr("127.0.0.1:9876");
    producer.setRetryTimesWhenSendFailed(5);// 同步發送模式:重試次數
    producer.setRetryTimesWhenSendAsyncFailed(5);// 異步發送模式:重試次數
    producer.start();
    producer.send(msg,500);//消息發送超時時間
  • 若是RocketMQ的客戶端版本爲4.3.0及以上版本

    若是客戶端版本爲4.3.0及其以上版本,因爲其設置的消息發送超時時間爲全部重試的總的超時時間,故不能直接經過設置RocketMQ的發送API的超時時間,而是須要對其API進行包裝,重試須要在外層收到進行,例如示例代碼以下:

    public static SendResult send(DefaultMQProducer producer, Message msg, int 
                                retryCount)
     
    {
      Throwable e = null;
      for(int i =0; i < retryCount; i ++ ) {
          try {
              return producer.send(msg,500); //設置超時時間,爲500ms,內部有重試機制
          } catch (Throwable e2) {
              e = e2;
          }
      }
      throw new RuntimeException("消息發送異常",e);
    }

三、System busy、Broker busy


在使用RocketMQ中,若是RocketMQ集羣達到1W/tps的壓力負載水平,System busy、Broker busy就會是你們常常會遇到的問題。例如以下圖所示的異常棧。

縱觀RocketMQ與system busy、broker busy相關的錯誤關鍵字,總共包含以下5個:

  • [REJECTREQUEST]system busy

  • too many requests and system thread pool busy

  • [PC_SYNCHRONIZED]broker busy

  • [PCBUSY_CLEAN_QUEUE]broker busy

  • [TIMEOUT_CLEAN_QUEUE]broker busy

3.1 原理分析

咱們先用一張圖來闡述一下在消息發送的全生命週期中分別在何時會拋出上述錯誤。

根據上述5類錯誤日誌,其觸發的原有能夠概括爲以下3種。
  • pagecache壓力較大

    其中以下三類錯誤屬於此種狀況

  • [REJECTREQUEST]system busy

  • [PC_SYNCHRONIZED]broker busy

  • [PCBUSY_CLEAN_QUEUE]broker busy

    判斷pagecache是否忙的依據就是在寫入消息時,在向內存追加消息時加鎖的時間,默認的判斷標準是加鎖時間超過1s,就認爲是pagecache壓力大,向客戶端拋出相關的錯誤日誌。

  • 發送線程池擠壓的拒絕策略
    在RocketMQ中處理消息發送的是一個只有一個線程的線程池,內部會維護一個有界隊列,默認長度爲1W,若是當前隊列中擠壓的數量超過1w,執行線程池的拒絕策略,從而拋出[too many requests and system thread pool busy]錯誤。

  • Broker端快速失敗

    默認狀況下Broker端開啓了快速失敗機制,就是在Broker端還未發生pagecache繁忙(加鎖超過1s)的狀況,但存在一些請求在消息發送隊列中等待200ms的狀況,RocketMQ會再也不繼續排隊,直接向客戶端返回system busy,但因爲rocketmq客戶端目前對該錯誤沒有進行重試處理,因此在解決這類問題的時候須要額外處理。

3.2 PageCache繁忙解決方案

一旦消息服務器出現大量pagecache繁忙(在向內存追加數據加鎖超過1s)的狀況,這個是比較嚴重的問題,須要人爲進行干預解決,解決的問題思路以下:

  • transientStorePoolEnable

    開啓transientStorePoolEnable機制,即在broker中配置文件中增長以下配置:

    transientStorePoolEnable=true

    transientStorePoolEnable的原理以下圖所示:

 引入transientStorePoolEnable能緩解pagecache的壓力背後關鍵以下:
  • 消息先寫入到堆外內存中,該內存因爲啓用了內存鎖定機制,故消息的寫入是接近直接操做內存,性能能獲得保證。

  • 消息進入到堆外內存後,後臺會啓動一個線程,一批一批將消息提交到pagecache,即寫消息時對pagecache的寫操做由單條寫入變成了批量寫入,下降了對pagecache的壓力。

    引入transientStorePoolEnable會增長數據丟失的可能性,若是Broker JVM進程異常退出,提交到PageCache中的消息是不會丟失的,但存在堆外內存(DirectByteBuffer)中但還未提交到PageCache中的這部分消息,將會丟失。但一般狀況下,RocketMQ進程退出的可能性不大,一般狀況下,若是啓用了transientStorePoolEnable,消息發送端須要有從新推送機制(補償思想)。

  • 擴容

    若是在開啓了transientStorePoolEnable後,還會出現pagecache級別的繁忙,那須要集羣進行擴容,或者對集羣中的topic進行拆分,即將一部分topic遷移到其餘集羣中,下降集羣的負載。


舒適提示:在RocketMQ出現pagecache繁忙形成的broker busy,RocketMQ Client會有重試機制。

3.3 TIMEOUT_CLEAN_QUEUE 解決方案

因爲若是出現TIMEOUT_CLEAN_QUEUE的錯誤,客戶端暫時不會對其進行重試,故現階段的建議是適當增長快速失敗的判斷標準,即在broker的配置文件中增長以下配置:

#該值默認爲200,表示200ms
waitTimeMillsInSendQueue=1000

本文來自筆者的另外一力做《RocketMQ實戰與進階》,專欄從使用場景入手介紹如何使用 RocketMQ,使用過程當中遇到什麼問題,如何解決這些問題,以及爲何能夠這樣解決,即原理講解(圖)穿插在實戰中。專欄的設計思路重在強調實戰二字,旨在讓一位 RocketMQ 初學者經過對本專欄的學習,快速「打怪升級」,理論與實戰結合,成爲該領域的佼佼者。

本文分享自微信公衆號 - 瓜農老梁(gh_01130ae30a83)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索