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 集群,和其他使用者也有过交流,总之经验是:多个小集群 优于 一个大集群。

三、最终的参数调优方案
机器配置参考: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
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 调度器优化系统性能

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值