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
 
二、調優歷程
Google資料
翻閱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 集群,和其他使用者也有過交流,總之經驗是:多個小集群 優於 一個大集群。

調優時,多關注磁盤IO 和 內存使用情況及釋放時間點,重點關注以下指標:

 

 

 

 

三、最終的參數調優方案
機器配置參考: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

  • vm.extra_free_kbytes,告訴VM在后台回收(kswapd)啟動的閾值與直接回收(通過分配進程)的閾值之間保留額外的可用內存。RocketMQ使用此參數來避免內存分配中的長延遲。(與具體內核版本相關)
  • vm.min_free_kbytes,如果將其設置為低於1024KB,將會巧妙的將系統破壞,並且系統在高負載下容易出現死鎖。
  • vm.max_map_count,限制一個進程可能具有的最大內存映射區域數。RocketMQ將使用mmap加載CommitLog和ConsumeQueue,因此建議將為此參數設置較大的值。(agressiveness --> aggressiveness)
  • vm.swappiness,定義內核交換內存頁面的積極程度。較高的值會增加攻擊性,較低的值會減少交換量。建議將值設置為10來避免交換延遲。
  • File descriptor limits,RocketMQ需要為文件(CommitLog和ConsumeQueue)和網絡連接打開文件描述符。我們建議設置文件描述符的值為655350。
  • Disk scheduler,RocketMQ建議使用I/O截止時間調度器,它試圖為請求提供有保證的延遲。 

1. 減少使用交換區

swappiness = 1
2. Disk scheduler使用DeadLine IO調度器

查看IO調度器:

#查看機器整體
dmesg | grep -i scheduler
#查看某個磁盤的調度器
cat /sys/block/vda/queue/scheduler
修改IO調度器:

echo deadline > /sys/block/vda/queue/scheduler
注意:Linux version 3.10 以上的版本,會出現修改不成功。

如下:

 

 

 

如果你的Linux 服務器,查看磁盤IO調度器發現是 none, none 意思是不使用 IO調度器,一般都是開啟了 blk-mq, 查看/sys/block/vda/ 下,如果有mq目錄,說明使用的是blk-mq機制。

Blk-mq

Linux 3.13引入了塊層的新框架 blk-mq(多隊列塊IO排隊機制),並且在3.16內核中已具有完整的功能。Blk-mq 通過8插槽服務器上的高性能閃存設備(例如PCIe SSD)允許超過1500萬IOPS。

Blk-mq無縫集成到Linux存儲堆棧中。它為設備驅動程序提供了基本功能,用於將I / O查詢映射到多個隊列。任務分布在多個線程中,因此分布到多個CPU內核(每個內核軟件隊列)。兼容Blk-mq的驅動程序通知blk-mq設備支持多少個並行硬件隊列(作為硬件調度隊列注冊的一部分的提交隊列的數量)。基於Blk-mq的設備驅動程序會繞過以前的Linux I / O調度程序。根據Linux I / O調度程序(基於request_fn的方法,請參閱Linux I / O堆棧圖),使用前一個塊I / O層的所有設備驅動程序將繼續獨立於blk-mq用作基於請求的驅動程序。

所以,如果你的MQ服務器硬盤是SSD 或者 內核為 3.10以上的,理論上使用blk-mq性能將會更高,不需要修改。

 
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 有較為深刻的了解,其中涉及 頁緩存、缺頁中斷、零拷貝、預讀、回寫、內存映射、IO調度器。
其他技術:並發處理、鎖機制、異步、池化技術、堆外內存、Netty 等相關技術。
只有對以上這些技術都有深刻理解,才能很好的理解RocketMQ,並對其做出合理的優化。

 

參考資料
Apache RocketMQ開發者指南

RocketMQ單機存儲原理

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

Linux Blk-mq 機制


免責聲明!

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



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