Apache Spark is a fast and general-purpose cluster computing system.
以下分析的是Spark2.3.0 版本。
Submitting Applications
The
spark-submit
script in Spark’sbin
directory is used to launch applications on a cluster. It can use all of Spark’s supported cluster managers through a uniform interface so you don’t have to configure your application especially for each one.
Spark's bin目录下的spark-submit脚本用于在集群上启动应用程序。
spark-submit:
# 如果不存在SPARK_HOME的环境变量,就执行find-spark-home脚本,这个脚本肯定是设置SPARK_HOME,具体你可以自己去看看
20 if [ -z "${SPARK_HOME}" ]; then
21 source "$(dirname "$0")"/find-spark-home
22 fi
23
24 # disable randomized hash for string in Python 3.3+
25 export PYTHONHASHSEED=0
26
# 执行spark-class脚本,并传递了org.apache.spark.deploy.SparkSubmit等一些参数
27 exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"
spark-submit会去检查SPARK_HOME环境变量,并将提交的应用程序和相关类传递给spark-class脚本执行,接下来我们看看spark-class这个脚本。
注
这里涉及到一些shell编程的语法,可以一并简单的学习一下。
source命令:
通常用法:source filepath 或 . filepath
功能:使当前shell读入路径为filepath的shell文件并依次执行文件中的所有语句,通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。例如,当我们修改了/etc/profile文件,并想让它立刻生效,而不用重新登录,就可以使用source命令,如source /etc/profile。
exec命令:
exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。
spark-class:
# 同样的检查一遍spark_home环境变量
20 if [ -z "${SPARK_HOME}" ]; then
21 source "$(dirname "$0")"/find-spark-home
22 fi
23
# 执行load-spark-env.sh脚本,加载spark的配置信息
24 . "${SPARK_HOME}"/bin/load-spark-env.sh
25
26 # 寻找jvm
27 if [ -n "${JAVA_HOME}" ]; then
28 RUNNER="${JAVA_HOME}/bin/java"
29 else
30 if [ "$(command -v java)" ]; then
31 RUNNER="java"
32 else
33 echo "JAVA_HOME is not set" >&2
34 exit 1
35 fi
36 fi
37
38 # 寻找spark的jar包,并将jar赋值给了LAUNCH_CLASSPATH变量
39 if [ -d "${SPARK_HOME}/jars" ]; then
40 SPARK_JARS_DIR="${SPARK_HOME}/jars"
41 else
42 SPARK_JARS_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION/jars"
43 fi
44
45 if [ ! -d "$SPARK_JARS_DIR" ] && [ -z "$SPARK_TESTING$SPARK_SQL_TESTING" ]; then
46 echo "Failed to find Spark jars directory ($SPARK_JARS_DIR)." 1>&2
47 echo "You need to build Spark with the target \"package\" before running this program." 1>&2
48 exit 1
49 else
50 LAUNCH_CLASSPATH="$SPARK_JARS_DIR/*"
51 fi
52
53 # Spark的默认构建策略是组装一个包含其所有依赖项的jar。
# 在进行迭代开发时,这可能很麻烦。在本地开发时,可以创建一个包含所有Spark依赖项的程序集jar,然后在进行更改时仅重新打包Spark本身。
54 if [ -n "$SPARK_PREPEND_CLASSES" ]; then
55 LAUNCH_CLASSPATH="${SPARK_HOME}/launcher/target/scala-$SPARK_SCALA_VERSION/classes:$LAUNCH_CLASSPATH"
56 fi
57
58 # For tests
59 if [[ -n "$SPARK_TESTING" ]]; then
60 unset YARN_CONF_DIR
61 unset HADOOP_CONF_DIR
62 fi
70 build_command() {
# 启动jvm执行${LAUNCH_CLASSPATH}下org.apache.spark.launcher.Main类解析由spark-submit传进来的参数
71 "$RUNNER" -Xmx128m -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@"
72 printf "%d\0" $?
73 }
74
75 # 关闭posix模式,因为它不允许进程替换
76 set +o posix
# 将build_command函数的输出循环读入数组
77 CMD=()
78 while IFS= read -d '' -r ARG; do
79 CMD+=("$ARG")
80 done < <(build_command "$@")
81
82 COUNT=${#CMD[@]}
83 LAST=$((COUNT - 1))
84 LAUNCHER_EXIT_CODE=${CMD[$LAST]}
# 某些JVM失败会导致错误被打印到stdout(而不是stderr),这导致解析启动器输出的代码变得混乱。
# 在这些情况下,检查退出代码是否为整数,如果没有,则将其作为特殊错误情况处理。
89 if ! [[ $LAUNCHER_EXIT_CODE =~ ^[0-9]+$ ]]; then
90 echo "${CMD[@]}" | head -n-1 1>&2
91 exit 1
92 fi
93
94 if [ $LAUNCHER_EXIT_CODE != 0 ]; then
95 exit $LAUNCHER_EXIT_CODE
96 fi
97
# 做了一个切片
98 CMD=("${CMD[@]:0:$LAST}")
# 启动执行org.apache.spark.deploy.SparkSubmit类
99 exec "${CMD[@]}"
spark-class这个脚本是由spark-submit执行的:
- 第一步:首先会检查SPARK_HOME等环境变量和配置信息;
- 第二步:接着将寻找spark的二进制源码jars;
- 第三步:这个时候就会启动
org.apache.spark.launcher.Main
类解析spark-submit脚本传进来的参数; - 第四步:循环将第三步打印的结果循环读入数据
CMD
; - 第五步:执行由
CMD
组成的命令行。
注
这里有一些不常见的命令:
java:
执行类,-cp <目录和 zip/jar 文件的类搜索路径>
set:
可以设置各种shell选项或者列 出shell变量.单个选项设置常用的特性.在某些选项之后-o参数将特殊特性打开.在某些选项之后使用+o参数将关闭某些特性,不带任何参数的set命 令将显示shell的全部变量.除非遇到非法的选项,否则set总是返回ture
env:
用来显示当前用户环境变量
export:
用来显示和设置当前用户环境变量
调试CMD
的输出
Running the Examples and Shell
Example applications are also provided in Python. For example:
./bin/spark-submit examples/src/main/python/pi.py 10
上面的示例程序可以在spark安装目录的example里面找到,下面我在spark-class脚本的98行和99行之间增加一个CMD
数组的输出。
echo "---------CMD start-----------"
echo "${CMD[@]}"
echo "---------CMD end------------"
然后提交运行以上示例程序:
$ ./spark-submit ${SPARK_HOME}/examples/src/main/python/pi.py 10
---------CMD start-----------
/opt/appl/jdk1.8.0_144/bin/java -cp /opt/appl/spark/conf/:/opt/appl/spark/jars/*:/opt/appl/hadoop/etc/hadoop/:/opt/appl/hadoop/share/hadoop/common/lib/*:/opt/appl/hadoop/share/hadoop/common/*:/opt/appl/hadoop/share/hadoop/hdfs/:/opt/appl/hadoop/share/hadoop/hdfs/lib/*:/opt/appl/hadoop/share/hadoop/hdfs/*:/opt/appl/hadoop/share/hadoop/yarn/lib/*:/opt/appl/hadoop/share/hadoop/yarn/*:/opt/appl/hadoop/share/hadoop/mapreduce/lib/*:/opt/appl/hadoop/share/hadoop/mapreduce/*:/opt/appl/hadoop/contrib/capacity-scheduler/*.jar -Xmx5G org.apache.spark.deploy.SparkSubmit /opt/appl/spark/examples/src/main/python/pi.py 10
---------CMD end-------------
由打印出来的CMD得出spark-class脚本最后执行的是org.apache.spark.deploy.SparkSubmit
这个类,而我们提交的是一个.py文件程序,我们想知道的python解释器和jvm通信的过程还没有找到,下次我们将分析org.apache.spark.deploy.SparkSubmit
这个类。