问题说明
查看RocketMQ Broker的日志:remoting.log,发现有以下报错:OutOfMemoryError: Direct buffer memory 或者rocketmq-client的日志:
问题分析
问题直接原因
DirectBuffer内存溢出和以下jvm参数有关系,DirectBuffer内存超过MaxDirectMemorySize就会报内存溢出
shell
-XX:MaxDirectMemorySize=15g
导致DirectBuffer过大的原因
MessageExtEncoder
DirectBuffer内存在RocketMQ broker中,除了网络外,只有一个地方会使用到: 每个MessageExtEncoder申请的DirectBuffe的内存大小,是由broker的配置maxMessageSize指定:
shell
# 限制的消息大小 128M
maxMessageSize=134217728
MessageExtEncoder 又是被CommitLog#putMessageThreadLocal这个ThreadLocal持有的,所以MessageExtEncoder申请的DirectBuffe总的内存大小,和线程数有关系。
使用MessageExtEncoder的场景
消息写入commitlog
消息写commitlog的时候,会使用putMessageThreadLocal
对消息序列化。 代码入口:CommitLog#asyncPutMessage
写入commitlog的线程数由sendMessageThreadPoolNums
参数决定,默认值:
shell
Math.min(Runtime.getRuntime().availableProcessors(), 4)
延迟消息
代码入口:DeliverDelayedMessageTimerTask#executeOnTimeup
RocketMQ延迟消息分为18个等级,也会有18个线程。
事务消息
代码入口:EndTransactionProcessor#sendFinalMessage
事务消息的线程由 endTransactionThreadPoolNums
参数决定,默认值:
shell
Math.max(8 + Runtime.getRuntime().availableProcessors() * 2, sendMessageThreadPoolNums * 4)
RPC消息
代码入口:ReplyMessageProcessor#processReplyMessageRequest
RPC消息的线程由processReplyMessageThreadPoolNums
参数决定,默认值:
shell
16 + Runtime.getRuntime().availableProcessors() * 2
PULL_OFFSET_MOVED
代码入口: PullMessageProcessor#generateOffsetMovedEvent
出现PULL_OFFSET_MOVED
原因有三个: NO_MESSAGE_IN_QUEUE broker的最大偏移量为0
OFFSET_OVERFLOW_BADLY 消息请求偏移量大于broker最大的偏移量
OFFSET_TOO_SMALL消息请求偏移量小于broker最小的偏移量
正常情况下,消息拉取不会用到堆外内存,但是当消费偏移量有问题时,才会使用到堆外内存。
消息拉取线程数量由pullMessageThreadPoolNums
参数决定,默认值:
shell
16 + Runtime.getRuntime().availableProcessors() * 2
出现PULL_OFFSET_MOVED
的时候,在broker.log的日志中会打印以下日志:PULL_OFFSET_MOVED:correction 通过这个日志可以看到出问题的topic和消费者组名称。
问题解决
通过问题的分析,可以知道RocketMQ Broker的最大堆外内存是:
shell
(sendMessageThreadPoolNums
+ endTransactionThreadPoolNums
+ processReplyMessageThreadPoolNums
+ pullMessageThreadPoolNums
+ 18) * maxMessageSize
查看broker.conf配置和jvm参数: 发现-XX:MaxDirectMemorySize=15g,而maxMessageSize * pullMessageThreadPoolNums 已经16g,超过了MaxDirectMemorySize。
再查看broker.log日志,的确是出现了 PULL_OFFSET_MOVED:correction 日志。
maxMessageSize和pullMessageThreadPoolNums太大,按理说,不应该配置这么大的值,需要按实际情况将这两个值调小。