【RocketMq】NameServ启动脚本分析(Ver4.9.4)(一)

简介: 【RocketMq】NameServ启动脚本分析(Ver4.9.4)

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}

上面需要注意的点:

  1. export 导出的临时环境变量,只适用当前SHELL连接。
  2. $0 代表当前j脚本名称本身,../dirname 结合类似../../效果,$(dirname $0)/..为Rocketmq的安装目录地址。
  3. 需要把ROCKETMQ_HOMEconflib路径告诉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


相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
3月前
|
消息中间件 监控 数据挖掘
基于RabbitMQ与Apache Flink构建实时分析系统
【8月更文第28天】本文将介绍如何利用RabbitMQ作为数据源,结合Apache Flink进行实时数据分析。我们将构建一个简单的实时分析系统,该系统能够接收来自不同来源的数据,对数据进行实时处理,并将结果输出到另一个队列或存储系统中。
207 2
|
3月前
|
消息中间件 存储 数据中心
RocketMQ的长轮询(Long Polling)实现分析
文章深入分析了RocketMQ的长轮询实现机制,长轮询结合了推送(push)和拉取(pull)两种消息消费模式的优点,通过客户端和服务端的配合,确保了消息的实时性同时将主动权保留在客户端。文中首先解释了长轮询的基本概念和实现步骤,然后通过一个简单的实例模拟了长轮询的过程,最后详细介绍了RocketMQ中DefaultMQPushConsumer的长轮询实现方式,包括PullMessage服务、PullMessageProcessor服务和PullCallback回调的工作原理。
107 1
|
3月前
|
消息中间件 Arthas Java
RocketMQ—一次连接namesvr失败的案例分析
项目组在使用RocketMQ时遇到Consumer连接Name Server失败的问题,异常显示连接特定地址失败。通过Arthas工具逐步分析代码执行路径,定位到创建Channel返回空值导致异常。进一步跟踪发现,问题源于Netty组件在初始化`ByteBufAllocator`时出现错误。分析依赖后确认存在Netty版本冲突。解决方法为排除冲突的Netty包,仅保留兼容版本。
215 0
RocketMQ—一次连接namesvr失败的案例分析
|
6月前
|
消息中间件 存储 安全
【深入浅出RocketMQ原理及实战】「底层原理挖掘系列」透彻剖析贯穿RocketMQ的消息顺序消费和并发消费机制体系的原理分析
【深入浅出RocketMQ原理及实战】「底层原理挖掘系列」透彻剖析贯穿RocketMQ的消息顺序消费和并发消费机制体系的原理分析
87 0
|
5月前
|
数据采集 监控 物联网
MQTT协议在智能制造中的应用案例与效益分析
【6月更文挑战第8天】MQTT协议在智能制造中的应用案例与效益分析
147 1
|
11天前
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
17天前
|
消息中间件 JSON Java
开发者如何使用轻量消息队列MNS
【10月更文挑战第19天】开发者如何使用轻量消息队列MNS
54 4
|
15天前
|
消息中间件
解决方案 | 云消息队列RabbitMQ实践获奖名单公布!
云消息队列RabbitMQ实践获奖名单公布!
|
25天前
|
消息中间件 安全 Java
云消息队列RabbitMQ实践解决方案评测
一文带你详细了解云消息队列RabbitMQ实践的解决方案优与劣
58 7
|
22天前
|
消息中间件 存储 弹性计算
云消息队列RabbitMQ实践
云消息队列RabbitMQ实践
下一篇
无影云桌面