一、問題
線上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,並對其做出合理的優化。
參考資料