1. 程式人生 > >貞炸了!上線之後,訊息收不到了!

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

hello,各位小夥伴們,上午好~ 昨晚生產系統機房切換,又度過了一個不眠之夜。趁著這段無聊時間,分享一下前一段時間 RocketMQ 踩坑經歷 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083838479-1060820315.gif) > 太慘了!!!早上剛躺下睡了兩小時,就被一通電話僥倖起來檢視問題。 ## 前言 事情是這樣的,前端時間我們有個新業務上線,這個業務需要監聽支付成功的 mq 訊息,然後向繫結的音箱推送訊息。這樣使用者在支付完成之後,商家端就就可以收到收款播報。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083839070-1289483674.jpg) 起初我們在測試環境的測試的時候,一切流程非常順利,沒有任何問題。但是等到我們釋出上線之後,卻出現了問題。 一筆支付成功之後,音箱沒有發出收款成功的播報。一切流程排查下來之後,這才發現原來 MQ 消費端沒有正常在消費訊息。 開始排查問題,第一想到的是消費端是不是釋出失敗了,但是檢視相關日誌,並沒有任何異常。 登入 MQ 控制檯,嘗試手動重新發布訊息,神奇的事來了,**消費端成功收到訊息**。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083839259-1158691881.jpg) 總結現在的問題,下文開始排查。 1. MQ 消費端應用沒有異常,但是無法正常消費 2. MQ 控制檯傳送訊息,消費端可以成功消費訊息 ## 排查問題 剛開始排查的時候,由於沒有任何異常業務日誌可以定位問題,所以問題排查起來十分困難。 排查了兩天了,想過各種問題。比如當前消費端使用 RocketMQ 客戶端版本比較高,是不是版本相容性導致的問題呢? 於是降低消費端的版本,重新發布之後,問題依然存在。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083839379-1302738959.gif) 沒辦法,只好使用 Google 大法了。 通過搜尋發現,原來預設情況下 rockmq 客戶端的日誌將會單獨列印輸出,日誌檔案位置如下: ``` ${user.home}/logs/rocketmqlogs ``` 下圖為當時的日誌截圖: ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083839606-1550777716.jpg) 可以看到消費端嘗試連線一個 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); ``` 2. 設定 JVM 引數,禁用 vip 埠 ``` -Dcom.rocketmq.sendMessageWithVIPChannel=false ``` ## 原始碼分析 雖然問題解決了,但是上述問題本質原因還沒有找到。所以這次我們就從原始碼出發,追本溯源。 ### 為什麼 vip 埠網路不通將會導致消費者不能正常消費? 從 rocketmq 錯誤日誌,我們可以看到報錯程式碼位於 `RebalanceService ` 類中。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083840046-1381978429.jpg) 這裡主要用來執行 topic Rebalance(重平衡)。 首先我們來了解一下,`Rebalance` 目的是為什麼了。 假設當前 rocketmq broker 端存在一個 topic ,擁有四個佇列,關係如下: ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083840311-836107183.jpg) 此時如果有一個消費者使用叢集模式消費訊息,那麼它將需要負責消費所有佇列中的訊息。 ![rocketmq 消費者-第 1 頁 的副本](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083840472-1427064553.jpg) 當我們再增加一個消費者消費訊息時,此時消費端將會自動進行重平衡,預設情況下將會使用平均分配原則。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083840686-315895315.jpg) 可以看到 `Rebalance` 機制可以提升的訊息的並行處理機制。 rocketmq 消費端啟動時竟會觸發 `Rebalance` 機制。接著,我們根據原始碼主要看下 `Rebalance` 主流程,程式碼位於`RebalanceImpl#rebalanceByTopic`。 通常我們使用叢集消費模式,所以這裡主要看叢集模式下 `Rebalance` 過程。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083840973-351825916.jpg) 上述程式碼整體流程如下: 1. 首先獲取 **Rebalance** 過程所需元資料,包括 **Topic** 下的佇列資訊集合以及消費者組下的消費者例項 id 資訊集合 2. 兩者都存在的情況下,將會按照一定策略將佇列資訊分配給每個消費者,預設按照 `AllocateMessageQueueAveragely`,即平均分配原則 3. 將預分配結果嘗試更新 `ProcessQueue Table`,如果有更新將會把新的佇列在加入非同步消費流程。 後續訊息流程就不看原始碼,比較複雜,網上找了一張訊息消費流程圖: ![來自:https://blog.csdn.net/binzhaomobile/article/details/75004190](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083841200-1450308763.jpg) 可以看到,由於網路埠問題,無法正常獲取所有消費者 ID 集合,這就導致無法正常分配佇列資訊。 ```java List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup); ``` 由於未被分配任一佇列,消費端程式也就業務無法正常拉取訊息。 ### 為什麼 mq 控制檯重新發送的訊息消費者可以收到? rocketmq 控制檯重新發送訊息程式碼如下: ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083841432-35666260.jpg) `MessageService` 將會把訊息的元資料封裝一個`CONSUME_MESSAGE_DIRECTLY`型別的請求,接著呼叫 rocketmq 提供的 admin API,給 rocketmq broker 傳送請求。 broker 端收到請求之後,將會查詢訊息,然後再向消費端發起 `CONSUME_MESSAGE_DIRECTLY` 請求。消費端接受到訊息請求之後,將會直接訊息這條訊息。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083841661-1158964656.jpg) ### 為什麼 broker 將會啟動兩個埠? rocketmq broker 雖然啟動了兩個埠,但是從 rocketmq broker 的原始碼可以發現這兩個埠啟動之後起到作用是一樣的。 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083842025-336651718.jpg) 那為什麼開啟兩個監聽埠那?我想很多同學應該也有這個疑惑,這裡給出一個開發者解釋答案。 https://github.com/apache/rocketmq/issues/1510 ![](https://img2020.cnblogs.com/other/1419561/202011/1419561-20201124083842425-901753727.jpg) 普通的埠將會承載所有訊息網路請求,如果此時請求非常繁忙,broker 端的所有 I/O 執行緒可能都在執行請求,這就會導致後續網路請求進入佇列,從而導致訊息請求執行緩慢。 這對於生產者來說,可能是一個致命的問題,因為訊息生產者通常訊息傳送延時要低。 這種情況下,我們就可以將訊息傳送到 VIP 埠,從而降低訊息傳送的延時。 預設情況下,rocketmq 客戶端的 `vipChannel` 配置為 `true`。 ```java 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 自身日誌,確定問題,日誌位置位於: ```properties ${user.home}/logs/rocketmqlogs ``` 好了,今天文章就到這裡。我是樓下小黑哥,你知道的越多,你不知道的就越多。 下週見~ > 歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:[studyidea.cn](https://studyi