引言
继 [[【RocketMq】NameServ启动脚本分析(Ver4.9.4)]] 之后又来看看Broker的脚本。总体上来看大差不差,以阅读核心的配置部分调优为主。
mqbroker
#!/bin/sh if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` cd "$saveddir" fi export ROCKETMQ_HOME sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.broker.BrokerStartup $@
前面的一大段脚本的最终目的就是获取ROCKETMQ_HOME
的变量。
我们关注最后一个脚本,这里调用了runbroker.sh
的脚本:
sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.broker.BrokerStartup $@
runbroker.sh
runbroker.sh
的脚本虽然内容很多,但是大部分和之前分析NameServ的启动内容是重合的,这里直接跳过其他函数判断,只关注JVM的参数设置部分。
#!/bin/sh #=========================================================================================== # Java Environment Setting #=========================================================================================== error_exit () { echo "ERROR: $1 !!" exit 1 } [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java [ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java [ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!" export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" export BASE_DIR=$(dirname $0)/.. export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} #=========================================================================================== # JVM Configuration #=========================================================================================== # The RAMDisk initializing size in MB on Darwin OS for gc-log DIR_SIZE_IN_MB=600 choose_gc_log_directory() { case "`uname`" in Darwin) if [ ! -d "/Volumes/RAMDisk" ]; then # create ram disk on Darwin systems as gc-log directory DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS." fi GC_LOG_DIR="/Volumes/RAMDisk" ;; *) # check if /dev/shm exists on other systems if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac} choose_gc_options() { JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/^1\.//' | cut -d'.' -f1) if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "8" ] ; then JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" fi if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M" fi } choose_gc_log_directory JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g"choose_gc_options 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} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n" JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" numactl --interleave=all pwd > /dev/null 2>&1 if [ $? -eq 0 ] then if [ -z "$RMQ_NUMA_NODE" ] ; then numactl --interleave=all $JAVA ${JAVA_OPT} $@ else numactl --cpunodebind=$RMQ_NUMA_NODE --membind=$RMQ_NUMA_NODE $JAVA ${JAVA_OPT} $@ fi else $JAVA ${JAVA_OPT} $@ fi
choose_gc_options() 分析
先来看如何选择GC参数部分。
choose_gc_options() { JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/^1\.//' | cut -d'.' -f1) if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "8" ] ; then JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" fi if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M" fi }
粗略看去和NameServ的参数基本上没啥差别(尴尬....),这里只好罗列一些这些参数的作用了:
注意Broker对于小于JDK8的版本和小于JDK9的版本做了两种策略,这里的脚本其实是有点奇怪的,因为RocketMq最低不是只支持JDK8么? 当然这样的脚本设置也不是不可以,只是没啥作用罢了。
因为怎么看怎么别扭,为了方便理解,个人调整了一下这个脚本的“真实意图”,针对JDK8和JDK8以下和JDK8以上三个分支判断:
- JDK8 以下用CMS+ParNew垃圾收集器经典组合
- G1 是在JDK9 才成为默认垃圾收集器的,JDK8 需要手动设置使用G1。需要注意这个版本G1是残血版本,Full Gc是单线程的,JDK11 才被Oracle官方加上去。(顺带一提满血的G1从配置可以猜到是大量复用CMS的代码实现的)
- JDK8之前的日志打印参数使用了 xloggc,JDK9以及之后的版本用一个统一的打印参数
xlog
替换与之配合的附加参数,脚本干净了很多。 - JDK版本提升可以看出官方在尽可能各方面简化垃圾收集器的参数控制,比如日志接口统一和简化。
choose_gc_options() { JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/^1\.//' | cut -d'.' -f1) # 如果当前版本小于JDK1.8,使用CMS+ParNew垃圾收集器组合和相关参数 if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "8" ] ; then # CMS + ParNew 垃圾收集器 # CMSInitiatingOccupancyFraction=70 表示当老年代达到70%时,触发CMS垃圾回收。 # CMSParallelRemarkEnabled 老年代收集器指定为CMS的时候有效,在进行了Full GC时对老年代进行压缩整理,处理掉内存碎片。 # UseConcMarkSweepGC 使用CMS老年代收集器 # SoftRefLRUPolicyMSPerMB 软引用不给任何的存活时间,对于序列化或者反射的对象在垃圾回收的时候积极清理 # CMSClassUnloadingEnabled 启用对Perm区启用类回收,防止Perm区内存垃圾对象堆满 # -XX:SurvivorRatio=8 Eden 区域在新生代占比,Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10 # -XX:-UseParNewGC ParNew 新生代垃圾收集器 JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" # 垃圾收集器日志存储配置 # PrintGCApplicationStoppedTime 打印应用由于GC而产生的停顿时间 # 这个参数的主要作用是可以在JVM运行的时候动态调整新生代的Eden、From、To三个区域的区域分配,计算依据是 GC 过程中统计的 **GC 时间、吞吐量、内存占用量**。 # -verbose:gc 和 -XX:+PrintGCDetails 垃圾收集时的信息打印 打印开启,大部分时候会一起配置 # PrintGCDateStamps 打印GC发生时的时间戳,搭配 -XX:+PrintGCDetails 使用,不可以独立使用 # PrintAdaptiveSizePolicy 动态调整 Eden From To 三个区域的大小,判断依据为 GC 时间、吞吐量、内存占用量 JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" # 给予5个GC日志文件,每个文件30M,如果5个文件写满,则从第一个文件覆盖。 JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" # 如果当前版本等于 JDK 1.8 else if [ "$JAVA_MAJOR_VERSION" -eq "8" ]; then # PrintGCApplicationStoppedTime 打印应用由于GC而产生的停顿时间 # 这个参数的主要作用是可以在JVM运行的时候动态调整新生代的Eden、From、To三个区域的区域分配,计算依据是 GC 过程中统计的 **GC 时间、吞吐量、内存占用量**。 # -verbose:gc 和 -XX:+PrintGCDetails 垃圾收集时的信息打印 打印开启,大部分时候会一起配置 # PrintGCDateStamps 打印GC发生时的时间戳,搭配 -XX:+PrintGCDetails 使用,不可以独立使用 # PrintAdaptiveSizePolicy 动态调整 Eden From To 三个区域的大小,判断依据为 GC 时间、吞吐量、内存占用量 JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" # 给予5个GC日志文件,每个文件30M,如果5个文件写满,则从第一个文件覆盖。 JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" # 触发全局并发标记的老年代使用占比,默认值45%。 # UseG1GC G1 垃圾收集器 # SoftRefLRUPolicyMSPerMB 软引用不给任何的存活时间,对于序列化或者反射的对象在垃圾回收的时候积极清理。 # G1HeapRegionSize 16M 一个Region的大小可以通过参数`-XX:G1HeapRegionSize`设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。 JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" fi # 如果是JDK 9以及JDK9之后的版本 else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" # -Xlog 是JDK9 统一日志参数,对于之前版本混乱的GC LOG日志管理进行一波优化 JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M" fi }
如果一个参数调研那么文章会没完没了,我们直接拆分三部分进行阅读。
JDK8以下的版本(无用)
理论上来说没屁用的GC参数,因为RocketMq规定了最低支持的JDK版本为JDK1.8。
if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "8" ] ; then # CMS + ParNew 垃圾收集器 # CMSInitiatingOccupancyFraction=70 表示当老年代达到70%时,触发CMS垃圾回收。 # CMSParallelRemarkEnabled 老年代收集器指定为CMS的时候有效,在进行了Full GC时对老年代进行压缩整理,处理掉内存碎片。 # UseConcMarkSweepGC 使用CMS老年代收集器 # SoftRefLRUPolicyMSPerMB 软引用不给任何的存活时间,对于序列化或者反射的对象在垃圾回收的时候积极清理 # CMSClassUnloadingEnabled 启用对Perm区启用类回收,防止Perm区内存垃圾对象堆满 # -XX:SurvivorRatio=8 Eden 区域在新生代占比,Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10 # -XX:-UseParNewGC ParNew 新生代垃圾收集器 JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" # 垃圾收集器日志存储配置 # PrintGCApplicationStoppedTime 打印应用由于GC而产生的停顿时间 # 这个参数的主要作用是可以在JVM运行的时候动态调整新生代的Eden、From、To三个区域的区域分配,计算依据是 GC 过程中统计的 **GC 时间、吞吐量、内存占用量**。 # -verbose:gc 和 -XX:+PrintGCDetails 垃圾收集时的信息打印 打印开启,大部分时候会一起配置 # PrintGCDateStamps 打印GC发生时的时间戳,搭配 -XX:+PrintGCDetails 使用,不可以独立使用 # PrintAdaptiveSizePolicy 动态调整 Eden From To 三个区域的大小,判断依据为 GC 时间、吞吐量、内存占用量 JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" # 给予5个GC日志文件,每个文件30M,如果5个文件写满,则从第一个文件覆盖。 JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JDK8 版本
从JDK8的版本开始,RocketMq的垃圾收集器变更为G1,对应的参数配置也是G1的配置。但是需要注意-Xloggc
的配置文件利用了时间戳进行格式化防止轮循重复覆盖的问题。其他参数已经在NameServ的笔记中进行过分析,个人把参数写入到命令上方方便查看
# 如果当前版本等于 JDK 1.8 else if [ "$JAVA_MAJOR_VERSION" -eq "8" ]; then # PrintGCApplicationStoppedTime 打印应用由于GC而产生的停顿时间 # 这个参数的主要作用是可以在JVM运行的时候动态调整新生代的Eden、From、To三个区域的区域分配,计算依据是 GC 过程中统计的 **GC 时间、吞吐量、内存占用量**。 # -verbose:gc 和 -XX:+PrintGCDetails 垃圾收集时的信息打印 打印开启,大部分时候会一起配置 # PrintGCDateStamps 打印GC发生时的时间戳,搭配 -XX:+PrintGCDetails 使用,不可以独立使用 # PrintAdaptiveSizePolicy 动态调整 Eden From To 三个区域的大小,判断依据为 GC 时间、吞吐量、内存占用量 JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" # 给予5个GC日志文件,每个文件30M,如果5个文件写满,则从第一个文件覆盖。 JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" # 触发全局并发标记的老年代使用占比,默认值45%。 # UseG1GC G1 垃圾收集器 # SoftRefLRUPolicyMSPerMB 软引用不给任何的存活时间,对于序列化或者反射的对象在垃圾回收的时候积极清理。 JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" fi
JDK9 及之后的版本
如果是之后的版本,则基本上的GC垃圾收集器和参数不变,但是需要注意JDK9之后因为Xloggc的参数被废弃,用了-xlog
的参数作为替代,这个起名确实比较坑,因为和之前长的特别像。
# 如果是JDK 9以及JDK9之后的版本 else JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0" # -Xlog 是JDK9 统一日志参数,对于之前版本混乱的GC LOG日志管理进行一波优化 JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M" fi
Oracle官方文档也解释了这几个JDK8以及之前的日志打印参数被废弃。
需要注意
由于日志参数打印属于JVM 的范畴,本节不做过多讨论。
总结
- JDK8之前使用Cms+ParNew,JDK8以及之后的版本全部采用G1垃圾收集器。
- NameServ的启动脚本和Broker的类似,看懂任意一个就可以看懂另一个。
- Xlog 和 Xloggc 是比较容易混淆的地方,也是个人认为Broker启动脚本在不同版本判断启动参数实际最大的区别。
- 依照脚本的判断逻辑,下面的JVM参数在JDK 9及之后会出现两次。
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0"
写在最后
从脚本风格可以看出和改写NameServ.sh
的人是同一个编写,所以有很多大量重复性的内容都给省略了,详细的介绍都放到了nameserv.sh
的脚本分析当中。
通篇看下来个人不太理解为什么要针对JDK8以前的版本做JVM参数调优,或许这就是工程师编写的严谨之处吧,考虑全面,值得学习。