系統學習消息隊列分享(八) 消息積壓了該如何處理?


據我了解,在使用消息隊列遇到的問題中,消息積壓這個問題,應該是最常遇到的問題了,並且,這個問題 還不太好解決。

我們都知道,消息積壓的直接原因,一定是系統中的某個部分出現了性能問題,來不及處理上游發送的消 息,才會導致消息積壓。

所以,我們先來分析下,在使用消息隊列時,如何來優化代碼的性能,避免出現消息積壓。然后再來看看, 如果你的線上系統出現了消息積壓,該如何進行緊急處理,最大程度地避免消息積壓對業務的影響。

優化性能來避免消息積壓

在使用消息隊列的系統中,對於性能的優化,主要體現在生產者和消費者這一收一發兩部分的業務邏輯中。 對於消息隊列本身的性能,你作為使用者,不需要太關注。為什么這么說呢?

主要原因是,對於絕大多數使用消息隊列的業務來說,消息隊列本身的處理能力要遠大於業務系統的處理能 力。主流消息隊列的單個節點,消息收發的性能可以達到每秒鍾處理幾萬至幾十萬條消息的水平,還可以通 過水平擴展Broker的實例數成倍地提升處理能力。

而一般的業務系統需要處理的業務邏輯遠比消息隊列要復雜,單個節點每秒鍾可以處理幾百到幾千次請求, 已經可以算是性能非常好的了。所以,對於消息隊列的性能優化,我們更關注的是,在消息的收發兩端,我 們的業務代碼怎么和消息隊列配合,達到一個最佳的性能。

1. 發送端性能優化

發送端業務代碼的處理性能,實際上和消息隊列的關系不大,因為一般發送端都是先執行自己的業務邏輯, 最后再發送消息。如果說,你的代碼發送消息的性能上不去,你需要優先檢查一下,是不是發消息之前的業 務邏輯耗時太多導致的。

對於發送消息的業務邏輯,只需要注意設置合適的並發和批量大小,就可以達到很好的發送性能。為什么這 么說呢?

我們之前的課程中講過Producer發送消息的過程,Producer發消息給Broker,Broker收到消息后返回確認 響應,這是一次完整的交互。假設這一次交互的平均時延是1ms,我們把這1ms的時間分解開,它包括了下 面這些步驟的耗時:

  • 發送端准備數據、序列化消息、構造請求等邏輯的時間,也就是發送端在發送網絡請求之前的耗時;
  • 發送消息和返回響應在網絡傳輸中的耗時;
  • Broker處理消息的時延。

如果是單線程發送,每次只發送1條消息,那么每秒只能發送 1000ms / 1ms * 1條/ms = 1000條 消息,這種 情況下並不能發揮出消息隊列的全部實力。

無論是增加每次發送消息的批量大小,還是增加並發,都能成倍地提升發送性能。至於到底是選擇批量發送還是增加並發,主要取決於發送端程序的業務性質。簡單來說,只要能夠滿足你的性能要求,怎么實現方便 就怎么實現。
比如說,你的消息發送端是一個微服務,主要接受RPC請求處理在線業務。很自然的,微服務在處理每次請 求的時候,就在當前線程直接發送消息就可以了,因為所有RPC框架都是多線程支持多並發的,自然也就實 現了並行發送消息。並且在線業務比較在意的是請求響應時延,選擇批量發送必然會影響RPC服務的時延。 這種情況,比較明智的方式就是通過並發來提升發送性能。

如果你的系統是一個離線分析系統,離線系統在性能上的需求是什么呢?它不關心時延,更注重整個系統的 吞吐量。發送端的數據都是來自於數據庫,這種情況就更適合批量發送,你可以批量從數據庫讀取數據,然 后批量來發送消息,同樣用少量的並發就可以獲得非常高的吞吐量。

 

2. 消費端性能優化

使用消息隊列的時候,大部分的性能問題都出現在消費端,如果消費的速度跟不上發送端生產消息的速度, 就會造成消息積壓。如果這種性能倒掛的問題只是暫時的,那問題不大,只要消費端的性能恢復之后,超過 發送端的性能,那積壓的消息是可以逐漸被消化掉的。

要是消費速度一直比生產速度慢,時間⻓了,整個系統就會出現問題,要么,消息隊列的存儲被填滿無法提 供服務,要么消息丟失,這對於整個系統來說都是嚴重故障。

所以,我們在設計系統的時候,一定要保證消費端的消費性能要高於生產端的發送性能,這樣的系統才能健 康的持續運行。

消費端的性能優化除了優化消費業務邏輯以外,也可以通過水平擴容,增加消費端的並發數來提升總體的消 費性能。特別需要注意的一點是,在擴容Consumer的實例數量的同時,必須同步擴容主題中的分區(也叫 隊列)數量,確保Consumer的實例數和分區數量是相等的。如果Consumer的實例數量超過分區數量,這 樣的擴容實際上是沒有效果的。原因我們之前講過,因為對於消費者來說,在每個分區上實際上只能支持單 線程消費。

我⻅到過很多消費程序,他們是這樣來解決消費慢的問題的:

 

 

它收消息處理的業務邏輯可能比較慢,也很難再優化了,為了避免消息積壓,在收到消息的OnMessage方 法中,不處理任何業務邏輯,把這個消息放到一個內存隊列里面就返回了。然后它可以啟動很多的業務線 程,這些業務線程里面是真正處理消息的業務邏輯,這些線程從內存隊列里取消息處理,這樣它就解決了單 個Consumer不能並行消費的問題。

這個方法是不是很完美地實現了並發消費?請注意,這是一個非常常⻅的錯誤方法! 為什么錯誤?因為會丟消息。如果收消息的節點發生宕機,在內存隊列中還沒來及處理的這些消息就會丟失。

消息積壓了該如何處理?

還有一種消息積壓的情況是,日常系統正常運轉的時候,沒有積壓或者只有少量積壓很快就消費掉了,但是 某一個時刻,突然就開始積壓消息並且積壓持續上漲。這種情況下需要你在短時間內找到消息積壓的原因, 迅速解決問題才不至於影響業務。

導致突然積壓的原因肯定是多種多樣的,不同的系統、不同的情況有不同的原因,不能一概而論。但是,我 們排查消息積壓原因,是有一些相對固定而且比較有效的方法的。

能導致積壓突然增加,最粗粒度的原因,只有兩種:要么是發送變快了,要么是消費變慢了。

大部分消息隊列都內置了監控的功能,只要通過監控數據,很容易確定是哪種原因。如果是單位時間發送的 消息增多,比如說是趕上大促或者搶購,短時間內不太可能優化消費端的代碼來提升消費性能,唯一的方法 是通過擴容消費端的實例數來提升總體的消費能力。

如果短時間內沒有足夠的服務器資源進行擴容,沒辦法的辦法是,將系統降級,通過關閉一些不重要的業 務,減少發送方發送的數據量,最低限度讓系統還能正常運轉,服務一些重要業務。

還有一種不太常⻅的情況,你通過監控發現,無論是發送消息的速度還是消費消息的速度和原來都沒什么變 化,這時候你需要檢查一下你的消費端,是不是消費失敗導致的一條消息反復消費這種情況比較多,這種情 況也會拖慢整個系統的消費速度。

如果監控到消費變慢了,你需要檢查你的消費實例,分析一下是什么原因導致消費變慢。優先檢查一下日志 是否有大量的消費錯誤,如果沒有錯誤的話,可以通過打印堆棧信息,看一下你的消費線程是不是卡在什么 地方不動了,比如觸發了死鎖或者卡在等待某些資源上了。

小結

這節課我們主要討論了2個問題,一個是如何在消息隊列的收發兩端優化系統性能,提前預防消息積壓。另 外一個問題是,當系統發生消息積壓了之后,該如何處理。

優化消息收發性能,預防消息積壓的方法有兩種,增加批量或者是增加並發,在發送端這兩種方法都可以使 用,在消費端需要注意的是,增加並發需要同步擴容分區數量,否則是起不到效果的。

對於系統發生消息積壓的情況,需要先解決積壓,再分析原因,畢竟保證系統的可用性是首先要解決的問 題。快速解決積壓的方法就是通過水平擴容增加Consumer的實例數量。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM