1. 程式人生 > 其它 >生產環境中,RabbitMQ 持續積壓訊息不進行ack,發生什麼了?

生產環境中,RabbitMQ 持續積壓訊息不進行ack,發生什麼了?

  問題:生產環境 rabbitmq 部分客戶端 channel 持續積壓訊息不進行ack。

  0. 服務配置rabbitmq 叢集(普通叢集模式)消費者 三臺 消費執行緒各消費者 10消費者配置 使用 spring-amqp|auto-ack 模式1. 故障發現

  近日有同學發現一個業務佇列存在上千個 unacked 訊息,並且有持續上漲的趨勢。

  2. 故障表現

  佇列下其中兩個客戶端的各一個 channel 分別阻塞幾百條資料,並且在持續累加,重啟應用後佇列 unacked 訊息全部進入 ready 狀態等待重消費,但是重啟後客戶端依然有 channel 重新開始堆積並且在趨勢上漲。

  3. 問題排查

  排查思路

  檢查 mq 控制檯是否是佇列建立問題消費者阻塞是否有規律可循(未ack資料是否有共同特徵、阻塞客戶端配置是否有問題)客戶端程式碼是否有問題、應用是否有jvm級別故障4. 問題定位

  經過一番篩查,問題定位到了程式碼部分,佇列消費程式碼並非剛上線,而是在前一日服務重啟後出現的這個問題,重新 review 程式碼後發現消費者有使用 CountDownLatch 等待多執行緒消費結果,CountDownLatch#countDown的呼叫沒有放到 finally 中執行,並且提交到執行緒池的任務也沒有使用 try catch 進行包裹。

  到此懷疑是消費執行緒阻塞到了 CountDownLatch#await 處,非同步任務處理時由於偶現異常程式碼並未執行到 CountDownLatch#countDown 處,再者由於非同步任務未捕獲異常導致錯誤直接拋到 jvm 日誌無法記錄錯誤。

  為了驗證這個問題,我們又dump了阻塞服務的棧資訊,發現確實有消費者執行緒阻塞到 CountDownLatch#await 處,問題定位結束。

  5. 解決方案

  從任務處下手新增 catch 記錄日誌,並將 CountDownLatch#await 放到 finally 中執行。重啟應用再次觀察,並未出現 unacked 訊息,觀察日誌也並未出現新新增的 error 日誌。

  6.問題拓展

  同一個 channel 為何會阻塞那麼多資料?

  線上生產環境採用推模式,rabbitmq 通過 channel 推送訊息到客戶端,客戶端採用 LinkedBlockingQueue 做快取,一個 channel 對應一個消費者執行緒,當消費者執行緒阻塞時 LinkedBlockingQueue 作為中轉一直在預存訊息,所以會出現很多 unacked 訊息。

  為什麼僅有部分一兩個 channel 出現堆積?

  線上新增錯誤日誌後實際並未出現錯誤列印,懷疑之前異常可能是由於重啟後第一次請求 rpc 偶現呼叫失敗,猜測暫無法復現,後續需觀察日誌。

  總結謹慎使用執行緒同步,謹防執行緒死鎖,務必保證執行緒不會 hang死。自建執行緒池做好錯誤兜底,不要將異常拋給jvm。