系统参数调优

在解压 RocketMQ 安装包后,在 bin 目录中有个 os.sh 的文件,该文件由 RocketMQ 官方推荐系统参数配置。通常这些参数可以满足系统需求,也可以根据情况进行调整。

最大文件数

设置用户的打开的最多文件数:

1
2
3
4
5
6
vim /etc/security/limits.conf
# End of file
baseuser soft nofile 655360
baseuser hard nofile 655360
* soft nofile 655360
* hard nofile 655360

系统参数设置

系统参数的调整以官方给出的为主,下面对各个参数做个说明。设置时可以直接执行 sh os.sh 完成系统参数设定,也可以编辑 vim /etc/sysctl.conf 文件手动添加如下内容,添加后执行 sysctl -p 让其生效。

1
2
3
4
5
6
7
8
9
vm.overcommit_memory=1
vm.drop_caches=1
vm.zone_reclaim_mode=0
vm.max_map_count=655360
vm.dirty_background_ratio=50
vm.dirty_ratio=50
vm.dirty_writeback_centisecs=360000
vm.page-cluster=3
vm.swappiness=1

参数说明:

参数 含义
overcommit_memory 是否允许内存的过量分配 overcommit_memory=0 当用户申请内存的时候,内核会去检查是否有这么大的内存空间 overcommit_memory=1 内核始终认为,有足够大的内存空间,直到它用完了为止 overcommit_memory=2 内核禁止任何形式的过量分配内存
drop_caches 写入的时候,内核会清空缓存,腾出内存来,相当于 sync drop_caches=1 会清空页缓存,就是文件 drop_caches=2 会清空 inode 和目录树 drop_caches=3 都清空
zone_reclaim_mode zone_reclaim_mode=0 系统会倾向于从其他节点分配内存 zone_reclaim_mode=1 系统会倾向于从本地节点回收 Cache 内存
max_map_count 定义了一个进程能拥有的最多的内存区域,默认为 65536
dirty_background_ratio/dirty_ratio 当 dirty cache 到了多少的时候,就启动 pdflush 进程,将 dirty cache 写回磁盘 当有 dirty_background_bytes/dirty_bytes 存在的时候,dirty_background_ratio/dirty_ratio 是被自动计算的
dirty_writeback_centisecs pdflush 每隔多久,自动运行一次(单位是百分之一秒)
page-cluster 每次 swap in 或者 swap out 操作多少内存页为 2 的指数 page-cluster=0 表示 1 页 page-cluster=1 表示 2 页 page-cluster=2 表示 4 页 page-cluster=3 表示 8 页
swappiness swappiness=0 仅在内存不足的情况下,当剩余空闲内存低于 vm.min_free_kbytes limit 时,使用交换空间 swappiness=1 内核版本 3.5 及以上、Red Hat 内核版本 2.6.32-303 及以上,进行最少量的交换,而不禁用交换 swappiness=10 当系统存在足够内存时,推荐设置为该值以提高性能 swappiness=60 默认值 swappiness=100 内核将积极的使用交换空间

集群参数调优

调优建议

对 Broker 的几个属性可能影响到集群性能的稳定性,下面进行特别说明。

1. 开启异步刷盘

除了一些支付类场景、或者 TPS 较低的场景(例如:TPS 在 2000 以下)生产环境建议开启异步刷盘,提高集群吞吐。

1
flushDiskType=ASYNC_FLUSH

2. 开启 Slave 读权限

消息占用物理内存的大小通过 accessMessageInMemoryMaxRatio 来配置默认为 40%;如果消费的消息不在内存中,开启 slaveReadEnable 时会从 slave 节点读取;提高 Master 内存利用率。

1
slaveReadEnable=true

3. 消费一次拉取消息数量

消费时一次拉取的数量由 broker 和 consumer 客户端共同决定,默认为 32 条。Broker 端参数由 maxTransferCountOnMessageInMemory 设置。consumer 端由 pullBatchSize 设置。Broker 端建议设置大一些,例如 1000,给 consumer 端留有较大的调整空间。

1
maxTransferCountOnMessageInMemory=1000

4. 发送队列等待时间

消息发送到 Broker 端,在队列的等待时间由参数 waitTimeMillsInSendQueue 设置,默认为 200ms。建议设置大一些,例如:1000ms~5000ms。设置过短,发送客户端会引起超时。

1
waitTimeMillsInSendQueue=1000

5. 主从异步复制

为提高集群性能,在生成环境建议设置为主从异步复制,经过压力测试主从同步复制性能过低。

1
brokerRole=ASYNC_MASTER

6. 提高集群稳定性

为了提高集群稳定性,对下面三个参数进行特别说明,在后面踩坑案例中也会提到。

关闭堆外内存:

1
transientStorePoolEnable=false

关闭文件预热:

1
warmMapedFileEnable=false

开启堆内传输:

1
transferMsgByHeap=true

集群平滑运维

优雅摘除节点

案例背景

自建机房 4 主 4 从、异步刷盘、主从异步复制。有一天运维同学遗失其中一个 Master 节点所有账户的密码,该节点在集群中运行正常,然不能登陆该节点机器终究存在安全隐患,所以决定摘除该节点。

如何平滑地摘除该节点呢?

直接关机,有部分未同步到从节点的数据会丢失,显然不可行。线上安全的指导思路“先摘除流量”,当没有流量流入流出时,对节点的操作是安全的。

流量摘除

1. 摘除写入流量

我们可以通过关闭 Broker 的写入权限,来摘除该节点的写入流量。RocketMQ 的 broker 节点有 3 种权限设置,brokerPermission=2 表示只写权限,brokerPermission=4 表示只读权限,brokerPermission=6 表示读写权限。通过 updateBrokerConfig 命令将 Broker 设置为只读权限,执行完之后原该 Broker 的写入流量会分配到集群中的其他节点,所以摘除前需要评估集群节点的负载情况。

1
2
3
4
bin/mqadmin updateBrokerConfig -b x.x.x.x:10911 -n x.x.x.x:9876 -k brokerPermission -v 4
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
update broker config success, x.x.x.x:10911

将 Broker 设置为只读权限后,观察该节点的流量变化,直到写入流量(InTPS)掉为 0 表示写入流量已摘除。

1
2
3
4
5
6
7
8
9
10
11
12
bin/mqadmin clusterList -n x.x.x.x:9876
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
#Cluster Name #Broker Name #BID #Addr #Version #InTPS(LOAD) #OutTPS(LOAD) #PCWait(ms) #Hour #SPACE
ClusterA broker-a 0 x.x.x.x:10911 V4_7_0_SNAPSHOT 2492.95(0,0ms) 2269.27(1,0ms) 0 137.57 0.1861
ClusterA broker-a 1 x.x.x.x:10911 V4_7_0_SNAPSHOT 2485.45(0,0ms) 0.00(0,0ms) 0 125.26 0.3055
ClusterA broker-b 0 x.x.x.x:10911 V4_7_0_SNAPSHOT 26.47(0,0ms) 26.08(0,0ms) 0 137.24 0.1610
ClusterA broker-b 1 x.x.x.x:10915 V4_7_0_SNAPSHOT 20.47(0,0ms) 0.00(0,0ms) 0 125.22 0.3055
ClusterA broker-c 0 x.x.x.x:10911 V4_7_0_SNAPSHOT 2061.09(0,0ms) 1967.30(0,0ms) 0 125.28 0.2031
ClusterA broker-c 1 x.x.x.x:10911 V4_7_0_SNAPSHOT 2048.20(0,0ms) 0.00(0,0ms) 0 137.51 0.2789
ClusterA broker-d 0 x.x.x.x:10911 V4_7_0_SNAPSHOT 2017.40(0,0ms) 1788.32(0,0ms) 0 125.22 0.1261
ClusterA broker-d 1 x.x.x.x:10915 V4_7_0_SNAPSHOT 2026.50(0,0ms) 0.00(0,0ms) 0 137.61 0.2789

2. 摘除读出流量

当摘除 Broker 写入流量后,读出消费流量也会逐步降低。可以通过 clusterList 命令中 OutTPS 观察读出流量变化。除此之外,也可以通过 brokerConsumeStats 观察 broker 的积压(Diff)情况,当积压为 0 时,表示消费全部完成。

1
2
3
4
5
6
7
#Topic             #Group                #Broker Name    #QID  #Broker Offset   #Consumer Offset  #Diff     #LastTime
test_melon_topic test_melon_consumer broker-b 0 2171742 2171742 0 2020-08-13 23:38:09
test_melon_topic test_melon_consumer broker-b 1 2171756 2171756 0 2020-08-13 23:38:50
test_melon_topic test_melon_consumer broker-b 2 2171740 2171740 0 2020-08-13 23:42:58
test_melon_topic test_melon_consumer broker-b 3 2171759 2171759 0 2020-08-13 23:40:44
test_melon_topic test_melon_consumer broker-b 4 2171743 2171743 0 2020-08-13 23:32:48
test_melon_topic test_melon_consumer broker-b 5 2171740 2171740 0 2020-08-13 23:35:58

3. 节点下线

在观察到该 Broker 的所有积压为 0 时,通常该节点可以摘除了。考虑到可能消息回溯到之前某个时间点重新消费,可以过了日志保存日期再下线该节点。如果日志存储为 3 天,那 3 天后再移除该节点。

平滑扩所容

案例背景

需要将线上的集群操作系统从 CentOS 6 全部换成 CenOS 7,具体现象和原因在踩坑记中介绍。集群部署架构为 4 主 4 从,见下图,broker-a 为主节点,broker-a-s 是 broker-a 的从节点。

image-20250126151748499

那需要思考的是如何做到平滑替换?指导思想为“先扩容再缩容”。

集群扩容

申请 8 台相同配置的机器,机器操作系统为 CenOS 7。分别组建主从结构加入到原来的集群中,此时集群中架构为 8 主 8 从,如下图:

image-20250126151837253

broker-a、broker-b、broker-c、broker-d 及其从节点为 CentOS 6。broker-a1、broker-b1、broker-c1、broker-d1 及其从节点为 CentOS 7。8 主均有流量流入流出,至此我们完成了集群的平滑扩容操作。

集群缩容

按照第二部分“优雅摘除节点”操作,分别摘除 broker-a、broker-b、broker-c、broker-d 及其从节点的流量。为了安全,可以在过了日志保存时间(例如:3 天)后再下线。集群中剩下操作系统为 CentOS 7 的 4 主 4 从的架构,如图。至此,完成集群的平滑缩容操作。

image-20250126151854858

注意事项

在扩容中,我们将新申请的 8 台 CentOS 7 节点,命名为 broker-a1、broker-b1、broker-c1、broker-d1 的形式,而不是 broker-e、broker-f、broker-g、broker-h。下面看下这么命名的原因,客户端消费默认采用平均分配算法,假设有四个消费节点。

第一种形式

扩容后排序如下,即新加入的节点 broker-e 会排在原集群的最后。

1
broker-a,broker-b,broker-c,broker-d,broker-e,broker-f,broker-g,broker-h

image-20250126152106693

注:当缩容摘除 broker-a、broker-b、broker-c、broker-d 的流量时,会发现 consumer-01、consumer-02 没有不能分到 Broker 节点,造成流量偏移,存在剩余的一半节点无法承载流量压力的隐患。

第二种形式

扩容后的排序如下,即新加入的主节点 broker-a1 紧跟着原来的主节点 broker-a。

1
broker-a,broker-a1,broker-b,broker-b1,broker-c,broker-c1,broker-d,broker-d1

image-20250126152157127

注:当缩容摘除 broker-a、broker-b、broker-c、broker-d 的流量时,各个 consumer 均分配到了新加入的 Broker 节点,没有流量偏移的情况。

集群节点进程神秘消失

现象描述

接到告警和运维反馈,一个 RocketMQ 的节点不见了。此类现象在以前从未发生过,消失肯定有原因,开始查找日志,从集群的 broker.log、stats.log、storeerror.log、store.log、watermark.log 到系统的 message 日志没发现错误日志。集群流量出入在正常水位、CPU 使用率、CPU Load、磁盘 IO、内存、带宽等无明显变化。

原因分析

继续查原因,最终通过 history 查看了历史运维操作。发现运维同学在启动 Broker 时没有在后台启动,而是在当前 session 中直接启动了。

1
sh bin/mqbroker -c conf/broker-a.conf

问题即出现在此命令,当 session 过期时 Broker 节点也就退出了。

解决方法

标准化运维操作,对运维的每次操作进行评审,将标准化的操作实现自动化运维就更好了。

正确启动 Broker 方式:

1
nohup sh bin/mqbroker -c conf/broker-a.conf &

Master 节点 CPU 莫名飙高

现象描述

RocketMQ 主节点 CPU 频繁飙高后回落,业务发送超时严重,由于两个从节点部署在同一个机器上,从节点还出现了直接挂掉的情况。

主节点 CPU 毛刺截图:

img

从节点 CPU 毛刺截图:

img

说明:中间缺失部分为掉线,没有采集到的情况。

系统错误日志一

1
2
3
4
5
6
7
2020-03-16T17:56:07.505715+08:00 VECS0xxxx kernel: <IRQ>  [<ffffffff81143c31>] ? __alloc_pages_nodemask+0x7e1/0x960
2020-03-16T17:56:07.505717+08:00 VECS0xxxx kernel: java: page allocation failure. order:0, mode:0x20
2020-03-16T17:56:07.505719+08:00 VECS0xxxx kernel: Pid: 12845, comm: java Not tainted 2.6.32-754.17.1.el6.x86_64 #1
2020-03-16T17:56:07.505721+08:00 VECS0xxxx kernel: Call Trace:
2020-03-16T17:56:07.505724+08:00 VECS0xxxx kernel: <IRQ> [<ffffffff81143c31>] ? __alloc_pages_nodemask+0x7e1/0x960
2020-03-16T17:56:07.505726+08:00 VECS0xxxx kernel: [<ffffffff8148e700>] ? dev_queue_xmit+0xd0/0x360
2020-03-16T17:56:07.505729+08:00 VECS0xxxx kernel: [<ffffffff814cb3e2>] ? ip_finish_output+0x192/0x380

系统错误日志二

1
2
3
4
5
30 2020-03-27T10:35:28.769900+08:00 VECSxxxx kernel: INFO: task AliYunDunUpdate:29054 blocked for more than 120 seconds.
31 2020-03-27T10:35:28.769932+08:00 VECSxxxx kernel: Not tainted 2.6.32-754.17.1.el6.x86_64 #1
32 2020-03-27T10:35:28.771650+08:00 VECS0xxxx kernel: "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
33 2020-03-27T10:35:28.774631+08:00 VECS0xxxx kernel: AliYunDunUpda D ffffffff815592fb 0 29054 1 0x10000080
34 2020-03-27T10:35:28.777500+08:00 VECS0xxxx kernel: ffff8803ef75baa0 0000000000000082 ffff8803ef75ba68 ffff8803ef75ba64

说明:系统日志显示错误“page allocation failure”和“blocked for more than 120 second”错误,日志目录 /var/log/messages。

GC 日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2020-03-16T17:49:13.785+0800: 13484510.599: Total time for which application threads were stopped: 0.0072354 seconds, Stopping threads took: 0.0001536 seconds
2020-03-16T18:01:23.149+0800: 13485239.963: [GC pause (G1 Evacuation Pause) (young) 13485239.965: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 7738, predicted base time: 5.74 ms, remaining time: 194.26 ms, target pause time: 200.00 ms]
13485239.965: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 255 regions, survivors: 1 regions, predicted young region time: 0.52 ms]
13485239.965: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 255 regions, survivors: 1 regions, old: 0 regions, predicted pause time: 6.26 ms, target pause time: 200.00 ms]
, 0.0090963 secs]
[Parallel Time: 2.3 ms, GC Workers: 23]
[GC Worker Start (ms): Min: 13485239965.1, Avg: 13485239965.4, Max: 13485239965.7, Diff: 0.6]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 8.0]
[Update RS (ms): Min: 0.1, Avg: 0.3, Max: 0.6, Diff: 0.5, Sum: 7.8]
[Processed Buffers: Min: 2, Avg: 5.7, Max: 11, Diff: 9, Sum: 131]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.8]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
[Object Copy (ms): Min: 0.2, Avg: 0.5, Max: 0.7, Diff: 0.4, Sum: 11.7]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 23]
[GC Worker Other (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 3.6]
[GC Worker Total (ms): Min: 1.0, Avg: 1.4, Max: 1.9, Diff: 0.8, Sum: 32.6]
[GC Worker End (ms): Min: 13485239966.7, Avg: 13485239966.9, Max: 13485239967.0, Diff: 0.3]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.9 ms]
[Other: 5.9 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 1.9 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 1.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.2 ms]
[Eden: 4080.0M(4080.0M)->0.0B(4080.0M) Survivors: 16.0M->16.0M Heap: 4176.5M(8192.0M)->96.5M(8192.0M)]
[Times: user=0.05 sys=0.00, real=0.01 secs]

说明:GC 日志正常。

Broker 错误日志

1
2
3
4
2020-03-16 17:55:15 ERROR BrokerControllerScheduledThread1 - SyncTopicConfig Exception, x.x.x.x:10911 
org.apache.rocketmq.remoting.exception.RemotingTimeoutException: wait response on the channel <x.x.x.x:10909> timeout, 3000(ms)
at org.apache.rocketmq.remoting.netty.NettyRemotingAbstract.invokeSyncImpl(NettyRemotingAbstract.java:427) ~[rocketmq-remoting-4.5.2.jar:4.5.2]
at org.apache.rocketmq.remoting.netty.NettyRemotingClient.invokeSync(NettyRemotingClient.java:375) ~[rocketmq-remoting-4.5.2.jar:4.5.2]

说明:通过查看 RocketMQ 的集群和 GC 日志,只能说明但是网络不可用,造成主从同步问题;并未发现 Broker 自身出问题了。

原因分析

系统使用 CentOS 6,内核版本为 2.6。通过摸排并未发现 broker 和 GC 本身的问题,却发现了系统 message 日志有频繁的“page allocation failure”和“blocked for more than 120 second”错误。所以将目光聚焦在系统层面,通过尝试系统参数设置,例如:min_free_kbytes 和 zone_reclaim_mode,然而并不能消除 CPU 毛刺问题。通过与社区朋友的会诊讨论,内核版本 2.6 操作系统内存回收存在 Bug。我们决定更换集群的操作系统。

解决办法

将集群的 CentOS 6 升级到 CentOS 7,内核版本也从 2.6 升级到了 3.10,升级后 CPU 毛刺问题不在乎出现。升级方式采取的方式先扩容后缩容,先把 CentOS 7 的节点加入集群后,再将 CentOS 6 的节点移除,详见前面实战部分“RocketMQ 集群平滑运维”。

1
Linux version 3.10.0-1062.4.1.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) ) #1 SMP Fri Oct 18 17:15:30 UTC 2019

集群频繁抖动发送超时

现象描述

监控和业务同学反馈发送超时,而且频繁出现。具体现象如下图。

预热现象

img

img

说明:上图分别为开启预热时(warmMapedFileEnable=true)集群的发送 RT 监控、Broker 开启预热设置时的日志。

存传输现象

CPU 抖动

img

img

说明:上图分别为开启堆外内存传输(transferMsgByHeap=false)时的 CPU 抖动截图、系统内存分配不足截图、Broker 日志截图。

原因分析

上面展现的两种显现均会导致集群 CPU 抖动、客户端发送超时,对业务造成影响。

预热设置:在预热文件时会填充 1 个 G 的假值 0 作为占位符,提前分配物理内存,防止消息写入时发生缺页异常。然而往往伴随着磁盘写入耗时过长、CPU 小幅抖动、业务具体表现为发送耗时过长,超时错误增多。关闭预热配置从集群 TPS 摸高情况来看并未有明显的差异,但是从稳定性角度关闭却很有必要。

堆外内存:transferMsgByHeap 设置为 false 时,通过堆外内存传输数据,相比堆内存传输减少了数据拷贝、零字节拷贝、效率更高。但是可能造成堆外内存分配不够,触发系统内存回收和落盘操作,设置为 true 时运行更加平稳。

解决办法

预热 warmMapedFileEnable 默认为 false,保持默认即可。如果开启了,可以通过热更新关闭。

1
bin/mqadmin updateBrokerConfig -b x.x.x.x:10911 -n x.x.x.x:9876 -k warmMapedFileEnable -v false

内存传输参数 transferMsgByHeap 默认为 true(即:通过堆内内存传输)保持默认即可。如果关闭了,可以通过热更新开启。

1
bin/mqadmin updateBrokerConfig -b x.x.x.x:10911 -n x.x.x.x:9876 -k transferMsgByHeap -v true

用了此属性消费性能下降一半

现象描述

配置均采用 8C16G,RocketMQ 的消费线程 20 个,通过测试消费性能在 1.5 万 tps 左右。通过 tcpdump 显示在消费的机器存在频繁的域名解析过程;10.x.x.185 向 DNS 服务器 100.x.x.136.domain 和 10.x.x.138.domain 请求解析。而 10.x.x.185 这台机器又是消息发送者的机器 IP,测试的发送和消费分别部署在两台机器上。

问题:消费时为何会有消息发送方的 IP 呢?而且该 IP 还不断进行域名解析。

img

原因分析

通过 dump 线程堆栈,如下图:

img

代码定位:在消费时有通过 MessageExt.bornHost.getBornHostNameString 获取消费这信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MessageExt extends Message {
private static final long serialVersionUID = 5720810158625748049L;
private int queueId;
private int storeSize;
private long queueOffset;
private int sysFlag;
private long bornTimestamp;
private SocketAddress bornHost;
private long storeTimestamp;
private SocketAddress storeHost;
private String msgId;
private long commitLogOffset;
private int bodyCRC;
private int reconsumeTimes;
private long preparedTransactionOffset;
}

调用 GetBornHostNameString 获取 HostName 时会根据 IP 反查 DNS 服务器:

1
2
InetSocketAddress inetSocketAddress = (InetSocketAddress)this.bornHost;
return inetSocketAddress.getAddress().getHostName();

解决办法

消费的时候不要使用 MessageExt.bornHost.getBornHostNameString 即可,去掉该属性,配置 8C16G 的机器消费性能在 3 万 TPS,提升了 1 倍。