RocketMQ調優的歷程和一些心得

一、問題

線上RocketMQ 集羣,偶爾報錯如下:

(1)[REJECTREQUEST]system busy, start flow control for a while
(2)[TIMEOUT_CLEAN_QUEUE]broker busy, start flow control for a while, period in queue: 206ms, size of queue: 5

 

二、調優歷程

谷歌查找資料

翻閱github 和 stackoverflow,在stackoverflow 上找到該問題:https://stackoverflow.com/questions/47749906/rocketmq-throw-exception-timeout-clean-queuebroker-busy-start-flow-control-f

按照以上方式增加配置文件參數:

 sendMessageThreadPoolNums=64
 useReentrantLockWhenPutMessage=true 

 之後重啓觀察,報錯確實變少了,但量大了後,還有報錯。期間對sendMessageThreadPoolNums參數做過多次調整,32 、64、128 都試過,發現在 4核CPU, 30G內存的機器上,配置sendMessageThreadPoolNums超過64 反倒表現的更不好。本人最後選擇sendMessageThreadPoolNums=32。

此時還會報 broker busy,實在沒辦法,只能看源碼,尤其對報錯前後相關代碼多次閱讀,基本瞭解了報錯原因。

兩個報錯的代碼如下:

 

通過閱讀源碼,得出結果:mq busy是因爲線程等待時間,超過了waitTimeMillsInSendQueue的值,該值默認 200ms。

所以還需分析,到底是什麼原因,導致線程在等待,並超過了200ms,有兩種可能:

        1. mq消息消費速度慢,消息堆積,導致消息寫入內存變慢,超過了200ms

         2. GC時JVM STW(stop the world) ,導致超時

之後對 JVM 進行了調優,選用 G1回收器,並且觀察 gc 時間,full GC 基本沒有了,新生代gc 時間控制在50ms 之內,而且頻率不高。

JVM 優化後,還是偶爾有 broker busy.

 

配置優化

查看rocketmq 日誌,存儲層日誌store.log:

可以看出,在消息發送時,主從同步時,連接從服務器等待超時,自動斷了連接。

如果是同步雙寫模式,master會等待slave也寫成功纔會給客戶端返回成功。這個也會耗時,性能不好,建議使用異步。

優化建議:主從同步使用異步,配置brokerRole=ASYNC_MASTER。

到此,優化配置:

#主從同步模式
brokerRole=ASYNC_MASTER
#消息發送隊列等待時間,默認200
waitTimeMillsInSendQueue=400 
#發送消息的最大線程數,默認1,因爲服務器配置不同,具體多少需要壓測後取最優值
sendMessageThreadPoolNums=32
#發送消息是否使用可重入鎖(4.1版本以上纔有)
useReentrantLockWhenPutMessage=true

到此,MQ 基本穩定,連續幾個月再沒有報錯。

後來MQ集羣內存快不夠了,擴了集羣,擴爲之前2倍節點。 

擴容後過段時間,又出現了 以上兩個錯, broker busy 報錯居多。。。

此時第一感覺是新增的機器問題,通過Falcon監控,觀察分析後,發現報錯期間,CPU、內存、IO 等都無異常,有部分報錯時間點 swap 波動較大。

當內存不夠用時,rocketMQ 會使用交換區,使用交換區性能較差,這裏就不對swap做過多解釋了。

對Linux內核參數調優:

查看 cat /proc/sys/vm/swappiness

設置 swappiness = 1

減少使用交換區,提升性能。

優化該參數後,報錯減少。

後來上線異步消息,併發增高,隨着使用方量級增長,broker busy 還是會出現。

在和 system busy   和 broker busy 的鬥爭中,我對RocketMQ的瞭解也在加深。

對於RocketMQ 集羣,和其他使用者也有過交流,總之經驗是:多個小集羣 優於 一個大集羣。

 

最終的參數調優方案

機器配置參考:CPU 4核 \ 內存30G

RocketMQ 的配置

#主從異步複製
brokerRole=ASYNC_MASTER
#異步刷盤
flushDiskType=ASYNC_FLUSH
#線上關閉自動創建topic
autoCreateTopicEnable=false

#發送消息的最大線程數,默認1
sendMessageThreadPoolNums=32
#使用可重入鎖
useReentrantLockWhenPutMessage=true
#發送消息線程等待時間,默認200ms
waitTimeMillsInSendQueue=1000

#開啓臨時存儲池
transientStorePoolEnable=true
#開啓Slave讀權限(分擔master 壓力)
slaveReadEnable=true
#關閉堆內存數據傳輸
transferMsgByHeap=false
#開啓文件預熱
warmMapedFileEnable=true

JVM

-server -Xms10g -Xmx10g 
-XX:+UseG1GC -XX:MaxGCPauseMillis=80 -XX:G1HeapRegionSize=16m 
-XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 
-XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 
-verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails  
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m 
-XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:MaxDirectMemorySize=15g 
-XX:-UseLargePages -XX:-UseBiasedLocking -XX:+PrintGCApplicationStoppedTime 
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 
-XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCDateStamps 
-XX:+PrintAdaptiveSizePolicy

Linux

1. 減少使用交換區

swappiness = 1

2. Disk scheduler使用DeadLine IO調度器

查看IO調度器:

#查看機器整體
dmesg | grep -i scheduler

#查看某個磁盤的調度器
cat /sys/block/sr0/queue/scheduler

如下:

修改IO調度器:

echo noop > /sys/block/sr0/queue/scheduler

Netty

相關配置參數

1. jvm參數加:-Dio.netty.recycler.maxCapacity.default=0

2. jvm參數去掉-XX:+DisableExplicitGC

Netty性能問題

1.查閱netty相關資料,在netty的github上找到了一個issue #4147,大致可以看出,netty實現的Recycler並不保證池的大小,也就是說有多少對象就往池中加入多少對象,從而可能撐滿服務器。通過在jvm啓動時加入-Dio.netty.recycler.maxCapacity.default=0參數來關閉Recycler池,前提:netty version>=4.0。所以可懷疑爲內存泄露!

千萬不要開啓-XX:+DisableExplicitGC!因爲netty要做System.gc()操作,而System.gc()會對直接內存進行一次標記回收,如果通過DisableExplicitGC禁用了,會導致netty產生的對象爆滿

2.Netty裏四種主力的ByteBuf,

其中UnpooledHeapByteBuf 底下的byte[]能夠依賴JVM GC自然回收;而UnpooledDirectByteBuf底下是DirectByteBuffer,如Java堆外內存掃盲貼所述,除了等JVM GC,最好也能主動進行回收;而PooledHeapByteBuf 和 PooledDirectByteBuf,則必須要主動將用完的byte[]/ByteBuffer放回池裏,否則內存就要爆掉。所以,Netty ByteBuf需要在JVM的GC機制之外,有自己的引用計數器和回收過程。

在DirectByteBuffer中,首先向Bits類申請額度,Bits類有一個全局的 totalCapacity變量,記錄着全部DirectByteBuffer的總大小,每次申請,都先看看是否超限 -- 堆外內存的限額默認與堆內內存(由-XMX 設定)相仿,可用 -XX:MaxDirectMemorySize 重新設定。

如果已經超限,會主動執行Sytem.gc(),期待能主動回收一點堆外內存。然後休眠一百毫秒,看看totalCapacity降下來沒有,如果內存還是不足,就拋出大家最頭痛的OOM異常。

 

心得:

MQ 調優過程,需要關注 disk 、mem 、IO、CPU 等方面指標,尤其要關注 iowait , 磁盤iowait 會直接影響性能。

發送消息線程數並不是越多越好,太多的線程,CPU 上下文切換也是很大的性能損耗。

需要對Linux 有較爲深刻的瞭解,其中涉及 頁緩存、缺頁中斷、零拷貝、回寫、內存映射;

其他技術:併發處理、鎖機制、異步、池化技術、堆外內存、Netty 等相關技術。

只有對這次技術都有深刻理解,才能很好的理解RocketMQ,並對其做出合理的優化。

 

參考資料

Apache RocketMQ開發者指南

RocketMQ單機存儲原理

調整 Linux I/O 調度器優化系統性能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章