Apache Spark入门级摘要

Apache Spark是一个围绕速度、易用性和复杂分析构建的大数据处理框架,Spark有如下优势:

  • Spark提供了一个全面、统一的框架用于管理各种有着不同性质(文本数据、图表数据等)的数据集和数据源(批量数据或实时的流数据)的大数据处理的需求。
  • 官方资料介绍Spark可以将Hadoop集群中的应用在内存中的运行速度提升100倍,甚至能够将应用在磁盘上的运行速度提升10倍。

1. 架构及生态

其架构示意图如下:

这里写图片描述

  • Spark Core:包含Spark的基本功能;尤其是定义RDD的API、操作以及这两者上的动作。其他Spark的库都是构建在RDD和Spark Core之上的。
  • Spark SQL:用于处理结构化数据的模块,每个数据库表被当做一个RDD,Spark SQL查询被转换为Spark操作。
  • Spark Streaming:对实时数据流进行处理和控制。Spark Streaming允许程序能够像普通RDD一样处理实时数据。
  • MLlib:一个常用机器学习算法库,算法被实现为对RDD的Spark操作。这个库包含可扩展的学习算法,比如分类、回归等需要对大量数据集进行迭代的操作。
  • GraphX:控制图、并行图操作和计算的一组算法和工具的集合。GraphX扩展了RDD API,包含控制图、创建子图、访问路径上所有顶点的操作

Spark架构的组成图如下:

这里写图片描述

  • Cluster Manager:standalone模式中即为Master主节点,控制整个集群,监控Worker。在Yarn模式中为资源管理器。
  • Worker节点:从节点,负责控制计算节点,启动Executor或者Driver
  • Driver: 运行Application 的main()函数。
  • Executor:执行器,是为某个Application运行在Worker node上的一个进程。

2. Spark与Hadoop

Hadoop的局限和不足
但是,MapRecue存在以下局限,使用起来比较困难。

  • 抽象层次低,需要手工编写代码来完成,使用上难以上手。
  • 只提供两个操作,Map和Reduce,表达力欠缺。
  • 一个Job只有Map和Reduce两个阶段(Phase),复杂的计算需要大量的Job完成,Job之间的依赖关系是由开发者自己管理的。
  • 处理逻辑隐藏在代码细节中,没有整体逻辑。
  • 中间结果也放在HDFS文件系统中。
  • ReduceTask需要等待所有MapTask都完成后才可以开始。
  • 时延高,只适用Batch数据处理,对于交互式数据处理,实时数据处理的支持不够。
  • 对于迭代式数据处理性能比较差

(1)Hadoop有两个核心模块,分布式存储模块HDFS和分布式计算模块MapReduce,Spark本身并没有提供分布式文件系统,因此Spark的磁盘存储大多依赖于Hadoop的分布式文件系统HDFS。

(2)Hadoop的MapReduce与Spark都可以进行数据计算。Spark与MapReduce最大的不同在于计算模型:

  • MapReduce,分为两个阶段,map和reduce,两个阶段完了,就结束了,所以我们在一个job里能做的处理很有限;
  • Spark,迭代计算模型,可以分为n个阶段,因为它是内存迭代式的。我们在处理完一个阶段以后,可以继续往下处理很多个阶段,而不只是两个阶段。

所以,Spark相较于MapReduce来说,计算模型可以提供更强大的功能。


3 Yarn框架介绍

Yarn是一个资源管理、任务调度的框架,主要包含三大模块:ResourceManager(RM)、NodeManager(NM)、ApplicationMaster(AM)。

  • ResourceManager负责所有资源的监控、分配和管理;
  • ApplicationMaster负责每一个具体应用程序的调度和协调;
  • NodeManager负责每一个节点的维护。

对于所有的applications,RM拥有绝对的控制权和对资源的分配权。而每个AM则会和RM协商资源,同时和NodeManager通信来执行和监控task。几个模块之间的关系如图所示:

这里写图片描述

Yarn总体上仍然是master/slave结构,在整个资源管理框架中,ResourceManager为master,NodeManager是slave。ResourceManager负责对各个NodeManger上资源进行统一管理和调度。当用户提交一个应用程序时,需要提供一个用以跟踪和管理这个程序的ApplicationMaster,它负责向ResourceManager申请资源,并要求NodeManger启动可以占用一定资源的任务。由于不同的ApplicationMaster被分布到不同的节点上,因此它们之间不会相互影响。


3.1 ResourceManager

ResourceManager是Master上一个独立运行的进程,负责集群统一的资源管理、调度、分配等等;NodeManager是Slave上一个独立运行的进程,负责上报节点的状态;App Master和Container是运行在Slave上的组件,Container是Yarn中分配资源的一个单位,包涵内存、CPU等等资源,Yarn以Container为单位分配资源。RM是一个全局的资源管理器,集群只有一个,负责整个系统的资源管理和分配,包括处理客户端请求、启动/监控APP master、监控NodeManager、资源的分配与调度。它主要由两个组件构成:调度器(Scheduler)和应用程序管理器(Applications Manager,ASM)。

(1) 调度器Scheduler

调度器根据容量、队列等限制条件(如每个队列分配一定的资源,最多执行一定数量的作业等),将系统中的资源分配给各个正在运行的应用程序。需要注意的是,该调度器是一个“纯调度器”,它不再从事任何与具体应用程序相关的工作,比如不负责监控或者跟踪应用的执行状态等,也不负责重新启动因应用执行失败或者硬件故障而产生的失败任务,这些均交由应用程序相关的ApplicationMaster完成。调度器仅根据各个应用程序的资源需求进行资源分配,而资源分配单位用一个抽象概念“资源容器”(Resource Container,简称Container)表示,Container是一个动态资源分配单位,它将内存、CPU、磁盘、网络等资源封装在一起,从而限定每个任务使用的资源量。此外,该调度器是一个可插拔的组件,用户可根据自己的需要设计新的调度器,Yarn提供了多种直接可用的调度器,比如Fair Scheduler和Capacity Scheduler等。

(2) 应用程序管理器

应用程序管理器负责管理整个系统中所有应用程序,包括应用程序提交、与调度器协商资源以启动ApplicationMaster、监控ApplicationMaster运行状态并在失败时重新启动它等。


3.2 NodeManager

  • NodeManager是每个节点上的资源和任务管理器,它是管理这台机器的代理,负责该节点程序的运行,以及该节点资源的管理和监控。Yarn集群每个节点都运行一个NodeManager。
  • NodeManager定时向ResourceManager汇报本节点资源(CPU、内存)的使用情况和Container的运行状态。当ResourceManager宕机时NodeManager自动连接RM备用节点。
  • NodeManager接收并处理来自ApplicationMaster的Container启动、停止等各种请求。

3.3 ApplicationMaster

  • 用户提交的每个应用程序均包含一个ApplicationMaster,它可以运行在ResourceManager以外的机器上。
  • 负责与RM调度器协商以获取资源(用Container表示)。
  • 将得到的任务进一步分配给内部的任务(资源的二次分配)。
  • 与NM通信以启动/停止任务。
  • 监控所有任务运行状态,并在任务运行失败时重新为任务申请资源以重启任务。
  • 当前Yarn自带了两个ApplicationMaster实现,一个是用于演示AM编写方法的实例程序DistributedShell,它可以申请一定数目的Container以并行运行一个Shell命令或者Shell脚本;另一个是运行MapReduce应用程序的AM—MRAppMaster。
  • 注:RM只负责监控AM,并在AM运行失败时候启动它。RM不负责AM内部任务的容错,任务的容错由AM完成。

3.4 Yarn运行流程

  1. Client向RM提交应用程序,其中包括启动该应用的ApplicationMaster的必须信息,例如ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等。
  2. ResourceManager启动一个Container用于运行ApplicationMaster
  3. 启动中的ApplicationMasterResourceManager注册自己,启动成功后与RM保持心跳。
  4. ApplicationMasterResourceManager发送请求,申请相应数目的Container
  5. ResourceManager返回ApplicationMaster的申请的Containers信息。申请成功的Container,由ApplicationMaster进行初始化。Container的启动信息初始化后,AM与对应的NodeManager通信,要求NM启动Container。AM与NM保持心跳,从而对NM上运行的任务进行监控和管理。
  6. Container运行期间,ApplicationMasterContainer进行监控Container通过RPC协议向对应的AM汇报自己的进度和状态等信息。
  7. 应用运行期间,Client直接与AM通信获取应用的状态、进度更新等信息。
  8. 应用运行结束后,ApplicationMasterResourceManager注销自己,并允许属于它的Container被收回

4. 运行流程及特点

4.1 基本概念

(1)Application: Appliction都是指用户编写的Spark应用程序,其中包括一个Driver功能的代码和分布在集群中多个节点上运行的Executor代码。

(2)Driver: Spark中的Driver即运行上述Application的main函数并创建SparkContext,创建SparkContext的目的是为了准备Spark应用程序的运行环境,在Spark中有SparkContext负责与ClusterManager通信,进行资源申请、任务的分配和监控等,当Executor部分运行完毕后,Driver同时负责将SparkContext关闭,通常用SparkContext代表Driver。

(3)Executor: 某个Application运行在Worker节点上的一个进程, 该进程负责运行某些Task, 并且负责将数据存到内存或磁盘上,每个Application都有各自独立的一批Executor。

(4)Cluter Manager:指的是在集群上管理资源的外部服务。目前有三种类型

  • Standalon : Spark原生的资源管理,由Master负责资源的分配
  • Apache Mesos: 与hadoop MR兼容性良好的一种资源调度框架
  • Hadoop Yarn: 主要是指Yarn中的ResourceManager

(5)Worker: 集群中任何可以运行Application代码的节点,在Standalone模式中指的是通过slave文件配置的Worker节点,在Spark on Yarn模式下就是NoteManager节点。

(6)Task: 被送到某个Executor上的工作单元,但Hadoop MR中的MapTask和ReduceTask概念一样,是运行Application的基本单位,多个Task组成一个Stage,而Task的调度和管理等是由TaskScheduler负责。

(7)Job: 包含多个Stage组成的并行计算,往往由Spark Action触发生成。

(8)Stage: 每个Job会被拆分成多组Task, 作为一个TaskSet, 其名称为Stage,Stage的划分和调度是由DAGScheduler来负责的,Stage有非最终的Stage(Shuffle Map Stage)和最终的Stage(Result Stage)两种,Stage的边界就是发生shuffle的地方

(9)DAGScheduler: 根据Job构建基于Stage的DAG(Directed Acyclic Graph有向无环图),并提交Stage给TASkScheduler。 其划分Stage的依据是RDD之间的依赖的关系找出开销最小的调度方法,如下图

(10)TaskSedulter: 将TaskSet提交给Worker运行,每个Executor运行什么Task就是在此处分配的. TaskScheduler维护所有TaskSet,当Executor向Driver发生心跳时,TaskScheduler会根据资源剩余情况分配相应的Task。另外TaskScheduler还维护着所有Task的运行标签,重试失败的Task。下图展示了TaskScheduler的作用
这里写图片描述

在不同运行模式中任务调度器具体为:

  • Spark on Standalone模式为TaskScheduler
  • Yarn-Client模式为YarnClientClusterScheduler
  • Yarn-Cluster模式为YarnClusterScheduler

将这些术语串起来的运行层次图如下:
这里写图片描述

Job=多个stage,Stage=多个同种task, Task分为ShuffleMapTask和ResultTask,Dependency分为ShuffleDependency和NarrowDependency。


4.2 Spark运行流程

这里写图片描述

  1. 构建Spark Application的运行环境,启动SparkContext。
  2. SparkContext向资源管理器(可以是Standalone,Mesos,Yarn)申请运行Executor资源。
  3. Executor向SparkContext申请Task。
  4. SparkContext将应用程序分发给Executor。
  5. SparkContext构建成DAG图,将DAG图分解成Stage、将Taskset发送给Task Scheduler,最后由Task Scheduler将Task发送给Executor运行。
  6. Task在Executor上运行,运行完释放所有资源。

4.3 Spark运行特点

  1. 每个Application获取专属的Executor进程,该进程在Application期间一直驻留,并以多线程方式运行Task。这种Application隔离机制是有优势的,无论是从调度角度看(每个Driver调度他自己的任务),还是从运行角度看(来自不同Application的Task运行在不同JVM中),当然这样意味着Spark Application不能跨应用程序共享数据,除非将数据写入外部存储系统。
  2. Spark与资源管理器无关,只要能够获取Executor进程,并能保持相互通信就可以了。
  3. 提交SparkContext的Client应该靠近Worker节点(运行Executor的节点),最好是在同一个Rack里,因为Spark Application运行过程中SparkContext和Executor之间有大量的信息交换。
  4. Task采用了数据本地性和推测执行的优化机制。

5. Spark运行模式

5.1 standalone独立集群

Standalone模式使用Spark自带的资源调度框架,采用Master/Slaves的典型架构,选用ZooKeeper来实现Master的HA,框架结构图如下:

这里写图片描述

该模式主要的节点有Client节点、Master节点和Worker节点。其中Driver既可以运行在Master节点上中,也可以运行在本地Client端。当用Spark-shell交互式工具提交Spark的Job时,Driver在Master节点上运行;当使用Spark-submit工具提交Job或者在Eclips、IDEA等开发平台上使用”new SparkConf.setManager(“Spark://master:7077”)”方式运行Spark任务时,Driver是运行在本地Client端上的

运行过程如下图:
这里写图片描述

  1. SparkContext连接到Master,向Master注册并申请资源(CPU Core 和Memory)
  2. Master根据SparkContext的资源申请要求和Worker心跳周期内报告的信息决定在哪个Worker上分配资源,然后在该Worker上获取资源,然后启动StandaloneExecutorBackend;
  3. StandaloneExecutorBackend向SparkContext注册;
  4. SparkContext将Applicaiton代码发送给StandaloneExecutorBackend;并且SparkContext解析Applicaiton代码,构建DAG图,并提交给DAG Scheduler分解成Stage(当碰到Action操作时,就会催生Job;每个Job中含有1个或多个Stage,Stage一般在获取外部数据和shuffle之前产生),然后以Stage(或者称为TaskSet)提交给Task Scheduler,Task Scheduler负责将Task分配到相应的Worker,最后提交给StandaloneExecutorBackend执行;
  5. StandaloneExecutorBackend会建立Executor线程池,开始执行Task,并向SparkContext报告,直至Task完成
  6. 所有Task完成后,SparkContext向Master注销,释放资源

5.2 Spark on YARN

当在YARN上运行Spark作业,每个Spark Executor作为一个YARN容器(container)运行。Spark可以使得多个Tasks在同一个容器(Container)里面运行。Spark on YARN有两种模式,yarn-cluster适用于生产环境;而yarn-client适用于交互和调试,也就是希望快速地看到application的输出。

在我们介绍yarn-cluster和yarn-client的深层次的区别之前,我们先明白一个概念:Application Master。在YARN中,每个Application实例都有一个Application Master进程,它是Application启动的第一个容器。它负责和ResourceManager打交道,并请求资源。获取资源之后告诉NodeManager为其启动Container。

5.2.1 yarn-cluster

yarn-cluster模式下,Driver运行在AM中,它负责向YARN申请资源,并监督作业的运行状况。当用户提交了作业之后,就可以关掉Client,作业会继续在YARN上运行。然而yarn-cluster模式不适合运行交互类型的作业。

这里写图片描述

5.2.2 yarn-client

而yarn-client模式下,Application Master仅仅向YARN请求executor,client会和请求的container通信来调度他们工作,也就是说Client不能离开。

这里写图片描述


6. Spark的核心优势RDD

弹性分布式数据集(RDD,Resilient Distributed Datasets)支持基于工作集的应用,同时具有数据流模型的特点:自动容错位置感知调度可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

6.1 主要特性

6.1.1 RDD抽象

RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合只能通过在其他RDD执行转换操作(如mapjoingroup by)而创建,然而这些限制使得实现容错的开销很低。与分布式共享内存系统需要付出高昂代价的检查点和回滚机制不同,RDD通过Lineage来重建丢失的分区:一个RDD中包含了如何从其他RDD衍生所必需的相关信息,从而不需要检查点操作就可以重构丢失的数据分区。尽管RDD不是一个通用的共享内存抽象,但却具备了良好的描述能力、可伸缩性和可靠性,能够广泛适用于数据并行类应用。

RDD是只读的、分区记录的集合。RDD只能基于在稳定物理存储中的数据集和其他已有的RDD上执行确定性操作来创建。这些确定性操作称之为转换,如map、filter、groupBy、join(转换不是程开发人员在RDD上执行的操作)。RDD不需要物化。RDD含有如何从其他RDD衍生(即计算)出本RDD的相关信息(即Lineage),据此可以从物理存储的数据计算出相应的RDD分区。

转换 map(f : T ) U) : RDD[T] ) RDD[U]filter(f : T ) Bool) : RDD[T] ) RDD[T]flatMap(f : T ) Seq[U]) : RDD[T] ) RDD[U]sample(fraction : Float) : RDD[T] ) RDD[T] (Deterministic sampling)groupByKey() : RDD[(K, V)] ) RDD[(K, Seq[V])]reduceByKey(f : (V; V) ) V) : RDD[(K, V)] ) RDD[(K, V)]union() : (RDD[T]; RDD[T]) ) RDD[T]join() : (RDD[(K, V)]; RDD[(K, W)]) ) RDD[(K, (V, W))]cogroup() : (RDD[(K, V)]; RDD[(K, W)]) ) RDD[(K, (Seq[V], Seq[W]))]crossProduct() : (RDD[T]; RDD[U]) ) RDD[(T, U)]mapValues(f : V ) W) : RDD[(K, V)] ) RDD[(K, W)] (Preserves partitioning)sort(c : Comparator[K]) : RDD[(K, V)] ) RDD[(K, V)]partitionBy(p : Partitioner[K]) : RDD[(K, V)] ) RDD[(K, V)]
动作 count() : RDD[T] ) Longcollect() : RDD[T] ) Seq[T]reduce(f : (T; T) ) T) : RDD[T] ) Tlookup(k : K) : RDD[(K, V)] ) Seq[V] (On hash/range partitioned RDDs)save(path : String) : Outputs RDD to a storage system, e.g., HDFS

6.1.2 容错性

一般来说,分布式数据集的容错性有两种方式:即数据检查点记录数据的更新。我们面向的是大规模数据分析,数据检查点操作成本很高:需要通过数据中心的网络连接在机器之间复制庞大的数据集,而网络带宽往往比内存带宽低得多,同时还需要消耗更多的存储资源(在内存中复制数据可以减少需要缓存的数据量,而存储到磁盘则会拖慢应用程序)。所以,RDD选择记录更新的方式。但是,如果更新太多,那么记录更新成本也不低。因此,RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列转换记录下来(即Lineage),以便恢复丢失的分区。

虽然只支持粗粒度转换限制了编程模型,但我们发现RDD仍然可以很好地适用于很多应用,特别是支持数据并行的批量分析应用,包括数据挖掘、机器学习、图算法等,因为这些程序通常都会在很多记录上执行相同的操作。RDD不太适合那些异步更新共享状态的应用,例如并行web爬行器。因此,我们的目标是为大多数分析型应用提供有效的编程模型,而其他类型的应用交给专门的系统。


6.1.3 延迟结算

定义RDD之后,程序员就可以在动作中使用RDD了。动作是向应用程序返回值,或向存储系统导出数据的那些操作,例如,count(返回RDD中的元素个数),collect(返回元素本身),save(将RDD输出到存储系统)。在Spark中,只有在动作第一次使用RDD时,才会计算RDD(即延迟计算)。这样在构建RDD的时候,运行时通过管道的方式传输多个转换。


6.1.4 缓存

用户可以请求将RDD缓存,这样运行时将已经计算好的RDD分区存储起来,以加速后期的重用。缓存的RDD一般存储在内存中,但如果内存不够,可以写到磁盘上。


6.1.5 分区

RDD还允许用户根据关键字(key)指定分区顺序,这是一个可选的功能。目前支持哈希分区和范围分区。例如,应用程序请求将两个RDD按照同样的哈希分区方式进行分区(将同一机器上具有相同关键字的记录放在一个分区),以加速它们之间的join操作。


6.1.6 窄依赖/宽依赖

RDD之间的依赖关系可以分为两类,即:(1)窄依赖(narrow dependencies):子RDD的每个分区依赖于常数个父分区(即与数据规模无关);(2)宽依赖(wide dependencies):子RDD的每个分区依赖于所有父RDD分区。例如,map产生窄依赖,而join则是宽依赖(除非父RDD被哈希分区)。

这里写图片描述

区分这两种依赖很有用。首先,窄依赖允许在一个集群节点上以流水线的方式(pipeline)计算所有父分区。例如,逐个元素地执行map、然后filter操作;而宽依赖则需要首先计算好所有父分区数据,然后在节点之间进行Shuffle,这与MapReduce类似。第二,窄依赖能够更有效地进行失效节点的恢复,即只需重新计算丢失RDD分区的父分区,而且不同节点之间可以并行计算;而对于一个宽依赖关系的Lineage图,单个节点失效可能导致这个RDD的所有祖先丢失部分分区,因而需要整体重新计算。


6.2 简单示例

本部分我们通过一个具体示例来阐述RDD。假定有一个大型网站出错,操作员想要检查Hadoop文件系统(HDFS)中的日志文件(TB级大小)来找出原因。通过使用Spark,操作员只需将日志中的错误信息装载到一组节点的内存中,然后执行交互式查询。首先,需要在Spark解释器中输入如下Scala命令:

lines = spark.textFile("hdfs://...")
errors = lines.filter(_.startsWith("ERROR"))
errors.cache()

第1行从HDFS文件定义了一个RDD(即一个文本行集合),第2行获得一个过滤后的RDD,第3行请求将errors缓存起来。注意在Scala语法中filter的参数是一个闭包。

这时集群还没有开始执行任何任务。但是,用户已经可以在这个RDD上执行对应的动作,例如统计错误消息的数目:

errors.count()

用户还可以在RDD上执行更多的转换操作,并使用转换结果,如:

// Count errors mentioning MySQL:
errors.filter(_.contains("MySQL")).count()
// Return the time fields of errors mentioning
// HDFS as an array (assuming time is field
// number 3 in a tab-separated format):
errors.filter(_.contains("HDFS"))
    .map(_.split('\t')(3))
    .collect()

使用errors的第一个action运行以后,Spark会把errors的分区缓存在内存中,极大地加快了后续计算速度。注意,最初的RDD lines不会被缓存。因为错误信息可能只占原数据集的很小一部分(小到足以放入内存)。
最后,为了说明模型的容错性,下图给出了第3个查询的Lineage图。在lines RDD上执行filter操作,得到errors,然后再filter、map后得到新的RDD,在这个RDD上执行collect操作。Spark调度器以流水线的方式执行后两个转换,向拥有errors分区缓存的节点发送一组任务。此外,如果某个errors分区丢失,Spark只在相应的lines分区上执行filter操作来重建该errors分区。

这里写图片描述


6.3 Spark任务调度器

调度器根据RDD的结构信息为每个动作确定有效的执行计划。调度器的接口是runJob函数,参数为RDD及其分区集,和一个RDD分区上的函数。该接口足以表示Spark中的所有动作(即count、collect、save等)。

总的来说,我们的调度器跟Dryad类似,但我们还考虑了哪些RDD分区是缓存在内存中的。调度器根据目标RDD的Lineage图创建一个由stage构成的无回路有向图(DAG)。每个stage内部尽可能多地包含一组具有窄依赖关系的转换,并将它们流水线并行化(pipeline)。stage的边界有两种情况:一是宽依赖上的Shuffle操作;二是已缓存分区,它可以缩短父RDD的计算过程。例如图6。父RDD完成计算后,可以在stage内启动一组任务计算丢失的分区。

这里写图片描述

Spark怎样划分任务阶段(stage)的例子。实线方框表示RDD,实心矩形表示分区(黑色表示该分区被缓存)。要在RDD G上执行一个动作,调度器根据宽依赖创建一组stage,并在每个stage内部将具有窄依赖的转换流水线化(pipeline)。 本例不用再执行stage 1,因为B已经存在于缓存中了,所以只需要运行2和3。

调度器根据数据存放的位置分配任务,以最小化通信开销。如果某个任务需要处理一个已缓存分区,则直接将任务分配给拥有这个分区的节点。否则,如果需要处理的分区位于多个可能的位置(例如,由HDFS的数据存放位置决定),则将任务分配给这一组节点。

对于宽依赖(例如需要Shuffle的依赖),目前的实现方式是,在拥有父分区的节点上将中间结果物化,简化容错处理,这跟MapReduce中物化map输出很像。

如果某个任务失效,只要stage中的父RDD分区可用,则只需在另一个节点上重新运行这个任务即可。如果某些stage不可用(例如,Shuffle时某个map输出丢失),则需要重新提交这个stage中的所有任务来计算丢失的分区。


7. 共享变量

多个task想要共享某个变量,Spark为此提供了两个共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。

7.1 Broadcast Variable

Spark提供的Broadcast Variable,是只读的。并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份。因此其最大的作用,就是减少变量到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外,spark内部也使用了高效的广播算法来减少网络消耗。

可以通过调用SparkContext的broadcast()方法,来针对某个变量创建广播变量。然后在算子的函数内,使用到广播变量时,每个节点只会拷贝一份副本,每个节点可以使用广播变量的value()方法获取值。

val factor = 3  
val factorBroadc ast = sc.broadcast(factor)  
val arr = Array(1,2,3,4,5)  
val rdd = sc.parallelize(arr)  
val multipleRdd = rdd.map(num => num*factorBroadcast.Value())  
multipleRdd.foreach(num => println(num)) 

7.2 Accumulator

Spark提供Accumulator,主要用于多个节点对一个变量进行共享性的操作。Accumulator只提供了累加的功能。但是却给我们提供了多个task对一个变量并行操作的功能。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。

al sumAccumulator = sc.accumulator(0)  
val arr = Array(1,2,3,4,5)  
val rdd = sc.parallelize(arr)  
rdd.foreach(num => sumAccumulator  += num)  
println(sumAccumulator.value)  

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试

关闭