hello,各位小夥伴們,上午好~
昨晚生產系統機房切換,又度過了一個不眠之夜。趁著這段無聊時間,分享一下前一段時間 RocketMQ 踩坑經歷
太慘了!!!早上剛躺下睡了兩小時,就被一通電話僥倖起來檢視問題。
前言
事情是這樣的,前端時間我們有個新業務上線,這個業務需要監聽支付成功的 mq 訊息,然後向繫結的音響推送訊息。這樣使用者在支付完成之後,商家端就就可以收到收款播報。
起初我們在測試環境的測試的時候,一切流程非常順利,沒有任何問題。但是等到我們釋出上線之後,卻出現了問題。
一筆支付成功之後,音響沒有發出收款成功的播報。一切流程排查下來之後,這才發現原來 MQ 消費端沒有正常在消費訊息。
開始排查問題,第一想到的是消費端是不是釋出失敗了,但是檢視相關日誌,並沒有任何異常。
登入 MQ 控制檯,嘗試手動重新發布訊息,神奇的事來了,消費端成功收到訊息。
總結現在的問題,下文開始排查。
- MQ 消費端應用沒有異常,但是無法正常消費
- 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 埠。
- 程式碼主動禁止使用 vip 埠,配置如下:
## 消費端
DefaultMQPushConsumer#setVipChannelEnabled(false)
## 生產端
DefaultMQProducer#setVipChannelEnabled(false);
- 設定 JVM 引數,禁用 vip 埠
-Dcom.rocketmq.sendMessageWithVIPChannel=false
原始碼分析
雖然問題解決了,但是上述問題本質原因還沒有找到。所以這次我們就從原始碼出發,追本溯源。
為什麼 vip 埠網路不通將會導致消費者不能正常消費?
從 rocketmq 錯誤日誌,我們可以看到報錯程式碼位於 RebalanceService
類中。
這裡主要用來執行 topic Rebalance(重平衡)。
首先我們來了解一下,Rebalance
目的是為什麼了。
假設當前 rocketmq broker 端存在一個 topic ,擁有四個佇列,關係如下:
此時如果有一個消費者使用叢集模式消費訊息,那麼它將需要負責消費所有佇列中的訊息。
當我們再增加一個消費者消費訊息時,此時消費端將會自動進行重平衡,預設情況下將會使用平均分配原則。
可以看到 Rebalance
機制可以提升的訊息的並行處理機制。
rocketmq 消費端啟動時竟會觸發 Rebalance
機制。接著,我們根據原始碼主要看下 Rebalance
主流程,程式碼位於RebalanceImpl#rebalanceByTopic
。
通常我們使用叢集消費模式,所以這裡主要看叢集模式下 Rebalance
過程。
上述程式碼整體流程如下:
- 首先獲取 Rebalance 過程所需後設資料,包括 Topic 下的佇列資訊集合以及消費者組下的消費者例項 id 資訊集合
- 兩者都存在的情況下,將會按照一定策略將佇列資訊分配給每個消費者,預設按照
AllocateMessageQueueAveragely
,即平均分配原則 - 將預分配結果嘗試更新
ProcessQueue Table
,如果有更新將會把新的佇列在加入非同步消費流程。
後續訊息流程就不看原始碼,比較複雜,網上找了一張訊息消費流程圖:
可以看到,由於網路埠問題,無法正常獲取所有消費者 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