1.spark-submit命令行说明
spark-submit的命令行格式为:
spark-submit [options] <app jar | python file | R file> [app arguments]
其中,app arguments为传递给主类main函数的参数。
spark-submit命令行选项同spark-shell的命令行选项。
2.spark-submit的提交模式
spark-submit有四种提交模式,由参数- -master和- -deploy-mode决定。
2.1.local模式
本地模式提交,由本地worker线程运行spark,涉及的文件都是本地文件。案例如下所示:
在$SPARK_HOME目录下执行
bin/spark-submit \
--class com.abc.edf.test.TestOfSparkContext \
--master local[2] \
/home/hadoop/data/test-jar/sql-1.0.jar
注意,local模式仅作为开发使用。
2.2.spark-on-yarn client模式
Yarn client模式中,Driver运行在Client端;Client请求container完成作业调度执行,client不能退出;日志在控制台输出,方便查看。
spark-submit提交jar包到yarn上的时候,数据输入路径、数据输出路径都必须是HDFS的路径,否则报错 :Input path does not exist。
连接的yarn集群位置由环境变量HADOOP_CONF_DIR决定。
案例如下所示:
bin/spark-submit \
--class com.abc.edf.test.TestOfSparkContext2OnYarn \
--master yarn \
/home/hadoop/data/test-jar/sql-3.0-onyarn.jar
或
bin/spark-submit \
--class com.abc.edf.test.TestOfSparkContext2OnYarn \
--master yarn \
--deploy-mode client \
/home/hadoop/data/test-jar/sql-3.0-onyarn.jar
2.3.spark-on-yarn cluster模式
yarn cluster模式中,Driver运行在applicationMaster;Client一但提交作业就可以关掉,作业已经运行在yarn上;日志在客户端看不到,以为作业运行在yarn上,通过“yarn logs -applicationId application_id”查看。
连接的yarn集群位置由环境变量HADOOP_CONF_DIR决定。
案例如下所示:
bin/spark-submit \
--class com.abc.edf.test.TestOfSparkContext2OnYarn \
--master yarn \
--deploy-mode cluster \
/home/hadoop/data/test-jar/sql-3.0-onyarn.jar
2.4.standalone模式
连接到指定的spark单机版集群(Spark standalone cluster)master。必须使用master所配置的接口,默认端口为7077。例如spark://10.10.10.10:7077。
standalone模式是Spark自带的,如果一个集群是Standalone的话,需要在每台机器上部署Spark。
案例如下所示:
./bin/spark-submit \
–-class com.imooc.spark.Test.TestOfSparkContext2 \
–-master spark://192.168.52.130:7077 \
–-executor-memory 4G \
–-total-executor-cores 6 \
/home/hadoop/data/jar-test/sql-1.0-yarnCluster.jar
2.5.Mesos模式
mesos模式必须要指定master,格式为mesos://{HOST}:{PORT}。连接到指定的Mesos集群,HOST参数是Mesos master的hostname,必须使用master所配置的端口,默认端口为5050。
建议大家在生产上使用spark on yarn模式,统一使用YARN对整个集群作业(MR/Spark)的资源调度。
3.spark-submit源码解析
查看spark-submit源码可发现,其最终执行的命令是
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit “$@"
下面来看下spark-class这个脚本。
有两个重要的方法,一个是build_command,如下所示:
build_command() {
"$RUNNER" -Xmx128m $SPARK_LAUNCHER_OPTS -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@"
printf "%d\0" $?
}
另一个是循环拼接执行指令,将参数放入CMD数组,如下所示:
CMD=()
DELIM=$'\n'
CMD_START_FLAG="false"
while IFS= read -d "$DELIM" -r ARG; do
if [ "$CMD_START_FLAG" == "true" ]; then
CMD+=("$ARG")
else
if [ "$ARG" == $'\0' ]; then
# After NULL character is consumed, change the delimiter and consume command string.
DELIM=''
CMD_START_FLAG="true"
elif [ "$ARG" != "" ]; then
echo "$ARG"
fi
fi
done < <(build_command "$@")
首先是在方法2里执行了build_command "$@",这里主要是构造java -cp指令,并且引入上面说的全部参数。即执行了JAVA -cp xxxx org.apache.spark.launcher.Main "$@",将org.apache.spark.deploy.SparkSubmit和run.sh中的参数全部传入,最终执行的是形式为java -cp org.apache.spark.launcher.Main [String]的java命令来启动一个jvm进程,执行的主方法为org.apache.spark.launcher.Main.main(),这个是真正执行的第一个spark的类。
接下来launcher.Main返回的结果将填充到CMD数组,用于执行最终命令,即执行命令:exec "${CMD[@]}",这里开始真正执行org.apache.spark.deploy.SparkSubmit类。
来看看这个exec命令:exec命令,是创建一个新的进程,只不过这个进程与前一个进程的ID是一样的。这样,原来的脚本剩余的部分就不能执行了,因为相当于换了一个进程。另外,创建新进程并不是说把所有的东西都直接复制,而是采用写时复制,即在新进程使用到某些内容时,才拷贝这些内容。
一句话总结: spark-class脚本定义build_command()方法并执行,以java -cp org.apache.spark.launcher.Main形式指定执行launcher.Main类,然后将launcher.Main返回的结果作为参数依次加载到CMD数组中,最后通过exec命令执行CMD命令,以当前进程来执行deploy.SparkSubmit类。