貞炸了!上線之後,訊息收不到了!

樓下小黑哥發表於2020-11-24

hello,各位小夥伴們,上午好~

昨晚生產系統機房切換,又度過了一個不眠之夜。趁著這段無聊時間,分享一下前一段時間 RocketMQ 踩坑經歷

太慘了!!!早上剛躺下睡了兩小時,就被一通電話僥倖起來檢視問題。

前言

事情是這樣的,前端時間我們有個新業務上線,這個業務需要監聽支付成功的 mq 訊息,然後向繫結的音響推送訊息。這樣使用者在支付完成之後,商家端就就可以收到收款播報。

起初我們在測試環境的測試的時候,一切流程非常順利,沒有任何問題。但是等到我們釋出上線之後,卻出現了問題。

一筆支付成功之後,音響沒有發出收款成功的播報。一切流程排查下來之後,這才發現原來 MQ 消費端沒有正常在消費訊息。

開始排查問題,第一想到的是消費端是不是釋出失敗了,但是檢視相關日誌,並沒有任何異常。

登入 MQ 控制檯,嘗試手動重新發布訊息,神奇的事來了,消費端成功收到訊息

總結現在的問題,下文開始排查。

  1. MQ 消費端應用沒有異常,但是無法正常消費
  2. MQ 控制檯傳送訊息,消費端可以成功消費訊息

排查問題

剛開始排查的時候,由於沒有任何異常業務日誌可以定位問題,所以問題排查起來十分困難。

排查了兩天了,想過各種問題。比如當前消費端使用 RocketMQ 客戶端版本比較高,是不是版本相容性導致的問題呢?

於是降低消費端的版本,重新發布之後,問題依然存在。

沒辦法,只好使用 Google 大法了。

通過搜尋發現,原來預設情況下 rockmq 客戶端的日誌將會單獨列印輸出,日誌檔案位置如下:

${user.home}/logs/rocketmqlogs

下圖為當時的日誌截圖:

可以看到消費端嘗試連線一個 20878 的埠,但是由於網路問題,一直連線失敗。

那這個 20878 是什麼埠?

我們並沒有主動配置這個埠,但是 rocketmq broker 配置的埠為 20880。

搜尋發現,原來 rocketmq broker 預設將會啟動三個通訊埠:

第一個是 rocketmq broker 配置檔案上配置的埠,預設埠為 10911,這裡我們修改成了 20880。

第二個是 rockemq broker vip 通道埠,這個埠將會在第一個埠基礎上減 2,即 20878。

第三個是 rockemq broker 使用者主從資料同步的埠,這個埠將會在第一個埠基礎上加 1,即 20881。

大概知道問題,解決辦法就很簡單了,要麼防火牆開啟 29878 網路埠的限制,要麼關閉使用 vip 埠。

RocketMQ 客戶端提供兩種方式關閉使用 vip 埠。

  1. 程式碼主動禁止使用 vip 埠,配置如下:
## 消費端
DefaultMQPushConsumer#setVipChannelEnabled(false)
## 生產端
DefaultMQProducer#setVipChannelEnabled(false);
  1. 設定 JVM 引數,禁用 vip 埠
-Dcom.rocketmq.sendMessageWithVIPChannel=false

原始碼分析

雖然問題解決了,但是上述問題本質原因還沒有找到。所以這次我們就從原始碼出發,追本溯源。

為什麼 vip 埠網路不通將會導致消費者不能正常消費?

從 rocketmq 錯誤日誌,我們可以看到報錯程式碼位於 RebalanceService 類中。

這裡主要用來執行 topic Rebalance(重平衡)。

首先我們來了解一下,Rebalance 目的是為什麼了。

假設當前 rocketmq broker 端存在一個 topic ,擁有四個佇列,關係如下:

此時如果有一個消費者使用叢集模式消費訊息,那麼它將需要負責消費所有佇列中的訊息。

rocketmq 消費者-第 1 頁 的副本

當我們再增加一個消費者消費訊息時,此時消費端將會自動進行重平衡,預設情況下將會使用平均分配原則。

可以看到 Rebalance 機制可以提升的訊息的並行處理機制。

rocketmq 消費端啟動時竟會觸發 Rebalance 機制。接著,我們根據原始碼主要看下 Rebalance 主流程,程式碼位於RebalanceImpl#rebalanceByTopic

通常我們使用叢集消費模式,所以這裡主要看叢集模式下 Rebalance 過程。

上述程式碼整體流程如下:

  1. 首先獲取 Rebalance 過程所需後設資料,包括 Topic 下的佇列資訊集合以及消費者組下的消費者例項 id 資訊集合
  2. 兩者都存在的情況下,將會按照一定策略將佇列資訊分配給每個消費者,預設按照 AllocateMessageQueueAveragely,即平均分配原則
  3. 將預分配結果嘗試更新 ProcessQueue Table,如果有更新將會把新的佇列在加入非同步消費流程。

後續訊息流程就不看原始碼,比較複雜,網上找了一張訊息消費流程圖:

來自:https://blog.csdn.net/binzhaomobile/article/details/75004190

可以看到,由於網路埠問題,無法正常獲取所有消費者 ID 集合,這就導致無法正常分配佇列資訊。

 List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);

由於未被分配任一佇列,消費端程式也就業務無法正常拉取訊息。

為什麼 mq 控制檯重新傳送的訊息消費者可以收到?

rocketmq 控制檯重新傳送訊息程式碼如下:

MessageService 將會把訊息的後設資料封裝一個CONSUME_MESSAGE_DIRECTLY型別的請求,接著呼叫 rocketmq 提供的 admin API,給 rocketmq broker 傳送請求。

broker 端收到請求之後,將會查詢訊息,然後再向消費端發起 CONSUME_MESSAGE_DIRECTLY 請求。消費端接受到訊息請求之後,將會直接訊息這條訊息。

為什麼 broker 將會啟動兩個埠?

rocketmq broker 雖然啟動了兩個埠,但是從 rocketmq broker 的原始碼可以發現這兩個埠啟動之後起到作用是一樣的。

那為什麼開啟兩個監聽埠那?我想很多同學應該也有這個疑惑,這裡給出一個開發者解釋答案。

https://github.com/apache/rocketmq/issues/1510

普通的埠將會承載所有訊息網路請求,如果此時請求非常繁忙,broker 端的所有 I/O 執行緒可能都在執行請求,這就會導致後續網路請求進入佇列,從而導致訊息請求執行緩慢。

這對於生產者來說,可能是一個致命的問題,因為訊息生產者通常訊息傳送延時要低。

這種情況下,我們就可以將訊息傳送到 VIP 埠,從而降低訊息傳送的延時。

預設情況下,rocketmq 客戶端的 vipChannel 配置為 true

private boolean vipChannelEnabled = Boolean.parseBoolean(System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "true"));

生產者的傳送訊息,消費者獲取後設資料資訊等請求預設將會使用 vip 埠。

不過這裡需要注意一點,消費者拉取訊息,將不會使用vip 埠。

雖然這個設計很巧妙,但是說實話個人覺得這個配置許可權應該交給開發者自己去配置,而不是預設開啟。

因為不熟悉的情況下還是很容易踩坑的,預設情況下,大家應該只熟悉 9876 與 10911 這兩個埠。

rocketmq 4.5.1 版本之後,vipChannel 配置被修改為 false,這時是否使用 vip 埠真正交給開發者自己

如果此時想開啟,需要主動 API 引數,或者 JVM 引數增加 -Dcom.rocketmq.sendMessageWithVIPChannel=true

總結

今天的問題主要由於 VIP 埠無法連線,從而導致消費端無法正常消費訊息。雖然最後的解決辦法非常簡單,但是這個排查過程真的很難。

我們平常在使用 rocketmq 過程中,通常只要設定 nameserver 的配置即可, broker 等地址資訊將會自動從 nameserver 獲取。這就間接導致了,我們可能只瞭解 9876 這個埠。

生產環境由於網路安全問題,一般不會開放全部的埠。所以,我們在使用 rocketmq 的過程,需要了解以下四個埠,分別為(預設配置):

  • 9876:nameserver 監聽埠
  • 10911: broker 監聽埠
  • 10909:broker vip 監聽埠
  • 10912:broker HA 埠,用於主從同步

生產使用 rocketmq 過程,如果碰到詭問題,不妨嘗試 telnet 看下閘道器連通性。另外還可以通過檢視 rocketmq 自身日誌,確定問題,日誌位置位於:

${user.home}/logs/rocketmqlogs

好了,今天文章就到這裡。我是樓下小黑哥,你知道的越多,你不知道的就越多。

下週見~

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

相關文章