Jvm-Sandbox原理分析-Sandbox的启动-01

Jvm-Sandbox的启动(一):sandbox.sh脚本分析

Sandbox的启动是通过其内置的shell脚本 sandbox.sh 开始执行的,一切的开始皆可从该脚本中探寻出结果。脚本有一定的代码量,大概有400+行,这里将该脚本分为如下几个部分进行讲解:
在这里插入图片描述

1、变量定义过程

这个过程首先预定义了接下来即将使用的一些变量。代码如下:

# 定义sandbox的home目录,并为其赋值
typeset SANDBOX_HOME_DIR
[[ -z ${SANDBOX_HOME_DIR} ]] && SANDBOX_HOME_DIR=${PWD}/..

# 定义 SANDBOX_USER,并为其赋值
typeset SANDBOX_USER=${USER}
[[ -z ${SANDBOX_USER} ]] && SANDBOX_USER=$(whoami)

# 定义 SANDBOX_SERVER_NETWORK
typeset SANDBOX_SERVER_NETWORK

# 定义lib目录,这个目录下主要存放jar包
typeset SANDBOX_LIB_DIR=${SANDBOX_HOME_DIR}/lib

# 定义 SANDBOX_TOKEN_FILE
typeset SANDBOX_TOKEN_FILE="${HOME}/.sandbox.token"

# 定义JVM参数 SANDBOX_JVM_OPS
typeset SANDBOX_JVM_OPS="-Xms128M -Xmx128M -Xnoclassgc -ea"

# 定义目标JVM的进程号,后面的agent主要attach到该JVM进程上
typeset TARGET_JVM_PID

# 定义目标机器IP以及默认机器IP
typeset TARGET_SERVER_IP
typeset DEFAULT_TARGET_SERVER_IP="0.0.0.0"

# 定义目标进程端口
typeset TARGET_SERVER_PORT

# 定义名称空间
typeset TARGET_NAMESPACE
typeset DEFAULT_NAMESPACE="default"

注释和变量命名已经描绘的非常清楚了,在看后面代码遇到忘记了的变量可以到这里来回顾下。

这里为其中一些变量补充说明:

  • SANDBOX_HOME_DIR:shell脚本中,-z表示检测紧跟的字符串长度是否为0,如果为0返回true。这里使用短路与,如果 ${SANDBOX_HOME_DIR} 为0,则使用 ${PWD}/.. 的目录作为sandbox的home目录。这种方式表示优先使用环境变量 SANDBOX_HOME_DIR,如果未定义环境变量SANDBOX_HOME_DIR,则使用当前目录。
  • SANDBOX_TOKEN_FILE:这个文件主要存放了sandbox attach记录,包括attach进程的host:port。
  • TARGET_SERVER_IP:一般情况下,我们都是将整个工程打包后上传至目标机器,然后在目标机器上执行该shell脚本,因此默认机器IP一般为localhost即可。

2、执行入口

执行入口就比较简单了,就一行代码,其中${@}会保存我们传递给该shell脚本的所有参数:

main "${@}"

比方说,我们以如下命令启动脚本,则${@} 就包含了-p 12345 这个参数

./sandbox.sh -p 12345

3、main函数

main函数是该脚本的重要方法,也是脚本的执行入口,它主要完成了以下几件事:

在这里插入图片描述
其代码如下所示:

function main() {
  # 遍历脚本参数
  while getopts "hp:vFfRu:a:A:d:m:I:P:ClSn:X" ARG; do
    case ${ARG} in
    h)
      # 帮助手册函数,大家可以自行翻阅源码查看
      usage
      exit
      ;;
    # 赋值PID
    p) TARGET_JVM_PID=${OPTARG} ;;
    v) OP_VERSION=1 ;;
    l) OP_MODULE_LIST=1 ;;
    R) OP_MODULE_RESET=1 ;;
    F) OP_MODULE_FORCE_FLUSH=1 ;;
    f) OP_MODULE_FLUSH=1 ;;
    u)
      OP_MODULE_UNLOAD=1
      ARG_MODULE_UNLOAD=${OPTARG}
      ;;
    a)
      OP_MODULE_ACTIVE=1
      ARG_MODULE_ACTIVE=${OPTARG}
      ;;
    A)
      OP_MODULE_FROZEN=1
      ARG_MODULE_FROZEN=${OPTARG}
      ;;
    d)
      OP_DEBUG=1
      ARG_DEBUG=${OPTARG}
      ;;
    m)
      OP_MODULE_DETAIL=1
      ARG_MODULE_DETAIL=${OPTARG}
      ;;
    # 赋值IP
    I) TARGET_SERVER_IP=${OPTARG} ;;
    # 赋值PORT
    P) TARGET_SERVER_PORT=${OPTARG} ;;
    C) OP_CONNECT_ONLY=1 ;;
    S) OP_SHUTDOWN=1 ;;
    n)
      OP_NAMESPACE=1
      ARG_NAMESPACE=${OPTARG}
      ;;
    X) set -x ;;
    ?)
      usage
      exit_on_err 1
      ;;
    esac
  done
  # 重置环境
  reset_for_env
  # 校验权限
  check_permission

  # 根据不同的参数,进行相应处理
  # 如果没有指定IP,则使用默认值
  [ -z "${TARGET_SERVER_IP}" ] && TARGET_SERVER_IP="${DEFAULT_TARGET_SERVER_IP}"

  # 如果没有指定port,使用默认值
  [ -z "${TARGET_SERVER_PORT}" ] && TARGET_SERVER_PORT=0

  # reset NAMESPACE
  [[ ${OP_NAMESPACE} ]] && TARGET_NAMESPACE=${ARG_NAMESPACE}
  [[ -z ${TARGET_NAMESPACE} ]] && TARGET_NAMESPACE=${DEFAULT_NAMESPACE}

  if [[ ${OP_CONNECT_ONLY} ]]; then
    [[ 0 -eq ${TARGET_SERVER_PORT} ]] &&
      exit_on_err 1 "server appoint PORT (-P) was missing"
    SANDBOX_SERVER_NETWORK="${TARGET_SERVER_IP};${TARGET_SERVER_PORT}"
  else
    # -p was missing
    [[ -z ${TARGET_JVM_PID} ]] && exit_on_err 1 "PID (-p) was missing."
    # attach jvm的核心方法
    attach_jvm
  fi

  # -v show version
  [[ -n ${OP_VERSION} ]] &&
    sandbox_curl_with_exit "sandbox-info/version"

  # -l list loaded modules
  [[ -n ${OP_MODULE_LIST} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/list"

  # -F force flush module
  [[ -n ${OP_MODULE_FORCE_FLUSH} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/flush" "&force=true"

  # -f flush module
  [[ -n ${OP_MODULE_FLUSH} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/flush" "&force=false"

  # -R reset sandbox
  [[ -n ${OP_MODULE_RESET} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/reset"

  # -u unload module
  [[ -n ${OP_MODULE_UNLOAD} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/unload" "&action=unload&ids=${ARG_MODULE_UNLOAD}"

  # -a active module
  [[ -n ${OP_MODULE_ACTIVE} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/active" "&ids=${ARG_MODULE_ACTIVE}"

  # -A frozen module
  [[ -n ${OP_MODULE_FROZEN} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/frozen" "&ids=${ARG_MODULE_FROZEN}"

  # -m module detail
  [[ -n ${OP_MODULE_DETAIL} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/detail" "&id=${ARG_MODULE_DETAIL}"

  # -S shutdown
  [[ -n ${OP_SHUTDOWN} ]] &&
    sandbox_curl_with_exit "sandbox-control/shutdown"

  # -d debug
  if [[ -n ${OP_DEBUG} ]]; then
    sandbox_debug_curl "module/http/${ARG_DEBUG}"
    exit
  fi

  # default
  sandbox_curl "sandbox-info/version"
  exit

}

整体看下来,main函数的逻辑还是比较清晰的:

它首先会遍历执行shell脚本的所有参数,为对应的参数设置标志位(OP_MODULE_xxx=1),如果参数有携带变量(参数有携带冒号的u:a:A:d:m:I:P:)的,再保存该变量(ARG_xxx=${OPTARG})。

然后执行reset_for_env函数,见名即知重置环境设置。

reset_for_env() {

  # 如果JAVA_HOME的字符串长度不为0,则令SANDBOX_JAVA_HOME为JAVA_HOME
  [[ -n "${JAVA_HOME}" ]] && SANDBOX_JAVA_HOME="${JAVA_HOME}"

  # 如果SANDBOX_JAVA_HOME为空,通过lsof命令从指定进程中提取Java_home信息
  [[ -z "${SANDBOX_JAVA_HOME}" ]] &&
    SANDBOX_JAVA_HOME="$(
      lsof -p "${TARGET_JVM_PID}" |
        grep "/bin/java" |
        awk '{print $9}' |
        xargs ls -l |
        awk '{if($1~/^l/){print $11}else{print $9}}' |
        xargs ls -l |
        awk '{if($1~/^l/){print $11}else{print $9}}' |
        sed 's/\/bin\/java//g'
    )"

  # 如果tools.jar存在且为普通文件,则为SANDBOX_JVM_OPS增加一些虚拟机参数“-Xbootclasspath/a”,即改变Bootstrap ClassLoader的类加载路径
  [[ -f "${SANDBOX_JAVA_HOME}"/lib/tools.jar ]] &&
    SANDBOX_JVM_OPS="${SANDBOX_JVM_OPS} -Xbootclasspath/a:${SANDBOX_JAVA_HOME}/lib/tools.jar"

  # 修改windows的问题,shell $HOME与user.home存在差异
  test -n "${USERPROFILE}" -a -z "$(cat "${SANDBOX_TOKEN_FILE}")" && SANDBOX_TOKEN_FILE=${USERPROFILE}/.sandbox.token

}

然后执行check_permission函数,进行一些校验。

check_permission()
{
  # 如果HOME目录不可写,则直接报错,错误码为1
    [[ ! -w ${HOME} ]] \
        && exit_on_err 1 "permission denied, ${HOME} is not writable."
        
    # 如果SANDBOX_LIB_DIR目录不可读,则直接报错,错误码为1,这个目录包含了接下来需要使用的JAR包,需要具有读权限
    [[ ! -r ${SANDBOX_LIB_DIR} ]] \
        && exit_on_err 1 "permission denied, ${SANDBOX_LIB_DIR} is not readable."
        
    # 尝试创建SANDBOX_TOKEN_FILE,创建失败报错,错误码为1
    touch ${SANDBOX_TOKEN_FILE} \
        || exit_on_err 1 "permission denied, ${SANDBOX_TOKEN_FILE} is not readable."
}

最后是根据前面轮询的环境变量参数执行一些处理,这些处理大致就是调用了两个函数

其中一个是sandbox_curl_with_exit:这个函数的调用链路为sandbox_curl_with_exit -> sandbox_curl -> sandbox_debug_curl,最后的debug这个函数源码如下所示,比较简单,就是组装了curl命令,向sandbox发起了http请求。

function sandbox_debug_curl() {
  local host=${SANDBOX_SERVER_NETWORK%;**}
  local port=${SANDBOX_SERVER_NETWORK#**;}
  if [[ "$host" == "0.0.0.0" ]]; then
    host="127.0.0.1"
  fi
  curl -N -s "http://${host}:${port}/sandbox/${TARGET_NAMESPACE}/${1}" ||
    exit_on_err 1 "target JVM ${TARGET_JVM_PID} lose response."
}

另外一个函数是attach_jvm,agent如何attach至目标JVM上的逻辑都在该函数中了。

4、attach jvm

这个函数也比较简单,就是组装了java执行命令,拉起 sandbox-core,并将一系列参数传递给拉起的java工程当中。

function attach_jvm() {
  # got an token
  local token
  token="$(date | head | cksum | sed 's/ //g')"

  # 通过java指令启动核心jar包,并添加上所需虚拟机参数
  "${SANDBOX_JAVA_HOME}/bin/java" \
    ${SANDBOX_JVM_OPS} \
    -jar "${SANDBOX_LIB_DIR}/sandbox-core.jar" \
    "${TARGET_JVM_PID}" \
    "${SANDBOX_LIB_DIR}/sandbox-agent.jar" \
    "home=${SANDBOX_HOME_DIR};token=${token};server.ip=${TARGET_SERVER_IP};server.port=${TARGET_SERVER_PORT};namespace=${TARGET_NAMESPACE}" ||
    exit_on_err 1 "attach JVM ${TARGET_JVM_PID} fail."

  # 判断SANDBOX_SERVER_NETWORK是否为空,为空则报错
  SANDBOX_SERVER_NETWORK=$(grep "${token}" "${SANDBOX_TOKEN_FILE}" | grep "${TARGET_NAMESPACE}" | tail -1 | awk -F ";" '{print $3";"$4}')
  [[ -z ${SANDBOX_SERVER_NETWORK} ]] &&
    exit_on_err 1 "attach JVM ${TARGET_JVM_PID} fail, attach lose response."

}

一般而言,执行Java命令的参数如下所示:

.../java -Xms128M -Xmx128M -Xnoclassgc -ea -jar .../sandbox-core.jar JVM_PID ".../sandbox-agent.jar" "home=${SANDBOX_HOME_DIR};token=${token};server.ip=${TARGET_SERVER_IP};server.port=${TARGET_SERVER_PORT};namespace=${TARGET_NAMESPACE}"

这里通过java -jar命令启动了sandbox-core.jar的核心jar包,并为其添加上三个参数:

  • JVM_PID
  • sandbox-agent.jar包的绝对路径字符串
  • home/token/ip/port/namespace信息字符串

5、总结

本篇文章分析了jvm-sandbox启动脚本sandbox.sh核心执行流程,描述了执行过程中的各个关键节点,并得知该脚本最后是使用java -jar命令拉起了sandbox-core.jar这个jar包。

在这里插入图片描述
至此,sandbox.sh的职责基本完成,Sandbox整体的启动来到了我们熟悉的java工程当中,后面的章节将继续对其深入分析。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值