编写 Spark 程序的一大好处是: 如果想要提升计算力和效率,只需要增加机器就可以了。对用户来说,只需要先在本地或者小的集群上先测试,然后不需修改任何代码就可以在生产环境执行。

Spark 运行时架构

在分布式模式下,Spark 使用 master/slave 架构:中央调节器被称作 driverdriver 和分布式的 worker 通信并协调,这些 worker 被称作 executor。每个 driver 都在自己的 Java 进程中运行,而其他的 executor 分别运行在自己单独的 Java 进程中。一个 driver 和它所有的 executor 组成一个 Spark 应用

一个 Spark 应用通过一个外部的服务 — 集群管理器 — 在一群机器上启动。

Driver

Driver是 Spark 程序中的 main() 方法运行所在的进程:driver 负责创建 SparkContext, 创建 RDD,并且负责执行 transformations 和 actions。当 driver 进程终止运行时,整个 spark 应用也结束了。

driver 在整个生命周期有两个职责:

  • 把 Saprk 程序分解为 任务(task)
    任务 是真正的物理执行过程。
    所有的 Spark 程序都遵循相同的结构:

    • 从 input 创建 RDD;
    • 通过 transformations, 从这些 RDD 派生出新的 RDD;
    • 执行 action 操作收集或者保存数据;
      一个 Spark 程序会隐式地创建一个所有操作的 有向无环图(DAG)。driver 会将这个逻辑图转换成物理执行计划。
      Spark 会做一些优化操作,比如将 map pipeline 到一起并合并,然后把执行图转换为一些 stage,在每个 stage 中都包含一些 tasks。任务被打包到一起并且被发送到集群中执行。
  • 在 executor 上调度 task 的执行
    当 executors 被启动时,它们会向 Driver 注册,因此在整个生命周期中 driver 都掌握着 executor 的全貌。每个 executor 代表着一个有能运行 task 并保存 RDD 数据的进程。
    driver 会根据掌握的数据分布情况将不同的任务分发到不同的 executor 上
    当 task 执行时,可能会有缓存数据存在于 executor 中,driver 也会掌握这些缓存数据以备后续的使用。

Executors

Executors 是负责运行具体 task 的 worker 进程。Executor 在 Spark 应用启动之初也随之启动。Executor 也有两个角色:

  • 运行被分配的任务,并且向 driver 返回结果;
  • 提供 RDD 的内存缓存之处。由于 RDD 被直接缓存在 executor 中,因此 task 可以伴随缓存数据执行。

集群管理器

Spark 依赖一个集群管理器来启动所有的 executor,在一些情况下,也会启动 driver。

总结

在集群中运行 Spark 应用的步骤:

  1. 用户使用 spark-submit 提交应用;
  2. spark-submit 启动 driver 程序并调用 main() 方法;
  3. drivercluster manager 申请资源来启动 executors;
  4. cluster manager 启动 executor;
  5. driver 在整个应用执行期间一直运行,基于 RDD action 和 transformation,driver 将 task 发送给 executors.
  6. taskexecutor 上运行并保存结果;
  7. 如果 driver 上的 main() 方法退出了或者调用了 SparkContext.stop() 方法,那么 driver 会终止 executors 并且通知 cluster manager 释放资源。

使用 spark-submit 部署应用

使用方法:

bin/spark-submit [options] <app jar | python file> [app options]

[options] 是一组 spark-submit 使用的选项,这些选项大致上可以被分为两类:

  • 调度信息。比如这个 job 需要的资源
  • 运行时依赖。比如需要部署到所有 executor 上的依赖库。

重点说一下下面的选项:

  • --deploy-mode
    当使用 client 的部署模式时,driver 进程就在调用 spark-submit 的机器上;
    当使用 cluster 部署模式时,driver 进程在 YARN 集群上的摸一个 worker node 上;

  • --jars
    一些会被上传并放在应用的 classpath 的 JAR 文件,如果应用依赖少量的 JAR 包,可以用这样的方式。

  • --files
    一些会被放置在应用运行的文件夹的文件,如果你有想要分不到所有节点上的数据文件,可以用这样的方式。

一些小坑

当发布 uber-jar 到集群上时,有可能会遇到你的应用所依赖的某些包和 Spark 依赖的包有冲突的情况。解决办法是使用 shading 技术。(比如使用 maven-shade-plugin)

资源竞争

Spark 程序依赖 Cluster manager 提供的类似 queue 的概念来解决不同 Spark 应用间的资源竞争的问题。