除了消息的丟失,另一個消息隊列常見的問題就是消息積壓了。我們都知道,消息之所以會擠壓是由於消費端的性能除了問題,導致消息的消費速度較低來不及處理上游發送的消息。這一章我們就來看一下,如果優化代碼的性能,避免出現消息積壓。
在使用消息隊列的系統中,對於性能的優化,主要體現在生產者和消費者這一收一發兩部分的業務邏輯中。對於消息隊列本身的性能,不需要太多的關注。一般來說消息隊列本身的處理能力要比業務邏輯處理能力高的多。主流消息隊列單個節點,消息收發的性能能達到幾萬至幾十萬條,還可以通過水平擴展Broker的實例成倍的提升處理能力。而一般的業務系統需要處理的業務邏輯遠比消息隊列復雜的多,單個節點每秒鍾能處理幾百到幾千次請求,已經算非常好的性能了。所以,對於消息隊列的性能優化,我們關注的應該是消息收發的兩端,我們的業務代碼怎么和消息隊列配合,達到要給最佳的性能。
一. 發送端性能優化
發送端業務代碼的處理性能和消息隊列的關系不大,因為一般發送端都會先執行自己的代碼,然后才會發送消息。如果說,你的代碼發送消息的性能上不去,就需要先檢查優化一下,是不是發送消息之前的業務邏輯是不是太耗時了?
對於發送消息的業務邏輯,只需要注意設置合理的並發和批量大小,就能達到很好的發送性能。
我們之前講過發送消息的過程,Producer發消息給Broker,Broker收到消息后返回確認響應,這是一次完整的交互,它包含了下面這些操作的耗時:
-
發送端准備數據、序列化消息、構造請求等邏輯的時間,也就是發送端在發送網絡請求之前的耗時
-
發送消息、返回響應用到的網絡傳輸的耗時
-
Broker處理消息的耗時
假設一次交互的平均延時是1ms,那么在單線程下1s能發送1000條消息,這種情況下並不能發揮出消息隊列的全部實例。無論是增加並發還是批量發送,都能提高發送性能。至於到底是選擇並發還是批量發送取決於發送端的業務性質。簡單來說,只要能滿足你的性能要求,怎么實現方便就怎么實現。
比如說,如果處理的線上業務一般會采用並發發送消息。如果是一個離線分析系統,不注意時延,更注重整個系統的吞吐量,發送端的數據來源也都是來自於數據庫,這種情況下我們一般使用批量發送來提升性能。
二. 消息端性能優化
使用消息隊列的時候,大部分性能問題都出現在消費端,如果消息的速度跟不上發送的速度就會出現消息積壓的情況。如果這種情況只是暫時的,那問題不大,只要消費端性能恢復之后,超過發送端的性能,那積壓的消息是可以逐漸被消化掉的。要是消費速度一直比生產速度慢,時間長了,整個系統就會出現問題,要么消息隊列的存儲被填滿無法提供服務,要么消息丟失,這都是很嚴重的事故。
消費端的性能除了可以優化代碼以外,也可以通過水平擴容增加消費端的並發數來提升總體的消費性能。特別需要注意的一點是,在擴容Consumer的實例數量的同時,必須同步擴容主題中的分區(也叫隊列)數量,確保Consumer的數量和分區數量相同,因為同一個分區同一時間只能有一個消費者。
三. 消息積壓了如何處理?
還有一種消息積壓的情況是,日常系統正常運行的時候沒有積壓或者只有少數積壓很快就會被消費掉了,但是某一個時刻,突然就開始積壓消息並且消息持續上漲。這種情況下需要你在短時間內找到消息積壓的原因,並且快速的解決。
導致突然積壓的原因是多樣的,不同的系統、不同的情況有不同的原因,不能一概而論。但是,我們排查消息積壓的原因,是由一些相對固定而且比較有效的方法的。
能導致積壓的突然增加,最粗粒度的原因只有兩種:要么消息發送變快了,要么消息消費變慢了。
大部分消息隊列內部都由監控功能,只要通過監控數據,很容易確定是哪種原因。如果是單位時間發送的消息增多,比如說趕上活動,短時間可能不太可能優化消費端的代碼來提升消費性能,唯一的方法是通過擴容消費端的實例來提升總體的消費能力。
如果短時間內沒有辦法擴容,沒辦法的辦法是,將系統降級,通過關閉一些不重要的業務,減少發送方發送的數據量,最低限度讓系統正常運轉,服務一些重要業務。
還有一些不太常見的情況,你通過監控發現,無論是消息發送還是消息消費都沒有什么變化,這時候需要檢查一下消費端是不是有一些消息消費失敗導致一條消息反復消費,這種情況也會拖慢整個系統的消費速度。
如果監控到消費變慢了,你需要檢查你的消費實例,分析一下是什么原因導致的消費速度降低。優先檢查一下日志是否由大量的消費錯誤,如果沒有的話,可以通過打印堆棧信息,看一下你的消費線程是不是卡在什么地方不動了,比如觸發了死鎖或者卡在等待資源上。