spark-submit 到底做了什么

本文的目的是提升linux shell脚本的功力,以及熟悉spark-submit提交的具体流程

spark-sumbit*

#!/usr/bin/env bash

if [ -z "${SPARK_HOME}" ]; then
  source "$(dirname "$0")"/find-spark-home
fi

# disable randomized hash for string in Python 3.3+
export PYTHONHASHSEED=0

exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

第一段

if [ -z "${SPARK_HOME}" ]; then
  source "$(dirname "$0")"/find-spark-home
fi

背景知识

  1. 双引号

    "${SPARK_HOME}"${SPARK_HOME} 这样写是等效的,均代表获取环境变量中的SPARK_HOME所引用的值
    在shell中 “$USER” 与 $USER 其代表的值是一样的。 加不加并不会记录到变量中,也不会添加到字符串中,其代表的一个根本的含义就是 定义 变量的意思。

    默认情况下,在shell编程中,不带双引号的都会被当作字符串来处理。这个惯例比较特殊。反正与一般意义上的高级编程语言不太一致。(读者可以自己揣摩一下单引号会得出什么样的结论)

    #!/bin/bash
    if [ $USER = "$USER" ];then
            echo "eq" # 执行eq
    else
            echo "neq"
    fi
    
    
  2. if(空格)[(空格)-[a-z](空格)(string)(空格)];then 语法

    这个是常见的shell 字符串条件判断的格式,-z代表不存在,请注意空格是必填的,这个是跟处理shell脚本的编译器设置的语法解析器有关,或者说内置的正则表达式就是这么获取条件匹配的
    在方括号内退出码为0(exit 0即正常退出)则执行then后面的语句,否则执行其他分支

    homewell:/home/hadoop/shareh$ cat /etc/passwd | grep abc
    homewell:/home/hadoop/shareh$ echo $?
    1
    

    比较其原型 是test命令

  3. source命令(直接copy命令)

    有人说是点(.)命令,也就是执行目标文件中的命令,但是source 不会执行生成子shell(概念要理清,这个子shell代表这创建一个新的进程被父进程管理环境变量)

    如果读者学过C++或者C,都会接触宏(#define)关键字,代表者代码在编译的时候直接把代码copy到引用了宏定义的代码段中,或者类似于C++中的内联函数,都是代码的直接copy.可以看 综合案例

    因此可以使用另一个脚本中暴露(export)的变量,因为相当于把另一脚本中的代码,直接copy到当前脚本中

    参考1
    参考2

  4. dirname 命令

    获取指定虚拟目录的父目录

    homewell:/home/hadoop/shareh$ dirname /home/hadoop/shareh/
    /home/hadoop
    homewell:/home/hadoop/shareh$ dirname /home/hadoop/shareh/abc
    abc/    abcd/   abc.sh  
    homewell:/home/hadoop/shareh$ dirname /home/hadoop/shareh/abc.sh 
    /home/hadoop/shareh
    
  5. 位置参数

    $0是调用程序的时候第一个字符串,通常与程序名一致,往往有些博客简单地把它理解成程序名,这样理解是很受伤的,很容易误伤,针对这种情况。希望读者能区分不同场景来理解(查看本案例)

    $1是第一个参数,$2是第二个参数

    5.案例

        #!/bin/bash
    
        echo "\$0:""$0" 
        echo "\$1:""$1"
        # 超过10个参数的用{}包裹起来,否则 $10 == "$1" + "0"
        echo "第十个参数""${10}"
    
        # 第一 全路径方式
        homewell:~$ /home/homewell/shell1/testdoller.sh 23
        $0:/home/homewell/shell1/testdoller.sh
        $1:23
        
        # 全局变量方式(同上是一致的)
        homewell:~/shell1$ export SHELLTEST_HOME="/home/homewell/shell1"
        homewell:~/shell1$ export PATH="$SHELLTEST_HOME:$PATH"
        homewell:~/shell1$ testdoller.sh 1235
        $0:/home/homewell/shell1/testdoller.sh
        $1:1235
        
        # 第二种 通过相对路径1
        homewell:~$ shell1/testdoller.sh 124
        $0:shell1/testdoller.sh
        $1:124
        
        # 第二种 通过相对路径2
        homewell:~$ ./shell1/testdoller.sh 124
        $0:./shell1/testdoller.sh
        $1:124
        
        # 第三种 直接以./方式执行
        homewell:~$ cd shell1/
        homewell:~/shell1$ ./testdoller.sh 125
        $0:./testdoller.sh
        $1:125
        
        # 第四种 通过sh 命令执行
        homewell:~/shell1$ sh testdoller.sh 126
        $0:testdoller.sh
        $1:126
    
    
    

扩展内容 其他内置的跟$有关的位置参数

综合案例

-rwxr-xr-x  1 zhangll zhangll  184 10月  2 21:14 pid2.sh*
-rwxrwxrwx  1 zhangll zhangll 1292 10月  2 21:40 pid.sh*

pid.sh

#!/bin/bash
表示当前shell
echo "pid.sh当前shell pid进程(\$$):$$"
echo "pid.sh当前shell最近一个后台进程pid(\$!):" "$!"
echo "pid.sh前面一个命令的退出码 (\$?) : $?"



# 执行当前的目录路径下的pid2.sh
echo "-------------./pid2.sh--------------"
./pid2.sh
echo "pid.sh当前进程打印 pid2中的pid2暴露的变量(\$PID2): $PID2"


echo "-------------查看(子进程)的一些特性------------"
# 向外暴露a变量,在父进程中无法查看,只有在子shell中能看
export a=1
echo "pid.sh当前shell显示a 变量(\$a):"$a
(export b=2; echo "子进程查看父进程暴露的(a):""$a";echo "子进程暴露的变量(\$b):""$b";echo "子进程打印的进程(\$\$):""$$";echo "子进程(\$BASHPID):$BASHPID")
echo "pid.sh 在 (子进程之后)当前shell最近一个后台进程pid2(\$!):" "$!"
echo "pid.sh当前shell显示子进程暴露的b变量(\$b) : $b"



echo "------------(./pid2.sh &)------------"
./pid2.sh &
echo "pid.sh 在 (./pid2.sh &)最近一个后台进程pid2(\$!):" "$!"
echo "pid.sh当前进程打印 pid2中的pid2暴露的变量(\$PID2): $PID2"




echo "-----------source pid2.sh -----------"
source ./pid2.sh
echo "pid.sh source pid2.sh 之后当前进程打印 pid2中的pid2暴露的变量(\$PID2): $PID2"


exit 0


pid2.sh

#!/bin/bash
echo "######pid2.sh 打印的 (\$\$):$$"
echo "######pid2.sh 打印的 (\$BASHPID):$BASHPID"
echo "######pid2.sh 打印的最近一个后台进程 (\$!):$!"
export PID2=30

执行结果

pid.sh当前shell pid进程($$):9553
pid.sh当前shell最近一个后台进程pid($!): 
pid.sh前面一个命令的退出码 ($?) : 0


-------------./pid2.sh--------------
######pid2.sh 打印的 ($$):9554
######pid2.sh 打印的 ($BASHPID):9554
######pid2.sh 打印的最近一个后台进程 ($!):
pid.sh当前进程打印 pid2中的pid2暴露的变量($PID2): 


-------------查看(子进程)的一些特性------------
pid.sh当前shell显示a 变量($a):1
子进程查看父进程暴露的(a):1
子进程暴露的变量($b):2
子进程打印的进程($$):9553
子进程($BASHPID):9555
pid.sh 在 (子进程之后)当前shell最近一个后台进程pid2($!): 
pid.sh当前shell显示子进程暴露的b变量($b) : 


------------(./pid2.sh &)------------
######pid2.sh 打印的 ($$):9556
######pid2.sh 打印的 ($BASHPID):9556
######pid2.sh 打印的最近一个后台进程 ($!):
pid.sh 在 (./pid2.sh &)最近一个后台进程pid2($!): 9556
pid.sh当前进程打印 pid2中的pid2暴露的变量($PID2): 


-----------source pid2.sh -----------
######pid2.sh 打印的 ($$):9553
######pid2.sh 打印的 ($BASHPID):9553
######pid2.sh 打印的最近一个后台进程 ($!):9556
pid.sh source pid2.sh 之后当前进程打印 pid2中的pid2暴露的变量($PID2): 30

可以发现

  1. $! 代表当前shell进程空间最近一个执行的后台进程的id,并不会获取父进程空间的最近一次后台进程pid
    后台进程与子进程的区别
  2. ()括号包裹的虽然是开启了一个新的空间,但是其进程id与父亲的一致,但是在其内暴露(export)的变量,父进程无法获取
  3. $$ 并非代表当前shell的pid,

解读

不管以./spark-submit 还是 直接执行spark-submit,当不存在SPARK_HOME环境变量的时候,就执行当前目录(spark-submit所在目录)中的find-spark-home命令
很显然find-spark-home 要做的工作肯定是设置一个 SPARK_HOME 环境变量用来给后期使用

第二段

# disable randomized hash for string in Python 3.3+
export PYTHONHASHSEED=0

背景知识

  1. export 暴露/导出

    该关键词在nodejs/js/c/c++中会经常出现,代表这给调用该脚本的环境(脚本/shell环境)暴露一个变量,这个变量将会在当前进程空间中使用,包括子进程空间,但是不会影响父进程空间的变量

解读

很显然暴露了一个PYTHONHASHSEED (python hash seed)
这个是设置一个python随机
中的seed值,当改值为0的时候,代表着关闭随机hash种子,意味着对相同字符串多次使用hash函数,得到的是相同的值,这里可能考虑到pyspark上会调用hash值产生不必要的麻烦,比如spark中的group by 算子,由于可能数据在不同节点上,因此不同节点上会启动不同的python环境(可以理解成python session),python3.2.3之后就默认开启python环境的hash值,这意味着不同环境下的相同字符串,会默认在字符串前面添加一个盐值,防止DOS拒绝服务攻击手段,防止链表太长,降低性能。然后在group by 的相同字符串可能会在不同机器上输出不同 内容,因此为了能够保证数据一致性,需要关闭这个值

地址

第三段

exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

背景知识

  1. exec 命令 参数

    代表当前命令与参数 直接覆盖当前shell内容,这个是最暴力宏替换,不过会暴露当前的用户变量(局部变量)与全局变量

    案例见 Linux shell 整理之 复合命令行篇(四)

  2. exec 永久重定向操作

    案例见 Linux shell 整理之 复合命令行篇(四)

  3. 双引号

    同第一段 1)

解读

因此第三段的意思就是使用spark_home/bin/spark-class 并且传递两个参数
第一个是 org.apache.spark.deploy.SparkSubmit
第二个是 "@"shell"@" 代表当前shell 执行所窜入的参数个体集合的列表,根据实际情况当然可以用 ""来代替。$@ 代表着把输入参数作为多个个体存在,而 $ 可以作为一个单独的个体存在

总结语

纸上得来终觉浅,绝知此事要躬行。简简单单3句话,其涵盖的内容,不是一两句话能够得出来的。当你用心去体会的时候,你会发现,原来一切都这么简单明白

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章