通过学习Spark源码为了更深入的了解Spark。主要按照以下流程进行Spark的源码分析,包含了Spark集群的启动以及任务提交的执行流程:
- Spark RPC分析
- start-all.sh
- Master启动分析
- Work启动分析
- spark-submit.sh脚本分析
- SparkSubmit分析
- SparkContext初始化
5.spark-submit.sh脚本分析
通过spark-submit.sh提交任务,查看详细信息,如下:
set CLASS=org.apache.spark.deploy.SparkSubmit
"%~dp0spark-class2.cmd" %CLASS% %*
6.SparkSubmit分析
这里看到是通过SparkSubmit这个类进行任务的提交,找到这个类的main方法,如下:
override def main(args: Array[String]): Unit = {
val submit = new SparkSubmit() {
self =>
override protected def parseArguments(args: Array[String]): SparkSubmitArguments = {
new SparkSubmitArguments(args) {
override protected def logInfo(msg: => String): Unit = self.logInfo(msg)
override protected def logWarning(msg: => String): Unit = self.logWarning(msg)
}
}
override protected def logInfo(msg: => String): Unit = printMessage(msg)
override protected def logWarning(msg: => String): Unit = printMessage(s"Warning: $msg")
/**
* 注释:
*/
override def doSubmit(args: Array[String]): Unit = {
try {
/**
* 注释:
*/
super.doSubmit(args)
} catch {
case e: SparkUserAppException => exitFn(e.exitCode)
}
}
}
/**
* 注释: 通过 SparkSubmit 的 doSubmit 提交
*/
submit.doSubmit(args)
}
这里是通过super.doSubmit方法提交。
def doSubmit(args: Array[String]): Unit = {
// Initialize logging if it hasn't been done yet. Keep track of whether logging needs to
// be reset before the application starts.
val uninitLog = initializeLogIfNecessary(true, silent = true)
val appArgs = parseArguments(args)
if (appArgs.verbose) {
logInfo(appArgs.toString)
}
/**
* 注释:
* 根据你的 spark-submit 的命令来决定到底执行那个方法
* $CLASS 参数
*/
appArgs.action match {
// TODO_MA 注释: 提交 app 运行
case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
case SparkSubmitAction.PRINT_VERSION => printVersion()
}
}
最终是通过JavaMainApplication进行任务的提交
private[deploy] class JavaMainApplication(klass: Class[_]) extends SparkApplication {
/**
* 注释: 启动 JavaMainApplication
*/
override def start(args: Array[String], conf: SparkConf): Unit = {
/**
* 注释: 通过反射获取 自定义业务逻辑主类的 main 方法实例
*
* new Array[String](0).getClass = $CLASS = SparkPi
*/
val mainMethod = klass.getMethod("main", new Array[String](0).getClass)
if (!Modifier.isStatic(mainMethod.getModifiers)) {
throw new IllegalStateException("The main method in the given main class must be static")
}
val sysProps = conf.getAll.toMap
sysProps.foreach { case (k, v) => sys.props(k) = v }
/**
* 注释: 执行 main 方法
* 才会跳转到 SparkPi 的 main 方法
*/
mainMethod.invoke(null, args)
}
}
7.SparkContext初始化
这里以一个案例为入口进入源码查看实现过程的细节。
val spark: SparkSession = SparkSession.builder.appName("Spark").getOrCreate()
在getOrCreate()中可以看到Spark的创建,如下:
/**
* 注释: 重要的地方:创建SparkContext
*/
// No active nor global default session. Create a new one.
val sparkContext: SparkContext = userSuppliedContext.getOrElse {
// TODO_MA 注释: 创建 SparkConf 中
val sparkConf = new SparkConf()
options.foreach { case (k, v) => sparkConf.set(k, v) }
// set a random app name if not given.
if (!sparkConf.contains("spark.app.name")) {
sparkConf.setAppName(java.util.UUID.randomUUID().toString)
}
/**
* 注释: 创建 SparkContext
*/
SparkContext.getOrCreate(sparkConf) // Do not update `SparkConf` for existing `SparkContext`, as it's shared by all sessions.
}
再次进入getOrCreate(sparkConf)方法会看到如下代码:
def getOrCreate(config: SparkConf): SparkContext = {
// Synchronize to ensure that multiple create requests don't trigger an exception
// from assertNoOtherContextIsRunning within setActiveContext
SPARK_CONTEXT_CONSTRUCTOR_LOCK.synchronized {
if (activeContext.get() == null) {
/**
* 注释: new SparkContext(config)
*/
setActiveContext(new SparkContext(config), allowMultipleContexts = false)
} else {
if (config.getAll.nonEmpty) {
logWarning("Using an existing SparkContext; some configuration may not take effect.")
}
}
activeContext.get()
}
}
可以看到SparkContext的创建是通过其构造方法创建,从这个类中提取SparkContext的初始化步骤:
- 创建Spark执行环境SparkEnv;
- 创建并且初始化Spark UI;
- hadoop相关配置以及Executor环境变量的设置;
- 创建任务调度TaskScheduler; -----重要
- 创建和启动DAGScheduler; -----重要
- TaskScheduler的启动; -----重要
- 初始化管理器BlockManager(BlockManager是存储体系的主要组件之一)
- 启动测量系统MetricsSystem;
- 创建和启动Executor分配管理器ExecutorAllocationManager;
- ContextCleaner的启动和创建。
- Spark环境更新
- 创建DAGSchedulerSource和BlockManagerSource;
- 将SparkContext标记激活
SparkContext初始化这里主要查看以下几点:
1.TaskScheduler,实现类是:TaskSchedulerImpl
2.DagScheduler
DAGSchedulerEventProcessLoop初始化和启动两件事
3.SchedulerBackEnd
有两个成员变量
1、负责和master通信:DriverEndPoint
2、负责和worker通信:StandaloneAppClient
在代码中如下:
/**
* 注释:第四步:创建任务调度TaskScheduler
* 如果集群管理器是standalone模式:该方法返回(StandaloneSchedulerBackend,TaskSchedulerImpl)
*/
// Create and start the scheduler
val (sched, ts) = SparkContext. createTaskScheduler(this, master, deployMode)
_schedulerBackend = sched
_taskScheduler = ts
/**
* 注释:第五步:创建和启动DAGScheduler
* 1、内部初始化了一个:DAGSchedulerEventProcessLoop 用来处理各种任务
* 2、在 DAGSchedulerEventProcessLoop 创建的时候,构造函数的内部的最后一句代码执行了 DAGSchedulerEventProcessLoop的启动。
* 将来任务的提交,取消等,都会发送一个事件给 DAGSchedulerEventProcessLoop
* 从而触发 dagScheduler.onRecevie() 的运行。
*/
_dagScheduler = new DAGScheduler(this)
_heartbeatReceiver.ask[Boolean](TaskSchedulerIsSet)
/**
* 注释:第六步:TaskScheduler的启动,主要任务:backend.start()
*/
// start TaskScheduler after taskScheduler sets DAGScheduler reference in DAGScheduler's constructor
_taskScheduler.start()
这里先查看下DAGScheduler初始化,核心代码如下:
/**
* 注释:重点 这段代码执行了第一件事。
* 初始化了一个事件处理器
* 真正的来处理一些事件: JobSubmitted JobCancelled
*
*/
private[spark] val eventProcessLoop = new DAGSchedulerEventProcessLoop(this)
/**
* 注释: 之前看到有地方把 SchedulerBackEnd 设置成 TaskScheduler 的成员变量
* 现在这儿把 DAGScheduler 也设置成 TaskScheduler 的成员变量
*/
taskScheduler.setDAGScheduler(this)
DAGSchedulerEventProcessLoop继承EventLoop。EventLoop中有开启线程来处理DAGScheduler
/**
* 注释: driver 中,初始化了一个 dagSchedudelr
* 它里面又初始化了一个 eventThread 专门用来处理 JobSubmitted
*/
// Exposed for testing.
private[spark] val eventThread = new Thread(name) {
setDaemon(true)
override def run(): Unit = {
try {
while (!stopped.get) {
// TODO_MA 注释:获取消息
// TODO_MA 注释:一定要注意:当 sparkContext 还没有初始化好的时候,是不执行 sc.runJob 提交任务的。
// TODO_MA 注释:当执行 sc.runJob(sc) 的时候,就会提交 Job 到这儿来。
val event = eventQueue.take()
try {
/**
* 注释:根据事件的类型,调用不同的 handleXXX 方法来进行处理。
* 当接收到任务提交的时候: event = JobSubmitted
*/
onReceive(event)
} catch {
case NonFatal(e) => try {
onError(e)
} catch {
case NonFatal(e) => logError("Unexpected error in " + name, e)
}
}
}
} catch {
case ie: InterruptedException => // exit even if eventQueue is not empty
case NonFatal(e) => logError("Unexpected error in " + name, e)
}
}
}
可以在DAGScheduler中找到通过eventProcessLoop.start()来执行这段代码。onReceive方法最终是通过DAGSchedulerEventProcessLoop的onReceive方法来执行。详细流程如下图所示

TaskScheduler的启动执行如下:
override def start() {
/**
* 注释:启动 StandalonedSchedulerBackend
* SchedulerBackend backend 在 TaskSchedulerImpl 初始化的时候实例化的
* 实现类是:StandaloneSchedulerBackend
*/
backend.start()
if (!isLocal && conf.getBoolean("spark.speculation", false)) {
logInfo("Starting speculative execution thread")
speculationScheduler.scheduleWithFixedDelay(new Runnable {
override def run(): Unit = Utils.tryOrStopSparkContext(sc) {
// TODO_MA 注释:推测执行
checkSpeculatableTasks()
}
}, SPECULATION_INTERVAL_MS, SPECULATION_INTERVAL_MS, TimeUnit.MILLISECONDS)
}
}
这里主要看backend.start方法,实际的执行如下:核心是创建DriverEndpointRef,即有创建和启动DriverEndpoint。
override def start() {
val properties = new ArrayBuffer[(String, String)]
for ((key, value) <- scheduler.sc.conf.getAll) {
if (key.startsWith("spark.")) {
properties += ((key, value))
}
}
/**
* 注释: 创建一个 DriverEndPoint
* 负责跟 master 打交道的
*/
// TODO (prashant) send conf instead of properties
driverEndpoint = createDriverEndpointRef(properties)
}
在StandaloneSchedulerBackend中通过调用父类的start() 方法启动一个 DriverEndPoint,又有启动StandaloneAppClient,如下:
/**
* 注释:里面维护了一个 DriverEndPoint 主要用来向 Executor分发任务
*/
client = new StandaloneAppClient(sc.env.rpcEnv, masters, appDesc, this, conf)
client.start()
4737



被折叠的 条评论
为什么被折叠?



