spark 是怎么跑在 yarn 上面的

两种启动方式

spark on yarn 根据你启动的时候, 根据你 master 和 mode 参数, 来使用 客户端模式 还是 集群模式

  • 客户端模式 yarn-client
  • 集群模式 yarn-cluster

客户端模式, 是指 spark 的 driver jvm 进程 是跑着你运行 spark-submit 的那台机器上的, 一般是用于调试和交互,可以即时的看到输出, 启动后, 是不能停掉的当前的 shell 进程的, 不然 driver进程 也就被杀掉了,

集群模式 一般是用于生产环境, 如果用户提交作业后,就可以关掉 client 了, 因为这个时候 driver 是跑在集群中的某一台 node 上的, 所在的客户端已经完成使命了。

yarn 的规矩

在 yarn 上面提交运行一个应用的过程为

  • Client 向 YARN 提交应用程序,其中包括 ApplicationMaster 程序及启动 ApplicationMaster 的命令
  • ResourceManager 为该 ApplicationMaster 分配第一个 Container,并与对应的 NodeManager 通信,要求它在这个 Container 中启动应用程序的 ApplicationMaster
  • ApplicationMaster 首先向 ResourceManager 注册
  • ApplicationMaster 为 Application 的任务申请并领取资源
    领取到资源后,要求对应的 NodeManager 在 Container 中启动任务
    NodeManager 收到 ApplicationMaster 的请求后,为任务设置好运行环境(包括环境变量、JAR 包、二进制程序等),将任务启动脚本写到一个脚本中,并通过运行该脚本启动任务
  • 各个任务通过 RPC 协议向 ApplicationMaster 汇报自己的状态和进度,以让 ApplicationMaster 随时掌握各个任务的运行状态,从而可以在失败时重启任务
  • 应用程序完成后,ApplicationMaster 向 ResourceManager 注销并关闭自己

无论 spark 以哪种方式启动, 只要是跑在 yarn 上面, 就得遵守yarn 的规矩, 所以 spark 能做的就是按照 yarn 的方式来适配,

spark-submit

这个工具是 spark 用来提交应用的一个工具, 它支持你把应用提交 在 local 上, mesos, 或者 yarn 上, 由于这个工具, 处理的corner case比较多, 所以代码也比较乱, 我简单梳理一下, spark-submit 使用反射机制启动用户作业的 main 函数, 如果是 yarn-cluser 模式就直接 启动 org.apache.spark.deploy.Client 类用户作业的类当参数带进去, 如果是 yarn-client 模式 ,就直接在本地启动用户作业的 main 函数,

org.apache.spark.deploy.Client 类里面针对两种模式会使用不同的运行不同的类,当做app master, 里面也调用不同的方法

val amClass =
if (isClusterMode) {
Utils.classForName("org.apache.spark.deploy.yarn.ApplicationMaster").getName
} else {
Utils.classForName("org.apache.spark.deploy.yarn.ExecutorLauncher").getName
}
```

if (isClusterMode) {
runDriver(securityMgr)
} else {
runExecutorLauncher(securityMgr)
}

```

集群模式 启动流程

上文我们提到了, 如果是 yarn-cluser 模式就启动 org.apache.spark.deploy.Client 类,

  • 调用 client 的 submitApplication 方法, 在 ResourceManager 中创建一个 Application, 获取应用的ID,
  • 判断资源管理集群是否能提供足够的资源, 判断我们应用申请的内存没有超过集群拥有的最大内存,
  • 为 ApplicationMaster 封装一个容器, 这个容器包含启动的环境变量, java options 和启动的命令行, 也即是一个 ContainerLaunchContext,
  • 创建一个 提交 yarn 应用的 ApplicationSubmissionContext, 包含ApplicationName ,yarn 队列和优先级, 应用的类型为 spark,
  • 通过 yarnClient.submitApplication 提交应用到 ResourceManager, 这之后就跟 client 没有关系了, 终端关掉都没事。
  • 因为是 cluster 模式, 所以 yarn 会申请一个 container 然后运行 ApplicationMaster 类, 里面会运行 runDriver 方法, 里面会启动 driver ,初始化 sparkcontext, 向 ResourceManager 注册, 设置 track url, 用来让用户查看运行情况,
  • 然后向 RM 申请 资源, 包装运行 CoarseGrainedExecutorBackend 的 container 并提交启动,把 driver的 url当参数带进去 ,方便 executor 启动后向 driver 注册自己

客户端模式 启动流程

  • yarn-client 模式 ,就直接在本地启动用户作业的 main 函数, 用户程序里面会初始化 sparkContext, spark context 启动的时候会启动一些必要的组件, 会启动 taskScheduler和dagScheduler, 在创建taskScheduler的时候会根据我们传进来的master来选择Scheduler和SchedulerBackend。 由于我们选择的是yarn-client模式,程序会反射创建 YarnScheduler 和 YarnClientSchedulerBackend 类, 继承关系如下图

  • taskScheduler start 的时候, 也就是调用 TaskSchedulerImpl 的 start, 会 调用 YarnClientSchedulerBackend 的start 方法
  • SchedulerBackend启动的过程中将会初始化一些参数,封装在ClientArguments中,并将封装好的ClientArguments传进Client类中, 也会调用 Client 的 submitApplication 方法, 不同的是里面封装的容器启动的主类是 ExecutorLauncher 。 提交 application 到RM,
  • 以上步骤都是运行在 提交所在机器上, driver 也是运行在了客户端, 客户端不能关闭, driver 的日志也是打印在客户端控制台,
    • 经过 yarn 调度 am 运行在一个节点上, 里面运行 ExecutorLauncher -> runExecutorLauncher, 这个时候就不用运行 driver了, 只需要只需要把客户端 driver 的track url 注册到 RM, 申请contianer 启动 executor的时候,给的也是 运行在客户的 driver 的地址, 在这里只是应付 yarn 的规矩, 在 app master 上面跑一个 ExecutorLauncher, 连接 运行在客户端的 driver 和跑在申请资源里面的 executor, executor 启动后向运行在客户端的 driver 注册自己

原创精品,首发个人公众号 spark技术分享 , 同步个人网站 coolplayer.net ,未经本人同意,禁止一切转载

欢迎大家关注:sunbiaobiao's微信公众号