前言
对于消息中间件来说,生产环境下需要对一些参数进行调整,才能把中间件的性能发挥出来。
第一步:需要对部署消息中间件的机器的OS内核参数进行调整(也就是linux操作系统的一些内核参数)。因为OS内核参数很多默认值未必适合生产环境的系统运行,需要将一些参数的值适当调大,才能让中间件发挥出来性能。
第二步:对于RocketMQ、MyCat、ElasticSearch、Kafka之类的东西,很多都是基于Java开发的,或者基于JVM的Scala开发的(Kafka)。所以可以认为在一台机器上部署和启动一个中间件系统,说白了就是启动一个JVM进程,由这个JVM进程来运行中间件系统内的所有代码,然后实现中间件系统的各种功能。对JVM参数的调整,可以针对内存区域的大小分配、垃圾回收器以及对应的行为参数、GC日志存放地址、OOM自动导出内存快照的进行相关配置。
第三步:中间件系统本身的核心参数配置。比如中间件系统的核心线程数量——中间件系统会开启很多的线程处理请求和工作负载,然后会进行大量的网络通信,同时会进行大量的磁盘IO类的操作。
OS内核参数调整
(1 vm.overcommit_memory
vm.overcommit_memory 参数有三个可选值: 0,1,2 。
0 —— 中间件系统在申请内存的时候, os内核会检查可用内存是否足够,如果足够的话,就会分配内存给你。如果感觉剩余内存不够使用,就会直接拒绝内存的申请,导致系统申请内存失败,进而导致中间件系统异常报错。
1 —— 一般将参数的值调整为1,意思是把所有可用的物理内存都允许分配给你,只要有内存就给中间件系统使用,这样就可以避免申请内存失败的问题。
举例: 比如在线上环境部署的Redis就因为使用 vm.overcommit_memory=0, 导致在save数据快照到磁盘文件的时候,需要申请大内存的时候被拒绝了,进而导致异常报错。
可以用命令修改:
echo 'vm.overcommit_memory=1' >> /etc/sysctl.conf
(2 vm.max_map_count
这个参数会直接影响中间件系统可以开启的线程数量。
如果这个参数过小,又得时候可能会导致有些中间件无法开启足够的线程,进而导致报错,甚至中间件系统挂掉。
默认参数为65536,但这个值时不够的。建议可以把参数调大10被,比如655360这样的值,保证中间件可以开启足够多的线程。
可以用命令修改:
echo 'vm.max_map_count=655360' >> /etc/sysctl.conf
(3 vm.swappiness
这个参数是用来控制进程的swap行为的。
简单来说,就是os会把一部分磁盘空间作为swap区域,然后如果有的进程现在可能不是太活跃,就会被操作系统把进程调整为休眠状态,然后把进程中的数据放入磁盘的swap区域,让这个进程把原来占用的内存空间腾出来给其他活跃运行的进程使用。(不活跃进程 -> 进程休眠 ——> 相关数据存入磁盘(swap区域)——>释放内存给活跃线程使用)。
参数设置范围 0 ~ 100。
0 —— 尽量别把任何一个进程放到磁盘的swap区域,尽量大家都用物理内存。
100 —— 尽量把一些进程给放到磁盘的swap区域,内存腾出来给活跃进程使用。
默认参数值为60,有点偏高,可能会导致中间件运行不活跃的时候被迫腾出内存空间然后放到磁盘的swap区域去。
通常在生产环境中建议把这个参数调整小一些,比如设置为10,尽量使用物理内存,别放到磁盘的swap区域去。
可以用命令修改:
echo 'vm.swappiness=10' >> /etc/sysctl.conf
(4 ulimit
这个参数是用来控制linux上最大文件链接数的,默认是1024。
默认值一般是不够用的,因为在进行大量的频繁的读写磁盘文件操作时,或者进行网络通信的时候,都会跟这个参数有关。
对于一个中间件系统来说肯定是不能使用默认值的,如果采用了默认值,很可能会在线上出现如下错误:
error:too many open files.
可以用命令修改:
echo 'ulimit -n 1000000' >> etc/profile
JVM参数调整
启动脚本
在 rocketmq/distribution/target/apache-rocketmq/bin
会有一些对应的脚本,比如 mqbroker
是用来启动 Broker
的, mqnamesvr
是用来启动 NameServer
的。
以 mqbroker
举例,可以看到这个脚本的内容,最后有如下一行:
sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.broker.BrokerStartup $@
# 这一行的内容就是使用 runbroker.sh 脚本来启动一个JVM进程,JVM进程刚开始执行的main类就是
org.apache.rocketmq.broker.BrokerStartup
runbroker.sh
脚本内容
JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
...
...
...
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"
========>>
========>>
"-server -Xms8g -Xmx8g -Xmn4g -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent25 -XX:InitiatingHeapOccupanyPercent=30 -XX:SoftReLRUPolicyMSPerMB=0 -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:MaxDirectMemorySize=15g -XX:-UseLargePages -XX:-UseBiasedLocking"
========>>
========>>
-server: 参数表示是用服务器模式启动。
-Xms8g -Xmx8g -Xmn4g: (重要)默认的堆内存是8g容量, 新生代内存是4g容量。但是高配物理机是48g内存,所以可以调整为 -Xms20g -Xmx20g -Xmn10g,甚至可以分配更多的容量,不过需要留一部分内存给操作系统来用。
-XX:+UseG1GC -XX:G1HeapRegionSize=16m: (重要)选用G1垃圾回收器做分代回收,对新生代和老年代都是用G1来回收。 设置G1的region大小为16m,这个是因为机器内存比较多,所以region的大小可以调大一些给到16m,不然用2m的region,就导致region数量过多的。
-XX:G1ReservePercent=25: 默认值是10,略微偏小。在G1管理的老年代里预留25%的空闲内存,保证新生代对象晋升到老年代的时候有足够的内存空间,避免老年代内存都满了,新生代有对象进入老年代没有充足的内存。
-XX:InitiatingHeapOccupanyPercent=30: 当堆内存的使用率达到30%之后就会自动启动G1的并发垃圾回收,开始尝试回收一些垃圾对象。默认值是45%,这里调整为30,可以提高GC的频率,避免了垃圾对象过多,一次垃圾回收耗时过长的问题。
-XX:SoftRefLRUPolicyMSPerMB=0: 参数默认设置为0,建议不要设置为0,避免频繁回收一些软应用的Class对象,这里可以调整为1000(详情可以参考JVM相关笔记)
-verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m: 这一堆参数都是控制GC日志打印输出的,确定了gc日志文件的地址,要打印哪些详细信息,然后控制每个gc日志文件的大小是30m,最多保留5个gc日志文件。
-XX:-OmitStackTraceInFastThrow: 有时候JVM会抛弃一些异常堆栈信息,因为这个参数设置之后,就是禁用这个特性 —— 省略异常栈信息从而快速抛出
-XX:+AlwaysPreTouch:强制让JVM启动的时候直接分配我们指定的内存,不要等到使用内存的时候再分配。
-XX:MaxDirectMemorySize=15g:RocketMQ里大量用了NIO中的direct buffer,这里限定了 direct buffer最多申请多少,如果你的机器内存比较大,可以适当调大这个值。
-XX:-UseLargePages -XX:-UseBiasedLocking: 禁用大内存页和偏向锁。
========>>
========>>
总结:
(1)RocketMQ默认的JVM参数是采用G1垃圾回收器,默认堆内存大小是8g,所以这部分需要根据机器的内存进行适当调整。
(2)然后就是对G1的垃圾回收的行为参数做了调整。
(3)针对GC日志的打印做了设置。
(4)禁用一些特性,比如异常堆栈信息输出的特性。
RocketMQ核心参数调整
在配置文件 rocketmq/distribution/target/apache-rocketmq/conf/dledger
文件里,设置RocketMQ内部用来发送消息的线程池的线程数量
sendMessageThreadPoolNums = 16; // 默认是16
# 这个参数可以根据你的机器的CPU核数进行适当增加,比如机器CPU是24核,可以增加这个线程数量到24或者30.
总结
(1)中间件系统在压测或者上生产之前,需要对三大块参数进行调整:OS内核参数,JVM参数、中间件核心参数。
(2)OS内核参数主要调整的地方都是跟磁盘IO、网络通信、内存管理以及线程管理有关的,需要适当调节大小。
(3)JVM参数需要我们去中间件系统的启动脚本中寻找他的默认JVM参数,然后根据机器的情况,对JVM的对内存大小、新生代大小、Direct Buffer大小做出调整,发挥机器资源。
(4)中间件核心参数主要也是关注其中跟网络通信、磁盘IO、线程数量、内存管理相关,根据机器资源,适当增加网络通信线程,控制同步刷磁盘或者异步刷磁盘,线程数量有多少,内存中一些队列大小等。