Rabbitmq的效能測試
在做系統的整體效能測試時發現經常會卡在一個較低的QPS(單機低於100)數值,而且應用伺服器的負載不高,檢查MQ消費速率只有40左右。接著把目標放在訊息傳送端上,發現訊息傳送速率很低,大約40條/s。
果斷搭建一個最小化工程單測Rabbitmq傳送效能,發現在啟用傳送端事務後效能下降非常明顯。
訊息數量 | 開啟事務 | 未開啟事務 |
---|---|---|
10w | 320796ms | 10246ms |
本機SSD硬碟測試結果10w條訊息未開啟事務,大約10s傳送完畢;而開啟了事務後,需要將近320s,差了30多倍。
接著翻閱Rabbitmq官網,發現開啟事務效能最大損失超過250倍。
Using standard AMQP 0-9-1, the only way to guarantee that a message isn’t lost is by using transactions – make the channel transactional then for each message or set of messages publish, commit. In this case, transactions are unnecessarily heavyweight and decrease throughput by a factor of 250. To remedy this, a confirmation mechanism was introduced. It mimics the consumer acknowledgements mechanism already present in the protocol.
事務為什麼會慢
rabbitTemplate.setChannelTransacted(true);
該標誌位開啟後表示Rabbitmq的傳送統一被spring事務管理。當一段程式碼被 @Transactional
包裹,那麼只有當事務結束後,訊息才會正在的傳送到Rabbitmq的exchange中。具體程式碼詳見 rabbitTemplate.java
中的 doSend()
。
事務機制是Rabbitmq自身支援的,原理是 channel.txSelect()
開啟事務, channel.txRollback()
回滾事務。 channel.txCommit()
提交事務。當事務開啟後,通過抓包會發現網路互動增多。
是否可以去掉事務呢?
實踐證明, 不行 。
因為某些訊息,特別是實體的新增或者更新訊息發出後,消費者有可能會通過API反查,這時如果生產者本地事務未提交。消費者就有可能消費到空資料或者舊資料。所以生產者必須將傳送訊息的事務包裹在本地資料庫事務當中。
在過去的實踐中,有一種解法可以在不開啟事務的情況下解決這個問題,就是利用本地訊息表,即生產者呼叫後不傳送,而是將訊息寫入到本地訊息表,當事務失敗那麼此次寫入操作也會回滾。真正傳送訊息到MQ就開啟另一個定時執行緒輪詢該本地訊息表非同步傳送訊息。這種方法理論上可行,但實際操作非常複雜,當有多個生產者例項時,定時傳送執行緒也會有多個,那麼就會遇到各種併發問題。
最大限度改善效能
既然無法去除事務,並且也不希望程式碼異常複雜。那麼可以將訊息分為兩類,一類是changlog即實體的變化,一類是command,即通知消費者可以開始做某事,通常用在同步轉非同步的場景。對於第一類訊息仍然保留事務,對於第二類訊息關閉傳送事務,採用PublisherConfirm的方式保證訊息傳送成功。
再次測試,效能明顯提高,但是並未達到預期,通過 innotop
命令檢視SQL/">MySQL壓力,發現只有10K/s上下。檢查Rabbitmq所在機器的負載。
iowait非常高,由於該機器上還裝有es,同樣是io密集型的應用,所以實際效能瓶頸都在磁碟io上了。
跟Devops確認了機器情況,這臺機器恰好是Rabbitmq的磁碟節點。為了快速驗證,新增了一塊SSD硬碟並將Rabbitmq訊息檔案都掛載到新加的磁碟上。再次測試,iowait下降明顯。
通過 innotop
命令檢視MySQL壓力,發現上升了一倍,達到20K/s。基本上把壓力都轉到了資料庫一側。系統整體效能提升了一個數量級。也許該Rabbitmq節點獨佔一臺機器效果會更好。
總結
效能優化有時候就像破案,看了jstat沒問題,gc沒問題,機器負載也不高,就是抓不到“元凶”。需要一點一點的扣,往往一個短板就造成了木桶效應。另外還有一點就是如果硬體能夠解決的事情,就不要過度優化軟體了,程式碼複雜度上升往往意味著更多的BUG,在資源有限的情況下多花點錢省點時間還是值得的。