NameServ启动脚本分析
mqnamesrv 启动命令
这里直接摘录了官方文档:
Start NameServer
### Start Name Server first $ nohup sh mqnamesrv &
### Then verify that the Name Server starts successfully $ tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success...
mqnamesrv 脚本
#!/bin/sh # 从环境变量当中获取RocketMq环境变量地址 if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home ## 解决链接问题 - $0 可能是maven的主页链接 # PS:$0 是脚本的命令本身 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绝对路径 ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` # 跳转到当前暂存的命令执行路径 cd "$saveddir" fi export ROCKETMQ_HOME # 关键: 执行runserver.sh脚本,携带logback的日志xml配置,以及传递JVM的启动main方法的入口类绝对路径 sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.namesrv.logback.xml org.apache.rocketmq.namesrv.NamesrvStartup $@
最开始的mqnamesrv.sh
脚本获取环境变量的部分看不懂其实没啥影响,大略有个印象即可,当然可以截取部分的命令到Linux运行测试一下就明白了,比如准备环境变量等等,最后一句话比较关键。
注意最后的两个字符$@
,这两个字符的作用如下:
$@ :表示所有脚本参数的内容。
$# :表示返回所有脚本参数的个数。
再次强调前面的一大坨获取环境变量看不懂没关系,看懂核心的执行脚本即可。
runserver.sh 脚本
runserver.sh 的脚本内容如下:
#!/bin/sh #=========================================================================================== # Java Environment Setting #=========================================================================================== error_exit () { echo "ERROR: $1 !!" exit 1 } find_java_home() { # uname 是获取Linux内核参数的指令,不带任何参数获取当前操作系统的类型,比如Linux就是“Linux”的文本 case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) # 可以简单认为获取到javac命令的绝对路径,然后执行两次cd..操作,以此作为JDK的路径 # 比如 /opt/jdk/bin/javac dirname 两次之后就是 /opt/jdk JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac } # 调用函数 find_java_home # 读取JAVA命令的执行地址 [ ! -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 导出的临时环境变量,只适用当前SHELL连接 # JAVA 命令的执行地址,设置为环境变量 export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" # $0 代表当前的请求传递的第一个参数,根据上一个脚本可以知道是:${ROCKETMQ_HOME}/bin/runserver.sh export BASE_DIR=$(dirname $0)/.. # 因为需要启动JVM进程,需要从ROCKETMQ_HOME的conf和lib路径告诉JDK找依赖包以及相关的配置文件 export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH} #=========================================================================================== # JVM Configuration #=========================================================================================== # The RAMDisk initializing size in MB on Darwin OS for gc-log # 在 Darwin OS 上为 gc-log 初始化 RAMDisk 的大小(以 MB 为单位) DIR_SIZE_IN_MB=600 choose_gc_log_directory() { # Darwin 操作系统需要特殊处理,忽略 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 # 检查 /dev/shm 是否存在于其他系统上 # What Is /dev/shm And Its Practical Usage # https://www.cyberciti.biz/tips/what-is-devshm-and-its-practical-usage.html if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac } choose_gc_options() { # 根据JDK的版本选择合适的GC参数,RocketMq最低需要JDK8,所以如果是1开头就是10以及之后的JDK版本 # Example of JAVA_MAJOR_VERSION value: '1', '9', '10', '11', ... # '1' means releases befor Java 9 JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | sed -r -n 's/.* version "([0-9]*).*$/\1/p') if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then # 小于JDK 9 版本的参数 # 堆内存(初始堆内存)为 4 g,新生代 2g,其他空间为 2g。元空间初始化128m,最大的扩容元空间为320mb JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" # g1收集器在jdk11得到并行Full GC能力,而zgc在jdk11版本处于实验状态,这里选择了比较稳妥的 CMS 老年代垃圾回收器 # UseCMSCompactAtFullCollection:CMS垃圾在进行了Full GC时,对老年代进行压缩整理,处理掉内存碎片 # CMSParallelRemarkEnabled 使用CMS老年代收集器 JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps" JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" else # JDK8 之后的参数 JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" 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 choose_gc_options JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages" #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}" $JAVA ${JAVA_OPT} $@
runserver.sh
脚本的内容,内容比较多,这里拆分讲解。
前置准备工作
首先是有关环境变量的配置获取以及查找JAVA_HOME。
#!/bin/sh #=========================================================================================== # Java Environment Setting #=========================================================================================== error_exit () { echo "ERROR: $1 !!" exit 1 } find_java_home() { # uname 是获取Linux内核参数的指令,不带任何参数获取当前操作系统的类型,比如Linux就是“Linux”的文本 case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) # 可以简单认为获取到javac命令的绝对路径,然后执行两次cd..操作,以此作为JDK的路径 # 比如 /opt/jdk/bin/javac dirname 两次之后就是 /opt/jdk JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac } # 调用函数 find_java_home # 读取JAVA命令的执行地址 [ ! -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 导出的临时环境变量,只适用当前SHELL连接 # JAVA 命令的执行地址,设置为环境变量 export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" # $0 代表当前的请求传递的第一个参数,根据上一个脚本可以知道是:${ROCKETMQ_HOME}/bin/runserver.sh export BASE_DIR=$(dirname $0)/.. # 因为需要启动JVM进程,需要从ROCKETMQ_HOME的conf和lib路径告诉JDK找依赖包以及相关的配置文件 export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH}
错误处理
首先是开头部分,如果出现异常就打印错误参数。在SH脚本文件中,$1
代表了跟在脚本后面的第一个参数,比如./script.sh filename1 dir1
,则$1 = filename1
:
error_exit () { // $1 通常是调用函数传入的第一个参数,比如下文的:Please set the JAVA_HOME variable in your environment, We need java(x64)! echo "ERROR: $1 !!" exit 1 }
查找JDK Home
接着是查找 JAVA_HOME 的位置:
find_java_home() { # uname 是获取Linux内核参数的指令,不带任何参数获取当前操作系统的类型,比如Linux就是“Linux”的文本 case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) # 可以简单认为获取到javac命令的绝对路径,然后执行两次 cd.. 操作,以此作为JDK的路径 # 比如 /opt/jdk/bin/javac dirname 两次之后就是 /opt/jdk JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac }
uname 是获取Linux内核参数的指令,不带任何参数获取当前操作系统的类型,比如Linux就是“Linux”的文本:
xander@xander:~$ uname Linux
这里通过uname 查找内核,如果是 darwin 的操作系统获取路径要特殊一些。而其他的方式则是$(dirname $(dirname $(readlink -f $(which javac))))
层层查找:
[zxd@localhost ~]$ echo $(dirname $(dirname $(readlink -f $(which javac)))); /opt/jdk8
这些可以简单认为获取到javac
命令的绝对路径,然后执行两次cd..
操作,以此作为JDK的路径。最简单的验证方法是放到Linux上执行一下:
[zxd@localhost ~]$ echo $(readlink -f $(which javac)) /opt/jdk8/bin/javac
[zxd@localhost ~]$ echo $(dirname $(dirname $(readlink -f $(which javac)))); /opt/jdk8
取JAVA命令的执行地址
这里比较简单,最后一句调用了error_exit函数,对应了第一个参数就是要打印的值。
# 读取JAVA命令的执行地址 [ ! -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 导出的临时环境变量,只适用当前SHELL连接 # 取JAVA 命令的执行地址,设置为环境变量 export JAVA_HOME export JAVA="$JAVA_HOME/bin/java" # $0 通常代表脚本名称本身,这里获取出来的结果是:${ROCKETMQ_HOME} export BASE_DIR=$(dirname $0)/.. # 因为需要启动JVM进程,需要把ROCKETMQ_HOME的conf和lib路径告诉JDK找依赖包以及相关的配置文件 export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH}
上面需要注意的点:
- export 导出的临时环境变量,只适用当前SHELL连接。
$0
代表当前j脚本名称本身,../
和dirname
结合类似../../
效果,$(dirname $0)/..
为Rocketmq的安装目录地址。- 需要把
ROCKETMQ_HOME
的conf
和lib
路径告诉JDK找依赖包以及相关的配置文件。
JAVA="$JAVA_HOME/bin/java" BASE_DIR=${ROCKETMQ_HOME}/bin CLASSPATH=.:${ROCKETMQ_HOME}/bin/conf:${BASE_DIR}/lib/*:${CLASSPATH}
【RocketMq】NameServ启动脚本分析(Ver4.9.4)(二)https://developer.aliyun.com/article/1395262