从spark启动任务源头 $SPARK_HOME/bin/spark-submit 开始阅读spark源码。
一、脚本阶段
提交任务命令,先使用local模式
spark-submit --master local --class com.lof.main.SparkPi /Users/user/Desktop/SparkPi.jar
sparkPi代码:
public class SparkPi {
public static void main(String[] args) {
SparkSession spark = SparkSession.builder().appName("JavaSparkPi").getOrCreate();
JavaSparkContext jsc = new JavaSparkContext(spark.sparkContext());
int slices = (args.length == 1) ? Integer.parseInt(args[0]) : 2;
List<Integer> l = new ArrayList<Integer>(100000 * slices);
for (int i = 0; i < n; i++) {
l.add(i);
}
JavaRDD<Integer> dataSet = jsc.parallelize(l, slices);
int count = dataSet.map(integer -> {
double x = Math.random() * 2 - 1;
double y = Math.random() * 2 - 1;
return (x * x + y * y <= 1) ? 1 : 0;
}).reduce((integer, integer2) -> integer + integer2);
System.out.println("Pi is roughly " + 4.0 * count / n);
spark.stop();
}
}
spark的任务提交脚本 $SPARK_HOME/bin/spark-submit
#!/usr/bin/env bash
# 如环境变量中没有配置 SPARK_HOME , 自动配置
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 "$@"
调用bin目录下的 spark-class ,并将参数传过去,接下来看一看 spark-class
#!/usr/bin/env bash
## 只保留核心代码
# 执行load-spark-env.sh,最终调用spark-env.sh
. "${SPARK_HOME}"/bin/load-spark-env.sh
# 查找jars目录
# 源码编译的目录下没有 ${SPARK_HOME}/jars 没有该目录
if [ -d "${SPARK_HOME}/jars" ]; then
SPARK_JARS_DIR="${SPARK_HOME}/jars"
else
SPARK_JARS_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION/jars"
fi
# 执行 lauancher.Main 方法,生成 commond 命令
build_command() {
"$RUNNER" -Xmx128m -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@"
printf "%d\0" $?
}
# Turn off posix mode since it does not allow process substitution
set +o posix
CMD=()
while IFS= read -d '' -r ARG; do
CMD+=("$ARG")
done < <(build_command "$@")
CMD=("${CMD[@]:0:$LAST}")
echo "${CMD[@]}"
exec "${CMD[@]}"
将"{CMD[@]}"输出,最终 spark-class 执行的命令为
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java
-cp /spark-2.3.2/conf/:/spark-2.3.2/assembly/target/scala-2.11/jars/*:/hadoop-2.7.3/etc/hadoop/
-Xmx1g org.apache.spark.deploy.SparkSubmit
--master local --class org.apache.spark.examples.SparkPi /Users/xxx/Desktop/SparkPi
可以看出,调用的是org.apache.spark.deploy.SparkSubmit类执行提交操作
源码中找到 org.apache.spark.deploy.SparkSubmit
在idea中,将–master local --class org.apache.spark.examples.SparkPi /Users/xxx/Desktop/SparkPi设置为启动参数,模拟执行
二、进入代码
org.apache.spark.deploy.SparkSubmit中main方法 核心是:1、加载参数 2、提交任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kkg8iUfJ-1585278184804)(/Users/lofty/Library/Application Support/typora-user-images/image-20200326172915847.png)]
1、加载环境参数
// 将提交参数解析及加载环境变量
val appArgs = new SparkSubmitArguments(args)
SparkSubmitArguments中核心方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEK842eW-1585278184806)(/Users/lofty/Library/Application Support/typora-user-images/image-20200326175820154.png)]
根据这3个方法可以看出,在提交过程中的参数的优先级:
命令行参数 > spark-defaults.conf > 环境变量
2、提交任务
提交任务首先执行SparkSubmit.scala 中的 submit()方法。
然后执行doMain()方法
在doMain()方法中调用runMain() 方法,runMain方法较长,只保留核心代码
private def runMain(
childArgs: Seq[String],
childClasspath: Seq[String],
sparkConf: SparkConf,
childMainClass: String,
verbose: Boolean): Unit = {
// 获取ClassLoader
val loader =
if (sparkConf.get(DRIVER_USER_CLASS_PATH_FIRST)) {
new ChildFirstURLClassLoader(new Array[URL](0),
Thread.currentThread.getContextClassLoader)
} else {
new MutableURLClassLoader(new Array[URL](0),
Thread.currentThread.getContextClassLoader)
}
Thread.currentThread.setContextClassLoader(loader)
// TODO 将jar包路径添加进ClassLoader中,以便后续通过反射直接调用直接
for (jar <- childClasspath) {
addJarToClasspath(jar, loader)
}
var mainClass: Class[_] = null
try {
// TODO 创建主类 class
mainClass = Utils.classForName(childMainClass)
} catch {
// 此处省略
}
// TODO 判断 SparkApplication是否其 父类 (主类是否实现SparkApplication接口)
val app: SparkApplication = if (classOf[SparkApplication].isAssignableFrom(mainClass)) {
mainClass.newInstance().asInstanceOf[SparkApplication]
} else {
// 此处省略
}
// TODO 直接通过反射执行 传入jar包的main方法
new JavaMainApplication(mainClass)
}
try {
// 调用start方法启动
app.start(childArgs.toArray, sparkConf)
} catch {
// 此处省略
}
}
第一步:获取当前线程的 ClassLoader
第二步:将application的jar包路径添加进ClassLoader中,以便后续直接通过反射调用
第三步:创建application主类的class对象
第四步:如果主类实现SparkApplication方法,则调用start()方法启动。否则通过JavaMainApplication的start()反射调用主类的main方法。
local模式就这么简单,加载参数、启动任务,全在当前JVM中执行
下一篇聊聊生产环境常用的 Yarn Cluster模式。