大数据学习其他开发工具

Shell脚本学习:spark2-shell启动流程分析

2021-08-30  本文已影响0人  xiaogp

摘要:SparkLinuxShell

学习一下spark2-shell的启动shell脚本,分析一下spark2-shell的启动流程

环境变量下spark2-shell启动命令

找到spark2-shell在环境变量中的位置/usr/bin/spark2-shell

which spark2-shell
# /usr/bin/spark2-shell

查看/usr/bin/spark2-shell的具体内容

less "`which spark2-shell`"
#!/bin/bash
  # Reference: http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
  SOURCE="${BASH_SOURCE[0]}"
  BIN_DIR="$( dirname "$SOURCE" )"
  while [ -h "$SOURCE" ]
  do
    SOURCE="$(readlink "$SOURCE")"
    [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
    BIN_DIR="$( cd -P "$( dirname "$SOURCE"  )" && pwd )"
  done
  BIN_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  CDH_LIB_DIR=$BIN_DIR/../../CDH/lib
  LIB_DIR=$BIN_DIR/../lib
export HADOOP_HOME=$CDH_LIB_DIR/hadoop

# Autodetect JAVA_HOME if not defined
. $CDH_LIB_DIR/bigtop-utils/bigtop-detect-javahome

exec $LIB_DIR/spark2/bin/spark-shell "$@"

这个启动脚本完成以下事情


BASH_SOURCE[0]获得脚本路径

BASH_SOURCE在shell脚本中使用甚多,在命令行中不使用,在shell脚本中是一个数组变量,用来存储脚本路径+脚本名,执行shell脚本a.sh时,shell脚本的程序名a.sh将被添加到BASH_SOURCE数组的第一个元素中,即${BASH_SOURCE[0]}的值为a.sh,此时${BASH_SOURCE[0]}等价于$0,和$0不同的是BASH_SOURCE[0]保存了bash的SOURCE调用层次,简单而言就是shell脚本中又调用了shell脚本会使用这个数组变量记录下多个文件名路径,简单测试如下

# vim test.sh
#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
echo $SOURCE
echo ${BASH_SOURCE}
echo $0

直接当前目录下使用相对路径运行,则直接显示相对路径即文件名

bash test.sh
test.sh
test.sh
test.sh

如果是以绝对路径运行则显示绝对路径文件名

root@ubuntu:~/shell# bash /home/shell/test.sh 
/home/shell/test.sh
/home/shell/test.sh
/home/shell/test.sh

在单独一个shell脚本的情况下${BASH_SOURCE[0]},$BASH_SOURCE,$0的结果是一样的
在实际的脚本中第一行获得spark2-shell的脚本路径/usr/bin/spark2-shell


dirname获得脚本上一层目录

dirname用于获得路径的上一层目录,测试如下

dirname /opt/idea/bin/idea  # /opt/idea/bin
dirname test.sh   # .
dirname ./test.sh  # .
dirname ""  # .

直接返回最后一个/之前的路径内容,如果采用相对路径直接不带/或者世界对空变量求dirname则直接返回.
在实际脚本中获得spark2-shell执行路径的上一层目录,输出为/usr/bin


-h readlink软连接判断和操作

查看启动命令/usr/bin/spark2-shell存在软连接

ll /usr/bin/spark2-shell
/usr/bin/spark2-shell -> /etc/alternatives/spark2-shell

结果还不是spark的安装目录,判断还是存在软连接,再查看一层

ll /etc/alternatives/spark2-shell
/etc/alternatives/spark2-shell -> /opt/cloudera/parcels/SPARK2-2.3.0.cloudera4-1.cdh5.13.3.p0.611179/bin/spark2-shell

终于找到实际运行的spark地址了,实际运行的spark2-shell就是/opt/cloudera/parcels/SPARK2-2.3.0.cloudera4-1.cdh5.13.3.p0.611179/bin/spark2-shell
在实际脚本中使用条件测试-h进行判断文件是否是软链接,如果是软连接使用readlink读取软连接的实际路径,readlink返回的是实际路径字符串

[ -h /usr/bin/spark2-shell ] && echo 1 || echo 0  # 1
readlink /usr/bin/spark2-shell  # /etc/alternatives/spark2-shell
[ -h /etc/alternatives/spark2-shell ] && echo 1 || echo 0  # 1
readlink /etc/alternatives/spark2-shell # /opt/cloudera/parcels/SPARK2-2.3.0.cloudera4-1.cdh5.13.3.p0.611179/bin/spark2-shell
[ -h /opt/cloudera/parcels/SPARK2-2.3.0.cloudera4-1.cdh5.13.3.p0.611179/bin/spark2-shell ] && echo 1 || echo 0  # 0

脚本中使用while判断SOURCE是否是软连接,并且循环内部不断使用readlink重新赋值SOURCE,直到找到实际spark工作路径才停止
如果不从环境变量中启动而是从实际的spark的工作目录下启动则不仅while循环直接进行接下来的操作,这一步的目的就是如果启动命令是软连接不是真实的spark位置则替换为spark实际位置工作目录


cd -P && pwd获得文件上一层目录

这行的目的是获得SOURCE上一层目录的路径,由于spark-shell在bin目录下所有实际上是获得spark的bin目录位置

BIN_DIR="$( cd -P "$( dirname "$SOURCE"  )" && pwd )"

有两个地方记录一下:cd -P,这个和cd的区别是如果目录是存在软连接的,则切到物理实际目录下,举个例子,将一个目录软连接到/usr/local/bin下

root@ubuntu:~# ll /usr/local/bin/tool
lrwxrwxrwx 1 root root 19 8月  30 11:50 /usr/local/bin/tool -> /home/tool/

此时如果对/usr/local/bin/tool使用cd和cp -P结果不一样,前者切换到该目录,后者切换到物理真实目录

root@ubuntu:~# cd /usr/local/bin/tool
root@ubuntu:/usr/local/bin/tool# 
root@ubuntu:~# cd -P /usr/local/bin/tool
root@ubuntu:~/tool# pwd
/home/tool

在脚本中的目的是在找bin目录时全部使用真实物理路径
第二个地方是为什么大费周章地现cd到上一层目录,再pwd打印出目录字符串,直接dirname不香吗,具体原因我没有百度到,我估计这样的目的是先判断一下dirname的结果路径是否存在,否则直接报错记录下来


export给子脚本设置变量

脚本中在获得BIN_DIR之后进一步根据相对路径确定CDH_LIB_DIR和LIB_DIR,三个绝对路径分别为

下面使用export配置HADOOP_HOME,先看一下后面这个东西是个啥$CDH_LIB_DIR/hadoop他就是一个目录位置是/opt/cloudera/parcels/CDH/lib/hadoop,里面有一些启动命令和相关jar包,再看export声明变量,作用是在子脚本(bashsourceexec)中能够拿到这个变量,子shell不能使用父shell的变量,除非使用export,我们测试一下

# a.sh
#!/bin/bash
val=3
bash /home/shell/b.sh
# b.sh
#!/bin/bash
echo $val

在a.sh中调用运行b.sh,运行a.sh拿不到变量val=3

bash a.sh
  # 输出为空

采用export声明变量,重新运行a.sh就能拿到val=3

# a.sh
#!/bin/bash
export val=3
bash /home/shell/b.sh
bash a.sh
3  # 输出为3

在shell脚本中使用export声明的变量只会在shell脚本的上下文中生效
在spark2-shell脚本中在上下文注入HADOOP_HOME可知在子脚本中会依赖HADOOP_HOME


.sourceexec 在当前shell环境下运行命令

再接下来是设置JAVA_HOME,采用.来运行目录下/opt/cloudera/parcels/CDH-5.14.2-1.cdh5.14.2.p0.3/lib/bigtop-utils/bigtop-detect-javahome
先记录一下.这种方式运行脚本等同于source,source和bash的区别是前者在当前shell环境中从文件名读取和执行命令,而后者启动一个新的子shell来运行它,使用.的目的是要执行的内容是设置JAVA_HOME,需要在当前shell的上下文生效而不是在另一个独立的子进程中声明,所以使用.或者source,可以举个例子对比source和bash

cat test.sh  
#!/bin/bash
echo $0
root@ubuntu:~/shell# bash test.sh 
test.sh
root@ubuntu:~/shell# . test.sh 
/bin/bash

如上的命令做事打印出脚本名,bash的输出结果是该脚本名test.sh,而source的结果是当前shell的脚本名就是/bin/bash
再看一下bigtop-detect-javahome脚本是个啥,直接看这个shell脚本的最后

if [ -z "${JAVA_HOME}" ]; then
    for candidate_regex in ${JAVA_HOME_CANDIDATES[@]}; do
        for candidate in `ls -rvd ${candidate_regex}* 2>/dev/null`; do
            if [ -e ${candidate}/bin/java ]; then
                export JAVA_HOME=${candidate}
                break 2
            fi
        done
    done
fi

它在看环境变量或者上下文中是否定义了JAVA_HOME,如果为空则进行候选集的遍历,直到有一个符合在在目录下找到/bin/java就设置(export)这个候选路径为JAVA_HOME,首先环境变量中是有JAVA_HOME的在/opt/jdk1.8.0_60

echo $JAVA_HOME
/opt/jdk1.8.0_60

所以实际上是没有走这个脚本的路基,再看一下它的候选JAVA_HOME是怎么写的

case ${BIGTOP_JAVA_MAJOR} in
    6) JAVA_HOME_CANDIDATES=(${JAVA6_HOME_CANDIDATES[@]})
    ;;
    7) JAVA_HOME_CANDIDATES=(${JAVA7_HOME_CANDIDATES[@]} ${OPENJAVA7_HOME_CANDIDATES[@]})
    ;;
    8) JAVA_HOME_CANDIDATES=(${JAVA8_HOME_CANDIDATES[@]} ${OPENJAVA8_HOME_CANDIDATES[@]})
    ;;
    *) JAVA_HOME_CANDIDATES=(${JAVA7_HOME_CANDIDATES[@]}
                             ${JAVA8_HOME_CANDIDATES[@]}
                             ${MISCJAVA_HOME_CANDIDATES[@]}
                             ${OPENJAVA7_HOME_CANDIDATES[@]}
                             ${OPENJAVA8_HOME_CANDIDATES[@]})
    ;;
esac

这里它在通过case in判断BIGTOP_JAVA_MAJOR这个变量是数字几,代表JAVA的版本,如果没有设置这个变量则将JAVA7_HOME_CANDIDATES,JAVA8_HOME_CANDIDATES,MISCJAVA_HOME_CANDIDATES,OPENJAVA7_HOME_CANDIDATES,OPENJAVA8_HOME_CANDIDATES这5个数组合并为一个新数组作为候选池子,这个地方使用了通过多个数组合并为一个数组的方法,即使用@获得数组所有元素再包一个圆括号,测试如下

a=(1 2 3)
b=(2 3 4)
c=(3 4 5)
d=(${a[@]} ${b[@]} ${c[@]})
echo ${d[@]}  # 1 2 3 2 3 4 3 4 5

来看一下它的候选数组是怎么写的,以JAVA8为例

JAVA8_HOME_CANDIDATES=(
    '/usr/java/jdk1.8'
    '/usr/java/jre1.8'
    '/usr/lib/jvm/j2sdk1.8-oracle'
    '/usr/lib/jvm/j2sdk1.8-oracle/jre'
    '/usr/lib/jvm/java-8-oracle'
)

它是一个所有预设的可能路径,精确到版本但是具体版本号可以模糊前缀匹配,比如/usr/java/jdk1.8.0_60
再接下来看一下最核心的最后一行

exec $LIB_DIR/spark2/bin/spark-shell "$@"

这一行是找到了spark2的真正工作目录且调用他bin目录下的spark-shell启动spark-shell,同时传入了一个参数"$@"


Spark目录下spark-shell启动命令

上一篇下一篇

猜你喜欢

热点阅读