Spark - 第15章 Spark如何在集群上运行


        介绍Spark在集群上执行代码的全过程,我们的介绍不依赖于实现,既与你使用的集群管理器无关,也与你运行的代码无关,所有的Spark代码都以相同的方式运行。

  • Spark应用程序的体系结构和组件
  • Spark应用程序(内部和外部)的生命周期
  • 重要的底层执行属性,例如流水线处理
  • 运行一个Spark应用程序都需要什么?

Spark应用程序的体系结构

  • Spark驱动器
             Spark驱动器是控制你应用程序的进程。它负责控制整个Spark应用程序的执行并且维护着Spark集群的状态,即执行器的任务和状态,它必须与集群管理器交互才能获得物理资源并启动执行器。简而言之,它只是一个物理机器上的一个进程,负责维护集群上运行的应用程序的状态。
  • Spark执行器
            Spark执行器也是一个进程,它负责执行由Spark驱动器分配的任务。执行器的核心功能是:完成驱动器分配的任务,运行它们,并报告其状态(成功或失败)和执行结果。每个Spark应用程序都有自己的执行器进程。
  • 集群管理器
            Spark驱动器和执行器并不是孤立存在的,集群管理器会将他们联系起来,集群管理器负责维护一组运行Spark应用程序的机器。集群管理器也拥有自己的“driver驱动器”(即master)和worker的抽象,核心区别在于集群管理器管理的是物理机器,而不是进程。

        当实际运行Spark应用程序时,我们会从集群管理器那里请求资源来运行它。根据应用程序的配置,我们可能获得一个运行Spark驱动器的机器资源,或者可能获得的是我们Spark执行器的计算资源。在Spark应用程序执行过程中,集群管理器将负责管理执行应用程序的底层机器。
        Spark目前支持三个集群管理器:一个简单的内置独立集群管理器。Apache Mesos和Hadoop YARN。但是,支持的集群管理器可能会越来越多,因此请务必查看集群管理器的文档以了解最新的更新,这会让你有机会使用你最喜欢的集群管理器。

执行模式

        当在运行应用程序之前,通过选择模式你将能够确定计算资源的物理位置。你有三种模式可供选择;

  • 集群模式
  • 客户端模式
  • 本地模式

集群模式

        集群模式可能是运行Spark应用程序的最常见方式。在集群模式下,用户将预编译的JAR包、Python脚本或R语言脚本提交给集群管理器。除执行器进程外,集群管理器还会在集群内的某个工作节点上启动驱动器进程,这意味着集群管理器负责维护所有与Spark应用程序相关的进程。集群管理器将Spark驱动器放置在一个工作节点上,并将Spark执行器放置在其他工作节点上。

客户端模式

        客户端模式与集群模式几乎相同,只是Spark驱动器保留在提交应用程序的客户端机器上。这意味着客户端机器负责维护Spark驱动器进程,并且集群管理器维护执行器进程。我们使用一台集群外的机器运行Spark应用程序,这些机器通常被称为网关机器(gateway machines)或边缘节点(edge nodes)。驱动器在集群外部的计算机上运行,但工作节点位于集群中的计算机上。

本地模式

        本地模式与前两种模式有很大不同:它在一台机器上运行整个Spark应用程序。它通过单机上的线程实现并行性。在本地模式上运行Spark是学习Spark的常用方法,也是测试应用程序或进行迭代本地开发的常用方法,但是我们不建议使用本地模式运行生产级别的应用程序。

Spark应用程序的生命周期(Spark外部)

客户请求

        第一步是提交一个应用程序,这是一个编译好的JAR包或者库。此时,你正在本地计算机上执行代码,并且将向集群管理器驱动器节点发出请求。在这里,我们仅会为Spark驱动器进程显式地请求资源,假设集群管理器接受请求并将驱动器放置到集群中的一个物理节点上,之后提交原始作业的客户端进程退出,应用程序开始在集群上运行。从支持Spark运行的基础架构方面来介绍。

启动

        现在驱动器进程已经被到集群上了,他开始执行用户代码,此代码必须包含一个初始化Spark集群(如驱动器和若干执行器)的SparkSession,SparkSession随后将与集群管理器驱动节点通信,要求它在集群上启动Spark执行器,集群管理器随后在集群工作节点上启动执行器,执行器的数量及其相关配置由用户通过最开始spark-submit调用中的命令行参数设置。
        如果一切顺利的话,集群管理器就会启动Spark执行器,并将程序执行位置等相关信息发送给Spark驱动器。在所有程序都正确关联之后,正如你期待的那样,我们就成功构建了一个“Spark集群”。

执行

        现在我们有了一个“Spark集群”,Spark就可以开始顺利地执行代码了。集群的驱动节点和工作节点相互通信、执行代码和移动数据,驱动节点将任务安排到每个工作节点上,每个工作节点回应给驱动节点这些任务的执行状态,也可能回复启动成功或启动失败等。

完成

        Spark应用程序完成后,Spark驱动器会以成功或失败的状态退出,然后集群管理器会为该驱动器关闭集群中的执行器。此时,你可以向集群管理器询问来获知Spark应用程序是成功退出还是失败退出。

Spark应用程序的生命周期(Spark内部)

        每个应用程序由一个或多个Spark作业组成,应用程序内的一系列Spark作业是串行执行的(除非你使用多线程并行启动多个作业)

SparkSession

        任何Spark应用程序的第一步都是创建一个SparkSeesion。在交互模式中,通常已经为你预先创建了,但在应用程序中你必须自己创建。
        一些老旧的代码可能会使用new SparkContext这种方法创建,但是应该尽量避免使用这种方法,而是推荐使用SparkSession的构建器方法,该方法可以更稳定地实例化Spark和SQL Context,并确保没有多线程切换导致的上下文冲突,因为可能有多个库视图在相同的Spark应用程序中创建会话。
        在创建SparkSeesion后,你就应该可以运行你的Spark代码了。通过SparkSeesion,你可以相应地访问所有低级的和老旧的Spark功能和配置。请注意,SparkSession类是在Spark 2.X版本后才支持的,你可能会发现较旧的代码会直接为结构化API创建SparkContext和SQLContext。

SparkContext

        SparkSession中的SparkContext对象代表与Spark集群的连接,可以通过它与一些Spark的低级API(如RDD)进行通信,在较早的示例和文档中,它通常以变量sc存储。通过SparkContext,你可以创建RDD、累加器和广播变量,并且可以在集群上运行代码。

逻辑指令

        了解如何使用DataFrame之类的声明式指令并将其转换为物理执行计划,是理解Spark如何在集群上运行的重要一步。

逻辑指令到物理执行

        使用一个简单的DataFrame执行三步操作:①重新分区;②执行逐个值得操作;③执行聚合操作并收集最终结果。它们由阶段和任务组成。

Saprk作业

        一般来说,一个动作应该触发一个Spark作业,调用动作总是会返回结果,每个作业被分解成一系列阶段,其数量取决于需要多少次shuffle操作。

  • 第一阶段,有8个任务
  • 第二阶段,有8个任务
  • 第三阶段,有6个任务
  • 第四阶段,有5个任务
  • 第五阶段,有200个任务
  • 第六阶段,有1个任务

阶段

        Spark中的阶段(stage)代表可以一起执行的任务组,用以在多台机器上执行相同的操作。一般来说,Spark会尝试将尽可能多的工作(即作业内部尽可能多的转换操作)加入同一个阶段,引擎在shuffle操作之后将启动新的阶段。一次shuffle操作意味着一次对数据的物理重分区,例如对DataFrame进行排序,或对从文件中加载的数据按key进行分组(这要求将具有相同key的记录发送到同一节点),这种重分区需要跨执行器的协调来移动数据。Spark在每次shuffle之后开始一个新阶段,并按照顺序执行各阶段以计算最终结果。
        对于分区数量的设置,一个经验法则是分区数量应该大于集群上执行器的数量,这可能取决于工作负载相关的多个因素。如果你在本地计算机上运行代码,则应该将分区数量设置得较低,因为你的本地计算机不太可能并行执行这些任务。对于可能有更多执行核心可以使用的集群来说,就应该设置的更多。无论分区数量设置成多少,整个阶段都是并行执行的,系统可以分别对这些分区并行执行聚合操作,将这些局部结果发送到一个汇总节点,然后再在这些局部结果上执行最终的聚合操作获得最终结果,再把该结果返回给驱动器。

任务

        Spark中的阶段由若干任务(task)组成,每个任务都对应于一组数据和一组将在单个执行器上运行的转换操作。如果数据集中只有一个大分区,我们将只有1个任务;如果有1000个小分区,我们将有1000个可以并行执行的任务。任务是应用于每个数据单元(分区)的计算单位,将数据划分为更多分区意味着可以并行执行更多分区。虽然可以通过增加分区数量来增加并行性,但这不是万能的,只是可以通过这一点来做一些简单的优化。

执行细节

        Spark中任务和阶段的一些重要执行细节也值得关注。第一个执行细节是Saprk会自动的以流水线的方式一并完成连续的阶段和任务,例如map操作接着另一个map操作。另外一个执行细节是,对于所有的shuffle操作,Spark会将数据写入持久化存储(例如磁盘),并可以在多个作业中重复使用它。

流水线执行

        使Spark成为一个著名的“内存计算工具”的很重要一点就是,与之前的工具不同(例如MapReduce),Spark在将数据写入内存或磁盘之前执行尽可能多的操作。Spark执行的关键优化之一是流水线,它在RDD级别或以下级别上执行。通过流水线技术,一系列有数据依赖关系的操作,如果不需要任何跨节点的数据移动,就可以将这一系列操作合并为一个单独的任务阶段。
        通过流水线优化的计算要比每步完成后将中间结果写入内存或磁盘要快得多。对于执行select,filter和select序列操作的DataFrame或SQL计算,也会同样地执行流水线操作。
        实际上,当你编写应用程序时,流水线优化对你来说是透明的,Spark引擎会自动的完成这项工作。但是如果你通过Spark UI或其日志文件检查你的应用程序,你将看到Spark系统将对多个RDD或DataFrame操作通过流水线优化合并为一个执行阶段。

shuffle数据持久化

        当Spark需要进行某些需要跨节点移动数据的操作时,例如按键约减操作(即reduce-by-key操作,其中每个键对应的输入数据需要先从多个节点获取并合并在一起),处理引擎不再执行流水线操作,而是执行跨网络的shuffle操作。在Spark执行shuffle操作时,总是首先让前一段的“源”任务(发送数据的哪些任务)将要发送的数据写入到本地磁盘的shuffle文件上,然后下一阶段执行按键分组和约减的任务将从每个shuffle文件中获取相应的记录并执行某些计算任务(例如,获取并处理特定键范围的数据)。将shuffle文件持久化到磁盘上允许Spark稍晚些执行reduce阶段的某些任务(例如,如果没有足够多的执行器同时执行分配任务,由于数据已经持久化到磁盘上,便可以稍晚些执行某些任务),另外在错误发生时,也允许计算引擎仅重新执行reduce任务而不必重新启动所有的输入任务。
        shuffle操作数据持久化有一个附带作用,在已经执行了shuffle操作的数据上运行新的作业不会重新运行shuffle操作的“源”一侧的任务(即生产shuffle数据的任务)。由于shuffle文件早已写入磁盘,因此Spark知道可以直接使用这些已经生成好的shuffle文件来运行作业的后一阶段,而不需要重做之前的阶段。在Spark UI和日志中,你将看到标记为“skipped”的预shuffle阶段。这种自动优化可以节省在同一数据上运行多个作业所花费的时间,当然,如果想要获得更好的性能,你也可以使用DataFrame或RDD的cache方法自己设置缓存,这样你可以精确控制哪些数据需要保存,并且控制保存到哪里。通过对聚合后的数据进行一些Spark动作操作,并在Spark UI监视这些执行过程,你将很快熟悉这种执行机制。

小结

        讨论了Spark应用程序如何在集群上执行的,包括集群如何实际地执行我们的代码,以及在此过程中Spark应用程序中到底发生了什么。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值