ITPub博客

首页 > 大数据 > > Spark任务 Driver的注册与启动源码剖析

Spark任务 Driver的注册与启动源码剖析

作者:格伯纳 时间:2020-05-12 12:04:01 0 删除 编辑

之前是在SparkSubmit源码剖析中,期间剖析SparkSubmit.scala通过doRunMain方法进入提交操作,提交后发生什么呢?

从提交的总体架构上来看,显示在Driver端提交,然后注册,那么提交后进入的步骤是Driver的注册于启动。直接先看Client.scala用于启动和终止Driver代码:

ClientApp来启动

接下调用了Master和Driver注册onStart方法

此时发送给master只有是注册过程,我们来看看master.scala

先判断Master状态是否为Alive,如果不为Alive,则回应提交Driver失败,需要向Alive的Master提交

否则

  1. 打印日志:已提交Driver“logInfo("Driver submitted "+ description.command.mainClass)”
  2. 使用createDriver方法,创建driver“valdriver= createDriver(description)”
  3. 使用持久化引擎将DriverInfo持久化“persistenceEngine.addDriver(driver)”
  4. 将driver添加到等待的waitingDriver队列中“waitingDrivers += driver”
  5. 将driver添加到drivers HashSet中“drivers.add(driver)”
  6. 执行scheduler,去进行调度“schedule()”

其中createDriver方法会把系统时间DriverID,Driver描述封装在DriverInfo对象中

然后是进入schedule调度方法

先来判断,当前master的状态,如果不是Alive,什么都不做

  1. Driver程序比Executor优先对当前ALIVE状态worker进行随机洗牌(shuffle)打乱workers集合内元素,得到打乱后的worker集合“valshuffledAliveWorkers=Random.shuffle(workers.toSeq.filter(_.state ==WorkerState.ALIVE))”
  2. 获取现在ALIVE的Worker数“valnumWorkersAlive= shuffledAliveWorkers.size”
  3. 遍历一个waitingDrivers集合的复制,将worker分配给每个等待的driver对于每一个driver,从分配给driver的最后一个worker开始,然后继续向前遍历,直到探索到所有的Alive Worker
  4. 调用startExecutorsOnWorkers方法

launchDriver(worker, driver)是干嘛的

  1. workerInfo中添加Driver信息
  2. workerInfo中添加worker信息
  3. 调用worker RpcEndPointRef向worker发送LanuchDriver消息
  4. dirver状态更新为Running状态

那么以上Driver注册完成~

注册之后进入Worker.scala节点:

recieve处理worker节点方法入口,那么Driver注册进来的Worker进入的launchDriver分支源码如下:

  1. 创建一个DriverRunner,包含conf配置,driverId,SparkHome等信息。
  2. 将创建的DriverRunner对象添加到drivers HashMap中保存
  3. 调用DriverRunner的start方法启动
  4. 记录Driver使用Cores和内存使用信息

OK进入DriverRunner的start方法看看启动做了什么

  1. 启动一个线运行和管理Driver
  2. 创建一个线程“new Thread”,实现run 方法
  3. 准备driver 依赖jar包并且执行driver,该方法返回一个退出码“prepareAndRunDriver”
  4. 根据exitCode,设置finalState,状态分为完成,被杀死,失败“finalState”
  5. 向worker发送DriverStateChanged信息通知Driver发生状态改变“worker.send(DriverStateChanged(driverId, finalState.get, finalException))”

OK接着中间“prepareAndRunDriver”继续Debug进去查看

  1. 创建Worker工作路径“createWorkingDirectory”
  2. 从DriverDir中下载jar包“downloadUserJar(driverDir)”
  3. 创建processBulder“buildProcessBuilder”
  4. runDriver方法启动

我们继续跟进runDriver方法

  1. 初始化initialize方法,创建stdout和stderr文件,builder命令格式化
  2. 重定向将process的InputStream和ErrorStream重定向到指定的stdout和stderr中
  3. 重试机制,runCommandWithRetry

继续Debug下去“runCommandWithRetry”.

runCommandWithRetry是实际在系统中执行命令的操作,该操作返回process的exitCode退出码

  1. 默认exitCode为-1“varexitCode=-1”
  2. 在提交重试时等待时间“varwaitSeconds=1”
  3. 运行这么多秒会重置指数后退“valsuccessfulRunDuration=5”
  4. 如果进程没被傻屌则一直执行下去“while (keepTrying)”
  5. 检查是否尝试另一次运行“ keepTrying = supervise && exitCode !=0&&!killed”
  6. 返回exitCode“exitCode”

以上Driver注册然后到Worker节点执行都结束。但是很多细节是没有阅读的,例如源码中是如何设计代码架构的,这种层层调度的代码逻辑像极了一个树形结构的思维。一个优良的代码架构应该具备解耦合和易用性,那么阅读源码带来的不仅仅是源码架构,还有个人从代码角度去理解一个架构的设计。

OK后面在继续看下Driver执行结束干了啥,主要Worker.scala在方法“handleDriverStateChanged”

DriverRunner根据退出码进行匹配,通知worker driver状态发生改变

worker接受到该消息后,做出如下操作:

  1. 向Master发送消息driverStateChanged
  2. 在drivers集合中移除当前driver
  3. 将当前driver添加到已完成driver集合中
  4. 移除该driver分配的内存、cpu cores资源

Master在接受到消息后,根据匹配做出操作,如果为ERROR FINISHED KILLED FAILED状态,则调用removeDriver移除Driver

removeDriver方法中,做出一系列移除操作

  1. 在drivers集合中移除driver
  2. 将driver添加到completedDrivers集合中
  3. 持久化引擎移除driver
  4. 更新driver状态为最终状态时的状态
  5. 移除worker上的driver
  6. 调用schedule方法,重新调度资源

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31509949/viewspace-3145744/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
管理员

注册时间:2018-03-30

  • 博文量
    2383
  • 访问量
    984152