一、性能指标监控
既然是性能测试,那么必然要看RocketMQ集群能承载的最高QPS是多少?同时在承载这个QPS的同时,各个机器的CPU、IO、磁盘、网络、内存的负载情况,以及JVM的GC情况等等。
我们如何去观察这些指标吗?通常来说,指标分为两部分:
- 机器本身指标:也就是上述说的CPU、内存、IO等等;
- RocketMQ指标:生产消息TPS、消费消息TPS、集群整体情况等等;
1.1 机器指标
对于机器指标,最简单的方式我们可以通过OS自身的一些命令进行观察,比如top
、free
等等,后面在压测环节我会具体讲。
一般大一点的公司都会有自己的监控平台,比如Zabbix、Open-Falcon等等,通过它们就可以对机器指标有一个较好的监控。
1.2 RocketMQ管理平台
RoeketMQ自身提供了可视化的管理界面,可以看到NameServer、Broker、Topic等基本信息。我们先来看下如何启动这个管理应用。
我们首先在任意一台部署了NameServer的机器上,执行以下命令:
git clone git.com/apache/rocketmq-externals.git
然后进入rocketmq-console
目录,进行打包:
mvn package -DskipTests
最后启动管理平台:
java -jar rocketmq-console-ng-1.0.1.jar --server.port=8080 --rocketmq.config.namesrvAdE(d)r=127.0.0.1:9876
然后就可以通过浏览器访问8080端口进行管理了:
二、系统参数调整
对于生产环境的RocketMQ集群,我们一般不能直接用默认参数启动,因为那样可能没法把中间件的性能发挥出来。所以,需要对部署MQ的机器的一些参数进行调整。
2.1 OS内核参数调整
由于OS的内核参数默认值未必适合生产环境下的MQ运行,所以一般需要根据机器配置进行调整。
vm.overcommit_memory
vm.overcommit_memory
这个参数有三个值可以选择:0、1、2。
值0:中间件在申请内存时,OS内核会检查机器可用内存是否足够,如果足够就分配内存给MQ,如果剩余内存不足,则拒绝申请,此时会导致中间件出现异常;
因此一般需要将这个参数值调整为1,表示允许分配所有可用的物理内存,只要有内存就给MQ来用,这样可以避免申请内存失败的问题。通过如下命令修改:
echo 'vm.overcommit_memory=1' >> /etc/sysctl.conf
比如,Redis在save数据快照到磁盘文件的时候,需要申请大内存,如果vm.overcommit_memory
这个参数值为0,就很可能被拒绝,进而导致异常报错。
vm.max_map_count
这个参数会影响中间件可以开启的线程数量,如果值过小,有时可能会导致有些中间件无法开启足够的线程,进而报错。
它的默认值是65536,在高并发场景下一般是不够的,因此建议把这个参数调大10倍,比如655360,保证中间件可以开启足够多的线程。 通过如下命令修改:
echo 'vm.max_map_count=655360' >> /etc/sysctl.conf
vm.swappiness
这个参数用来控制进程的swap行为。所谓进程swap,就是OS会把一部分磁盘空间作为swap区域,如果有的进程当前不是太活跃,就会被OS调整为睡眠状态,把进程中的数据放入磁盘上的swap区域,然后让这个进程把原来占用的内存腾出来,以节省内存资源的使用。
vm.swappiness
的取值在0到100之间,默认为60: 如果值设置为0,表示尽量别把任何一个进程放到磁盘swap区域去,尽量用物理内存。如果值是100,表示尽量把进程放到磁盘swap区域去,以腾出内存。
因此,生产环境建议把这个参数调整小一些,比如设置为10,尽量用物理内存,减少swap行为。 可以用如下命令修改:
echo 'vm.swappiness=10' >> /etc/sysctl.conf
ulimit
这个参数用来控制Linux上的最大文件链接数,默认值1024。当程序在频繁的读写磁盘文件时,或者是进行网络通信时,都会跟这个参数有关系。
对于一个生产环境下的中间件,默认值是不够的,如果采用默认值,线上可能会出现如下错误:error: too many open files
。
因此通常建议用如下命令修改这个值:
echo 'ulimit -n 1000000' >> /etc/profile
其实大家思考一下这几个参数,会发现最后要调整的参数,无非就是跟IO、网络通信、内存、线程数量有关,因为中间件在运行的时候主要就是跟这些打交道。
2.2 JVM参数调整
RocketMQ本身是基于Java开发的,所以运行时是基于JVM的,那么就必然涉及对JVM参数的一些调整。
RocketMQ的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
是其真正的启动脚本,内容如下:
JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch"
JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking"
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"
上面就是一些JVM启动时的默认参数,包括堆内存大小、新生代大小、垃圾回收类型及配置,等等。可以看到,RocketMQ默认采用G1作为垃圾回收器,默认堆内存8G。我们需要根据机器的实际配置来调整上面的参数。
2.3 MQ参数调整
RocketMQ自身也有些参数,我们需要对其进行调整。
比如,较为核心的是sendMessageThreadPoolNums
,默认值是16,表示RocketMQ内部用来发送消息的线程池中的线程数量。 这个参数可以根据机器的CPU核数进行适当调整,比如机器CPU是24核,那就增加这个线程数量到24或者30。
sendMessageThreadPoolNums
在目录rocketmq/distribution/target/apache-rocketmq/conf/dledger
下, RocketMQ还有一些其它的核心参数,在后续章节中我们再一一分析。
三、压力测试
接着,我们就要利用生产者和消息者对已经部署好的RocketMQ进行压测了。回顾下系统的逻辑架构图:
我们需要新建Maven两个工程,一个是生产者,一个是消费者,两个工程都需要加入下面的Maven依赖,然后分别打包并部署到上述对应的四台机器上:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.5.1</version>
</dependency>
3.1 生产者
生产者程序就是开启多个线程,不断的往Broker里生产消息
3.2 消费者
生产者程序就是不断的从Broker里获取消息并处理
3.3 压测
我们让两个Producer不停的往RocketMQ集群发送消息,每个Producer所在的机器启动了80个线程。RocketMQ集群是一主双从的dLedger模式的架构,只有Master Broker接受消息的写入,同时两个Consumer不停的从RocketMQ集群消费消息。
TPS和消息延时
TPS方面,我们根据RocketMQ的管理控制台页面监测,发现Master-Broker的TPS可以稳定达到7W左右,也就是每秒可以处理7w条消息。
消息延时方面,我们发现:一条消息从被Producer生产出来,到最后被Consumer消费完成,时间跨度不超过1s,这个性能是正常可接受的。
CPU负载
我们可以通过top
命令检查Broker所在机器的CPU负载情况,执行top
命令后,可以看到类似如下一行信息:
load average:12.03,12.05,12.08
上述信息分别代表CPU在1分钟、5分钟和15分钟内的cpu负载情况 。
上面的12表示有12个核在使用中,而我们的Broker机器是24核的,所以还有12个核其实还没使用,cpu还是有很大余力的,所以在7万TPS的压力下,CPU负载没什么问题。
内存使用率
我们可以通过free
命令查看机器内存的使用率,我们的机器是48G内存,观察到在7万TPS的压力下,还剩下很大一部分内存都是空闲可用的。
GC频率
我们可以通过jstat
命令查看RocketMQ的JVM的GC频率,观察到:新生代每隔几十秒GC一次,每次GC后存活的对象很少,几乎不进入老年代,Full GC在观察的几小时内从未出现。说明在7万TPS的压力下,GC频率正常。
IO负载
我们可以先用top
命令查看,磁盘IO等待占CPU执行时间的百分比,执行top命令后会看到一行类似下面的内容:
Cpu(s): 0.3% us, 0.3% sy, 0.0% ni, 76.7% id, 13.2% wa, 0.0% hi, 0.0% si
上面的13.2% wa
,就是磁盘IO等待占CPU执行时间的百分比 , 如果这个比例太高,说明IO负载很高,导致大量的IO等待。一般来说,超过50%就很危险了。
网卡流量
通知执行命令sar -n DEV 1 2
,可以查看服务器的网卡流量,也就是每秒钟网卡读写的数据量。对于千兆网卡,理论上每秒可以传输128MB的数据,一般100MB就是极限了。
我们压测时发现,对于每条500字节的消息,当TPS达到7万时,Master-Broker所在机器的网卡就几乎打满了。因为Master-Broker不但负责消息的写入,还要把数据同步给两个Slave-Broker,同时还有一些心跳之类的其它网络开销。
通过上述压测,我们可以看到,整个RocketMQ集群的性能瓶颈其实在网卡上,当TPS达到7万时,cpu、内存、IO负载都在正常范围,GC频率也正常。所以,针对我们自己的机器配置,理论上RocketMQ集群的负载上限是7w。
为了支撑10万TPS,我们就需要采用6台机器部署Broker,最终的系统逻辑架构图如下: