过程描述:
1.通过Shell脚本启动Master,Master类继承Actor类,通过ActorySystem创建并启动。
2.通过Shell脚本启动Worker,Worker类继承Actor类,通过ActorySystem创建并启动。
3.Worker通过Akka或者Netty发送消息向Master注册并汇报自己的资源信息(内存以及CPU核数等),以后就是定时汇报,保持心跳。
4.Master接受消息后保存(源码中通过持久化引擎持久化)并发送消息表示Worker注册成功,并且定时调度,移除超时的Worker。
5.通过Spark-Submit提交作业或者通过Spark Shell脚本连接集群,都会启动一个Spark进程Driver。
6.Master拿到作业后根据资源筛选Worker并与Worker通信,发送信息,主要包含Driver的地址等。
7.Worker进行收到消息后,启动Executor,Executor与Driver通信。
8.Driver端计算作业资源,transformation在Driver 端完成,划分各个Stage后提交Task给Executor。
9.Exectuor针对于每一个Task读取HDFS文件,然后计算结果,最后将计算的最终结果聚合到Driver端或者写入到持久化组件中。
Spark启动Shell脚本
1.start-all.sh
可以中脚本文件看出,spark启动事首选启动spark-config.sh,然后启动start-master.sh 最后启动脚本
2.start-master.sh脚本
${SPARK_HOME}/sbin"/spark-daemon.sh start $CLASS1 \
--ip $SPARK_MASTER_IP--port $SPARK_MASTER_PORT--webui-port $SPARK_MASTER_WEBUI_PORT\
$ORIGINAL_ARGS
/sbin"/spark-daemon.sh start $CLASS1 \
--ip $SPARK_MASTER_IP--port $SPARK_MASTER_PORT--webui-port $SPARK_MASTER_WEBUI_PORT\
$ORIGINAL_ARGS
start-master.sh脚本主要执行的是这一块逻辑,主要使用spark-daemon.sh利用传入的参数启动Spark。
3.spark-daemon.sh脚本
nohup nice -n"$SPARK_NICENESS""${SPARK_HOME}"/bin/spark-class $command"$@">> "$log"2>&1 < /dev/null &
-n"$SPARK_NICENESS""${SPARK_HOME}"/bin/spark-class $command"$@">> "$log"2>&1 < /dev/null &
脚本中启动执行的主要脚本命令是上述代码,主要通过${SPARK_HOME}/bin/spark-class启动,接下来我们看一下spark-class
4.spark-class文件
done< <("$RUNNER"-cp"$LAUNCH_CLASSPATH"org.apache.spark.launcher.Main "$@")
exec "${CMD[@]}"
//这里是$RUNNER代表的含义
if[ -n"${JAVA_HOME}"]; then
RUNNER="${JAVA_HOME}/bin/java"
else
if[ `command -v java`]; then
RUNNER="java"
else
echo"JAVA_HOME is not set">&2
exit1
fi
fi
//这里是$LAUNCH_CLASSPATH表示的含义
SPARK_ASSEMBLY_JAR=
if[ -f"${SPARK_HOME}/RELEASE"]; then
ASSEMBLY_DIR="${SPARK_HOME}/lib"
else
ASSEMBLY_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION"
fi
SPARK_ASSEMBLY_JAR="${ASSEMBLY_DIR}/${ASSEMBLY_JARS}"
LAUNCH_CLASSPATH="$SPARK_ASSEMBLY_JAR"
< <("$RUNNER"-cp"$LAUNCH_CLASSPATH"org.apache.spark.launcher.Main "$@")
exec "${CMD[@]}"
//这里是$RUNNER代表的含义
if[ -n"${JAVA_HOME}"]; then
RUNNER="${JAVA_HOME}/bin/java"
else
if[ `command -v java`]; then
RUNNER="java"
else
echo"JAVA_HOME is not set">&2
exit1
fi
fi
//这里是$LAUNCH_CLASSPATH表示的含义
SPARK_ASSEMBLY_JAR=
if[ -f"${SPARK_HOME}/RELEASE"]; then
ASSEMBLY_DIR="${SPARK_HOME}/lib"
else
ASSEMBLY_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION"
fi
SPARK_ASSEMBLY_JAR="${ASSEMBLY_DIR}/${ASSEMBLY_JARS}"
LAUNCH_CLASSPATH="$SPARK_ASSEMBLY_JAR"
从上面的代码可以看出主要是通过获取Java环境然后启动Spark程序
Spark Submit脚本
关于作业提交的主要脚本是spark-submit脚本,主要执行的代码是
exec "$SPARK_HOME"/bin/spark-class org.apache.spark.deploy.SparkSubmit "${ORIG_ARGS[@]}"
"$SPARK_HOME"/bin/spark-class org.apache.spark.deploy.SparkSubmit "${ORIG_ARGS[@]}"
这里主要的用到的类是org.apache.spark.deploy.SparkSubmit,接下来我们将要看SparkSubmit代码,这里的Spark代码基于Spark1.3.1
SparkSubmit类
/bin/spark-submit \
--classcn.edu.hust.WordCount \
--masterspark://207.184.161.138:7077 \
--executor-memory20G \
--total-executor-cores100\
/path/to/examples.jar \
1000
--classcn.edu.hust.WordCount \
--masterspark://207.184.161.138:7077 \
--executor-memory20G \
--total-executor-cores100\
/path/to/examples.jar \
1000
下面是SparkSubmit类的时序图:
具体叙述流程如下 :
1.SparkSubmit在main()方法中执行,然后根据提交的类型调用相应的方法,这里是"Submit",调用submit()方法,submit()里面进行一些判断后,使用反射Class.forName(childMainClass, true, loader),然后调用invoke()方法来调用程序员自己写的类,我们这里是WordCount。
2.在WordCount类中,main()方法里有调用SparkContext,SparkContext构造器使用createSparkEnv()方法,这个方法使用SparkEnv.createDriverEnv(conf, isLocal, listenerBus)方法创建SparkEnv对象;在SparkEnv类,调用create()方法来进行创建SparkEnv,在这个方法内部,有一个AkkaUtils.createActorSystem(actorSystemName, hostname, port, conf, securityManager)的调用过程,主要用来产生Akka中的ActorSystem以及得到绑定的端口号。
3.在创建SparkEnv对象后,SparkContext构造器使用代码SparkContext.createTaskScheduler(this, master)创建TaskScheduler对象,这里根据实际的提交模式来进行创建TaskScheduler对象,提交模式有:local、Mesos、Zookeeper、Simr、Spark,这里模们主要分析Spark集群下的模式;然后还需要创建一个SparkDeploySchedulerBackend对象;在创建TaskScheduler对象调用initialize()方法,这里选择调度模式,主要有两种模式,FIFO和FAIR,默认的调度模式;最后调用taskScheduler的start()方法,里面主要调用SparkDeploySchedulerBackend对象的start()方法,首先调用父类的start()方法产生一个用于和Executor通信的DriverActor对象,然后里面主要创建一个AppClient对象内部有ClientActor类对象,用于Driver和Master进行RPC通信。