Spark 笔记

spark 概述

什么是spark

Spark 是一个由加州大学伯克利分校 (UC Berkeley AMP) 开发的一站式通用大数据计算框架。Spark 的核心技术弹性分布式数据集 (Resilient Distributed Datasets,RDD), 提供了比Hadoop 更加丰富的 MapReduce 模型, 拥有 MapReduce 所具有的所有优点, 但不同于MapReduce 的是, Spark 中 Job 的中间输出和结果可以保存在内存中, 从而可以基于内存快速的对数据集进行多次迭代, 来支持复杂的机器学习、 图计算和准实时流处理等, 效率更高, 速度更快。

spark 的生态圈

目前,Spark 已经发展成为一个包含多个子项目的集合,其中包含 SparkSQL、Spark Streaming、GraphX、MLlib 等子项目。
在这里插入图片描述

Spark Core:实现了Spark的基本功能,包含任务调度、内存管理、错误恢复、与存储系统交互等模块。Spark Core中还包含了对弹性分布式数据集(Resilient Distributed DataSet,简称RDD)的API定义。

Spark SQL:是Spark用来操作结构化数据的程序包。通过Spark SQL,我们可以使用 SQL或者Apache Hive版本的SQL方言(HQL)来查询数据。Spark SQL支持多种数据源,比如Hive表、Parquet以及JSON等。

Spark Streaming:是Spark提供的对实时数据进行流式计算的组件。提供了用来操作数据流的API,并且与Spark Core中的 RDD API高度对应。

Spark MLlib:提供常见的机器学习(ML)功能的程序库。包括分类、回归、聚类、协同过滤等,还提供了模型评估、数据 导入等额外的支持功能。

集群管理器:Spark 设计为可以高效地在一个计算节点到数千个计算节点之间伸缩计算。为了实现这样的要求,同时获得最大灵活性,Spark支持在各种集群管理器(Cluster Manager)上运行,包括Hadoop YARN、Apache Mesos,以及Spark自带的一个简易调度 器,叫作独立调度器。

spark 特点


  • 与 Hadoop 的 MapReduce 相比,Spark 基于内存的运算要快 100 倍以上,基于硬盘的运算也要快 10 倍以上。Spark 实现了高效的 DAG 执行引擎,可以通过基于内存来高效处理数据流。

  • 易用
    Spark 支持 Java、Python、R 和 Scala 的 API,还支持超过 80 种高级算法,使用户可以快速构建不同的应用。而且 Spark 支持交互式的 Python 和 Scala 的 shell,可以非常方便地在这些 shell 中使用 Spark 集群来验证解决问题的方法。

  • 通用
    Spark 提供了统一的解决方案。Spark 可以用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)。这些不同类型的处理都可以在同一个应用中无缝使用。Spark 统一的解决方案非常具有吸引力,毕竟任何公司都想用统一的平台去处理遇到的问题,减少开发和维护的人力成本和部署平台的物力成本。

  • 兼容性
    Spark 可以非常方便地与其他的开源产品进行融合。比如,Spark 可以使用 Hadoop 的 YARN 和 Apache Mesos 作为它的资源管理和调度器,并且可以处理所有 Hadoop 支持的数据,包括 HDFS、HBase 和 Cassandra 等。这对于已经部署 Hadoop 集群的用户特别重要,因为不需要做任何数据迁移就可以使用 Spark 的强大处理能力。Spark 也可以不依赖于第三方的资源管理和调度器,它实现了 Standalone 作为其内置的资源管理和调度框架,这样进一步降低了 Spark 的使用门槛,使得所有人都可以非常容易地部署和使用 Spark。此外,Spark 还提供了在 EC2 上部署 Standalone 的 Spark 集群的工具。

Spark 的运行模式

spark 的运行模式多种多样,在单机上既可以以本地模式运行,也可以以伪分布模式运行。当以分布式的方式在cluster集群中运行时,底层的资源调度可以使用mesos 或者 yarn,也可以使用spark 自带的Standalone 模式
在这里插入图片描述

重要角色

  • Application

application 的概念和 hadoop mapreduce 中的类似,都是指用户编写的spark应用程序,其中包含了一个driver 功能和分布在集群中多个节点上运行executor 代码

  • Driver(驱动器)

Spark的驱动器是执行开发程序中的main方法的进程。它负责开发人员编写的用来创建SparkContext、创建RDD,以及进行RDD的转化操作和行动操作代码的执行。如果你是用spark shell,那么当你启动Spark shell的时候,系统后台自启了一个Spark驱动器程序,就是在Spark shell中预加载的一个叫作 sc的SparkContext对象。如果驱动器程序终止,那么Spark应用也就结束了。主要负责:
1)把用户程序转为作业(JOB)
2)跟踪Executor的运行状况
3)为执行器节点调度任务
4)UI展示应用运行状况


1、把用户程序转为任务:Driver在运行的时候,会将一个作业根据shuffle切分成一个或多个stage,然后在将stage分成一个或多个task。
2、为Executor调度任务:当cluster manager启动Executor后,Executor会向Driver反向注册自己,因此Driver会得到每个Executor的状态信息,Driver会将task根据数据所在的位置分配给合适的executor,executor在运行的时候,会将计算的中间数据存储下来,同时Driver会对这些数据进行跟踪,利用存储数据的位置调度之后的任务

  • Executor(执行器)

Spark Executor是一个工作进程,负责在 Spark 作业中运行任务,任务间相互独立。Spark 应用启动时,Executor节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。如果有Executor节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他Executor节点上继续运行。主要负责:
1)负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程;
2)通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接缓存在Executor进程内的,因此任务可以在运行时充分利用缓存数据加速运算。

  • (4) cluster manager

指的是集群上获取资源的外部服务,目前有三种类型。
Standalone :Spark原生资源管理,由Master负责对资源的分配,可以在亚马逊上运行EC2。
Apache Mesos:与HadoopMapReduce兼容的一个很好的资源调度框架。
Hadoop Yarn:主要指Yarn 中的 ResourceManager

  • worker

集群中任何可以运行 application代码的节点,类似于yarn 中的NodeManager节点,在standalone模式中指的就是通过slave文件配置的worker节点,在 spark on yarn 模式中指的就是Node Manager 节点

  • task

被送到某个executor上的工作单元,和 hadoop mapReduce 中的mapTask 和 reduceTask 概念一样,是运行Application的基本单元,多个Task组成stage ,而task的调度和管理等由下面的taskScheduler负责

  • job

包含多个task组成的并行计算,往往由spark Action 触发产生。一个application中可能会产生多个job

  • stage

每个job会被拆分很多组task,作为一个taskSet ,其名称为 stage。stage的划分和调度由下面的DAGScheduler负责,Stage有非最终的stage(即shuffle map stage)和最终的stage(即result stage)两种。stage的边界就是发生shuffle 的地方

  • DAGScheduler

根据Job构建基于stage的DAG,并提交stage给TaskScheduler。其划分stage的依据是RDD之间的依赖关系

  • TaskScheduler

将taskset提交给worker(集群)运行,每个executor运行什么task就是在此处分配的

  • RDD

spark的基本计算单元,可以通过一系列算子进行操作(主要有transformation 和 action操作)。同时,RDD 是spark最核心的东西,它表示已被分区、被序列化的、不可变的、有容错机制的,并且能够被并行操作的数据集合。其存储级别可以是内存,也可以是磁盘

  • 共享变量

在spark application 运行时,可能需要共享一些变量,提供给task 或 driver等使用。spark 提供了两种共享变量,一种是可以缓存到各个节点的广播变量,另一种是只支持加法操作,可以实现求和的累加变量

  • 宽依赖

或称为 shuffleDependency ,与 Hadoop mapReduce 中 shuffer 的数据依赖相同,宽依赖需要计算好所有父RDD对应分区的数据,然后在节点之间进行shuffle

  • 窄依赖

或称为narrowDependency,指某个具体的RDD,其分区 partition a最多被子RDD中的一个分区partition b 依赖。此种情况只有map 任务,是不需要发生 shuffle 过程的。窄依赖又分为 1:1 和 N:1两种。

Local模式

local 模式就是运行在一台机器上的模式,也称单节点模式 。Local 模式是最简单的一种Spark运行方式,它采用单节点多线程(CPU)方式运行, 通常就是用于在本机学习或者测试使用的

Standalone模式

在这里插入图片描述

Yarn模式(重点)

Spark客户端直接连接Yarn,不需要额外构建Spark集群。有yarn-client和yarn-cluster两种模式,主要区别在于:Driver程序的运行节点

yarn-client:Driver程序运行在客户端,适用于交互、调试,希望立即看到app的输出

yarn-cluster:Driver程序运行在由RM(ResourceManager)启动的AP(APPMaster)适用于生产环境。

在这里插入图片描述

client
  • Driver运行在Client
  • AM职责就是去YARN上申请资源
  • Driver会和请求到的container/executor进行通信
  • Driver是不能退出的

使用Spark On YARN的client提交方式提交Spark应用程序的执行步骤

  1. Spark Yarn Client向YARN的ResourceManager申请启动Application Master。
  2. ResourceManager收到请求后,在集群中选择一个NodeManager节点,为应用程序分配一个Container,并在Container中启动ApplicationMaster,ApplicationMaster中不包含Driver程序,它只负责启动和监控Executor,并和客户端的Driver进行通信。
  3. Client中的SparkContext初始化完毕后,与ApplicationMaster建立通讯,向ResourceManager注册,并申请资源(Container)
  4. 一旦ApplicationMaster申请到资源(也就是Container)后,便与对应的NodeManager通信,要求它在获得的Container中启动Executor,Executor启动后会向Client中的SparkContext注册并申请Task
  5. client中的SparkContext分配Task给Executor执行,Executor运行Task并向Driver汇报运行的状态和进度,以让Client随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务
  6. 应用程序运行完成后,Client的SparkContext向ResourceManager申请注销并关闭自己因为是与Client端通信,所以Client不能关闭。

客户端的Driver将应用提交给Yarn后,Yarn会先后启动ApplicationMaster和executor,另外ApplicationMaster和executor都 是装载在container里运行,container默认的内存是1G,ApplicationMaster分配的内存是driver- memory,executor分配的内存是executor-memory。同时,因为Driver在客户端,所以程序的运行结果可以在客户端显 示,Driver以进程名为SparkSubmit的形式存在。

cluster
  • Driver运行位置在AM
  • Client提交上去了 它退出对整个作业没影响
  • AM(申请资源)+Driver(调度DAG,分发任务)

使用Spark On YARN的cluster提交方式提交Spark应用程序的执行步骤

  1. 客户端向YARN的ResourceManager提交Spark应用程序。
  2. ResourceManager收到请求后,选择一个NodeManager节点向其分配一个Container,并在该Container中启动ApplicationMaster,ApplicationMaster中包含SparkContext的初始化。
  3. ApplicationMaster向ResourceManager申请Container。ResourceManager收到请求后,向ApplicationMaster分配Container。
  4. ApplicationMaster请求NodeManager,NodeManager在获得的Container中启动executor。
  5. executor启动后,向ApplicationMaster的Driver中的SparkContext注册并申请Task(这一点与Spark On YARN的client方式不一样)。
  6. executor得到Task后,开始执行Task,并向SparkContext汇报执行状态和进度等信息。
  7. 应用程序运行完成后,ApplicationMaster向ResourceManager申请注销并关闭自己

Mesos模式(了解)

常见术语

在这里插入图片描述

spark core

什么是RDD

RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark 中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。在Spark 中,对数据的所有操作不外乎创建RDD、转化已有RDD 以及调用RDD 操作进行求值。每个RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。RDD 具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD 允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。RDD 支持两种操作:transformation操作和action操作。RDD 的转化操作是返回一个新的RDD 的操作,比如map()和filter(),而action操作则是向驱动器程序返回结果或把结果写入外部系统的操作。比如count() 和first()。
Spark 采用惰性计算模式,RDD 只有第一次在一个行动操作中用到时,才会真正计算。Spark 可以优化整个计算过程。默认情况下,Spark 的RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个RDD , 可以使用RDD.persist() 让Spark 把这个RDD 缓存下来。

RDD有5个主要的属性:

原文:

Internally, each RDD is characterized by five main properties:

  • A list of partitions
  • A function for computing each split
  • A list of dependencies on other RDDs
  • Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
  • Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

译文:

在内部,每个 RDD 都有五个主要属性:

  • 分区列表
  • 计算每个拆分的函数
  • 对其他 RDD 的依赖列表
  • 可选地,键值 RDD 的分区器(例如,说 RDD 是散列分区的)
  • 可选地,用于计算每个拆分的首选位置列表(例如,HDFS 文件的块位置)

说明:

  • 一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。图3-1描述了分区存储的计算模型,每个分配的存储是由BlockManager实现的。每个分区都会被逻辑映射成BlockManager的一个Block,而这个Block会被一个Task负责计算。
  • 一个计算每个分区的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。详情请参阅3.4.5节。
  • RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
  • 一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。使用Partitioner必须满足两个前提:1.rdd必须是<key,value>形式 2.发生shuffle操作。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。
  • 一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。

RDD特点

RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。

分区

RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据。如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。
在这里插入图片描述

只读

如下图所示,RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。
在这里插入图片描述

由一个RDD转换到另一个RDD,可以通过丰富的操作算子实现,不再像MapReduce那样只能写map和reduce了,如下图所示。
在这里插入图片描述

RDD的操作算子包括两类,一类叫做transformations,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。下图是RDD所支持的操作算子列表。

依赖

RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖,RDDs之间分区是一一对应的,另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。
在这里插入图片描述

缓存

如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示,RDD-1经过一系列的转换后得到RDD-n并保存到hdfs,RDD-1在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的RDD-1转换到RDD-m这一过程中,就不会计算其之前的RDD-0了。
在这里插入图片描述

CheckPoint

虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDDs之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD支持checkpoint将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDDs了,它可以从checkpoint处拿到数据。

RDD的依赖关系

Lineage

RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

在这里插入图片描述

宽依赖

宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle,总结:宽依赖我们形象的比喻为超生

在这里插入图片描述

窄依赖

窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女

在这里插入图片描述

DAG

DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据

在这里插入图片描述

任务的划分

RDD任务切分中间分为:Application、Job、Stage和Task

1)Application:初始化一个SparkContext即生成一个Application

2)Job:一个Action算子就会生成一个Job

3)Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。

4)Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。

注意:Application->Job->Stage-> Task每一层都是1对n的关系。

算子

转换算子

在这里插入图片描述

在这里插入图片描述

行动算子

在这里插入图片描述

在这里插入图片描述

缓存

Spark RDD 缓存是在内存存储RDD计算结果的一种优化技术。把中间结果缓存起来以便在需要的时候重复使用,这样才能有效减轻计算压力,提升运算性能。

当对RDD执行持久化操作时,每个节点都会将自己操作的RDD的partition持久化到内存中,并且在之后对该RDD的反复使用中,直接使用内存缓存的partition。这样的话,对于针对一个RDD反复执行多个操作的场景,就只要对RDD计算一次即可,后面直接使用该RDD,而不需要反复计算多次该RDD。

cache() & persist()

RDD通过persist方法或cache方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中。

但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。

通过查看源码发现cache最终也是调用了persist方法,默认的存储级别都是仅在内存存储一份

缓存有可能丢失,或者存储存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。

CheckPoint

Spark中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。

为当前RDD设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用SparkContext.setCheckpointDir()设置的。在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。

持久化存储级别

级别使用空间CPU时间是否在内存中是否在磁盘上备注
MEMORY_ONLY
MEMORY_ONLY_2数据存2份
MEMORY_ONLY_SER数据序列化
MEMORY_ONLY_SER_2数据序列化,数据存2份
MEMORY_AND_DISK中等部分部分如果数据在内存中放不下,则溢写到磁盘
MEMORY_AND_DISK_2中等部分部分数据存2份
MEMORY_AND_DISK_SER部分部分
MEMORY_AND_DISK_SER_2部分部分数据存2份
DISK_ONLY
DISK_ONLY_2数据存2份
OFF_HEAP
MEMORY_ONLY

使用未序列化的Java对象格式,将数据保存在内存中。如果内存不够存放所有的数据,则数据可能就不会进行持久化。那么下次对这个RDD执行算子操作时,那些没有被持久化的数据,需要从源头处重新计算一遍。这是默认的持久化策略,使用cache()方法时,实际就是使用的这种持久化策略。

MEMORY_ONLY_SER

基本含义同MEMORY_ONLY。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。

MEMORY_AND_DISK

使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会将数据写入磁盘文件中,下次对这个RDD执行算子时,持久化在磁盘文件中的数据会被读取出来使用。

MEMORY_AND_DISK_SER

基本含义同MEMORY_AND_DISK。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。

DISK_ONLY

使用未序列化的Java对象格式,将数据全部写入磁盘文件中。

OFF_HEAP

这个目前是试验型选项,类似MEMORY_ONLY_SER,但是数据是存储在堆外内存的。

后缀带“_2”的存储级别

对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于进行容错。假如某个节点挂掉了,节点的内存或磁盘中的持久化数据丢失了,那么后续对RDD计算时还可以使用该数据在其他节点上的副本。如果没有副本的话,就只能将这些数据从源头处重新计算一遍了。

如何选择一种最合适的持久化策略
  • 默认情况下,性能最高的当然是MEMORY_ONLY,但前提是你的内存必须足够足够大,可以绰绰有余地存放下整个RDD的所有数据。因为不进行序列化与反序列化操作,就避免了这部分的性能开销;对这个RDD的后续算子操作,都是基于纯内存中的数据的操作,不需要从磁盘文件中读取数据,性能也很高;而且不需要复制一份数据副本,并远程传送到其他节点上。但是这里必须要注意的是,在实际的生产环境中,恐怕能够直接用这种策略的场景还是有限的,如果RDD中数据比较多时(比如几十亿),直接用这种持久化级别,会导致JVM的OOM内存溢出异常。
  • 如果使用MEMORY_ONLY级别时发生了内存溢出,那么建议尝试使用MEMORY_ONLY_SER级别。该级别会将RDD数据序列化后再保存在内存中,此时每个partition仅仅是一个字节数组而已,大大减少了对象数量,并降低了内存占用。这种级别比MEMORY_ONLY多出来的性能开销,主要就是序列化与反序列化的开销。但是后续算子可以基于纯内存进行操作,因此性能总体还是比较高的。此外,可能发生的问题同上,如果RDD中的数据量过多的话,还是可能会导致OOM内存溢出的异常。
  • 如果纯内存的级别都无法使用,那么建议使用MEMORY_AND_DISK_SER策略,而不是MEMORY_AND_DISK策略。因为既然到了这一步,就说明RDD的数据量很大,内存无法完全放下。序列化后的数据比较少,可以节省内存和磁盘的空间开销。同时该策略会优先尽量尝试将数据缓存在内存中,内存缓存不下才会写入磁盘。
  • 通常不建议使用DISK_ONLY和后缀为_2的级别:因为完全基于磁盘文件进行数据的读写,会导致性能急剧降低,有时还不如重新计算一次所有RDD。后缀为_2的级别,必须将所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性能开销,除非是要求作业的高可用性,否则不建议使用。

RDD 分区器

Spark目前支持Hash分区Range分区,用户也可以自定义分区,Hash分区为当前的默认分区,Spark中分区器直接决定了RDD中分区的个数、RDD中每条数据经过Shuffle过程属于哪个分区和Reduce的个数

注意:

(1)只有Key-Value类型的RDD才有分区器的,非Key-Value类型的RDD分区器的值是None
(2)每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于那个分区的。

Hash分区

HashPartitioner分区的原理:对于给定的key,计算其hashCode,并除以分区的个数取余,如果余数小于0,则用余数+分区的个数(否则加0),最后返回的值就是这个key所属的分区ID。

HashPartitioner分区弊端:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据。

Ranger分区

将一定范围内的数映射到某一个分区内,尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大,但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内。

累加器

分布式共享只写变量

累加器支持在所有不同节点之间进行累加计算(比如计数或者求和)。

累加器用来对信息进行聚合,通常在向 Spark传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。

Spark累加器支持在Driver端进行全局汇总的计算需求,实现分布式计数的功能。累加器在Driver端定义赋初始值,在Excutor端更新,最终在Driver端读取最后的汇总值。
在这里插入图片描述

广播变量

分布式共享只读变量

广播变量用来把变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每个任务都生成一个副本。

在Driver端使用broadcast()将一些大变量(List、Array)持久化,Executor根据broadcastid拉取本地缓存中的Broadcast对象,如果不存在,则尝试远程拉取Driver端持久化的那份Broadcast变量。
在这里插入图片描述
这样所有的Executor均存储了一份变量的备份,这个executor启动的task会共享这个变量,节省了通信的成本和服务器的资源。注意不能广播RDD,因为RDD不存储数据;同时广播变量只能在Driver端定义和修改,Executor端只能读取。

spark sql

spark streaming

其他

Spark 运行流程

在这里插入图片描述

  1. SparkContext 向资源管理器注册并向资源管理器申请运行 Executor
  2. 资源管理器分配 Executor,然后资源管理器启动 Executor
  3. Executor 发送心跳至资源管理器
  4. SparkContext 构建 DAG 有向无环图
  5. 将 DAG 分解成 Stage(TaskSet)
  6. 把 Stage 发送给 TaskScheduler
  7. Executor 向 SparkContext 申请 Task
  8. TaskScheduler 将 Task 发送给 Executor 运行
  9. 同时 SparkContext 将应用程序代码发放给 Executor
  10. Task 在 Executor 上运行,运行完毕释放所有资源

spark作业提交流程

  • YarnClient 运行模式介绍
    在这里插入图片描述

    在YARN Client模式下,Driver在任务提交的本地机器上运行,Driver启动后会和ResourceManager通讯申请启动ApplicationMaster,随后ResourceManager分配container,在合适的NodeManager上启动ApplicationMaster,此时的ApplicationMaster的功能相当于一个ExecutorLaucher,只负责向ResourceManager申请Executor内存。

    ResourceManager接到ApplicationMaster的资源申请后会分配container,然后ApplicationMaster在资源分配指定的NodeManager上启动Executor进程,Executor进程启动后会向Driver反向注册,Executor全部注册完成后Driver开始执行main函数,之后执行到Action算子时,触发一个job,并根据宽依赖开始划分stage,每个stage生成对应的taskSet,之后将task分发到各个Executor上执行。

  • YarnCluster 模式介绍
    在这里插入图片描述
    在YARN Cluster模式下,任务提交后会和ResourceManager通讯申请启动ApplicationMaster,随后ResourceManager分配container,在合适的NodeManager上启动ApplicationMaster,此时的ApplicationMaster就是Driver。

    Driver启动后向ResourceManager申请Executor内存,ResourceManager接到ApplicationMaster的资源申请后会分配container,然后在合适的NodeManager上启动Executor进程,Executor进程启动后会向Driver反向注册,Executor全部注册完成后Driver开始执行main函数,之后执行到Action算子时,触发一个job,并根据宽依赖开始划分stage,每个stage生成对应的taskSet,之后将task分发到各个Executor上执行。

如何理解Spark中血统(RDD)的概念?它的作用是什么?

RDD(分布式弹性数据集)是Spark的基础数据单元,代表一个不可变、可分区、里面的元素可并行计算的集合。其本身不存储数据,仅作为数据访问的一种虚拟结构。Spark通过对RDD的相互转换操作完成整个计算过程。还可以形成依赖关系,进而实现管道化,从而避免了中间结果的存储,大大降低了数据复制、磁盘IO和序列化开销。

RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。

RDD在Lineage依赖方面分为两种窄依赖与宽依赖,用来解决数据容错时的高效性以及划分任务时候起到重要作用

如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用

RDD是弹性数据集,“弹性”体现在哪里呢?你觉得RDD有哪些缺陷?

  1. 自动进行内存和磁盘切换
  2. 基于lineage的高效容错
  3. task如果失败会特定次数的重试
  4. stage如果失败会自动进行特定次数的重试,而且只会只计算失败的分片
  5. checkpoint【每次对RDD操作都会产生新的RDD,如果链条比较长,计算比较笨重,就把数据放在硬盘中】和persist 【内存或磁盘中对数据进行复用】(检查点、持久化)
  6. 数据调度弹性:DAG TASK 和资源管理无关
  7. 数据分片的高度弹性repartion

缺陷:
惰性计算的缺陷也是明显的:中间数据默认不会保存,每次动作操作都会对数据重复计算,某些计算量比较大的操作可能会影响到系统的运算效率

Spark的宽窄依赖

在这里插入图片描述

  • 宽依赖:父RDD的一个分区会被子RDD的多个分区依赖(涉及到shuffle)
  • 窄依赖:父RDD的一个分区只会被子RDD的一个分区依赖

DAGSchduler的工作原理

  • DAGScheduler是一个面向stage调度机制的高级调度器,为每个job计算stage的DAG(有向无环图),划分stage并提交taskset给TaskScheduler。
  • 追踪每个RDD和stage的物化情况,处理因shuffle过程丢失的RDD,重新计算和提交。
  • 查找rdd partition 是否cache/checkpoint。提供优先位置给TaskScheduler,等待后续TaskScheduler的最佳位置划分

TaskScheduler的工作原理

DAG 中为什么要划分 Stage?

并行计算
一个复杂的业务逻辑如果有 shuffle,那么就意味着前面阶段产生结果后,才能执行下一个阶段,即下一个阶段的计算要依赖上一个阶段的数据。那么我们按照 shuffle 进行划分(也就是按照宽依赖就行划分),就可以将一个 DAG 划分成多个 Stage/阶段,在同一个 Stage 中,会有多个算子操作,可以形成一个 pipeline 流水线,流水线内的多个平行的分区可以并行执行。

如何划分 DAG 的 stage?

根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。

对于窄依赖,partition 的转换处理在 stage 中完成计算,不划分(将窄依赖尽量放在在同一个 stage 中,可以实现流水线计算)。

对于宽依赖,由于有 shuffle 的存在,只能在父 RDD 处理完成后,才能开始接下来的计算,也就是说需要要划分 stage。

Stage划分算法

  • 从触发action操作的算子开始,从后往前遍历DAG。
  • 为最后一个rdd创建finalStage。
  • 遍历过程中如果发现该rdd是宽依赖,则为其生成一个新的stage,与旧stage分隔而开,此时该rdd是新stage的最后一个rdd。
  • 如果该rdd是窄依赖,将该rdd划分为旧stage内,继续遍历,以此类推,继续遍历直至DAG完成。

每个stage又根据什么决定task个数

Stage是一个TaskSet,将Stage根据分区数划分成一个个的Task。

说说job、stage和task的关系

Job、stage和task是spark任务执行流程中的三个基本单位。其中job是最大的单位,也是Spark Application任务执行的基本单元,由action算子划分触发生成。

stage隶属于单个job,根据shuffle算子(宽依赖)拆分。单个stage内部可根据数据分区数划分成多个task,由TaskScheduler分发到各个Executor上的task线程中执行。

在这里插入图片描述

列举Spark常用的transformation和action算子,有哪些算子会导致Shuffle?

  • transformation

map
mapRartition
flatMap
filter

  • action

reduce
collect
first
take

  • 有哪些会引起Shuffle过程的Spark算子呢?

reduceByKey
groupByKey
ByKey

reduceByKey与groupByKey的区别,哪一种更具优势?

  • reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
  • groupByKey:按照key进行分组,直接进行shuffle

所以,在实际开发过程中,reduceByKey比groupByKey,更建议使用

Repartition和Coalesce 的关系与区别,能简单说说吗?

  • 关系:
    两者都是用来改变RDD的partition数量的,repartition底层调用的就是coalesce方法:coalesce(numPartitions, shuffle = true)
  • 区别:
    repartition一定会发生shuffle,coalesce 根据传入的参数来判断是否发生shuffle。
    一般情况下增大rdd的partition数量使用repartition,减少partition数量时使用coalesce。

Spark cache一定能提升计算性能么?说明原因?

cache是将数据缓存到内存里,当小数据量的时候是能提升效率,但数据大的时候内存放不下就会报溢出。

Cache和persist有什么区别和联系?

cache调用了persist方法,cache只有一个默认的缓存级别MEMORY_ONLY ,而persist可以根据情况设置其它的缓存级别。

简述下Spark中的缓存(cache和persist)与checkpoint机制,并指出两者的区别和联系

  • 位置
    Persist 和 Cache将数据保存在内存,Checkpoint将数据保存在HDFS

  • 生命周期
    Persist 和 Cache 程序结束后会被清除或手动调用unpersist方法,Checkpoint永久存储不会被删除。

  • RDD依赖关系
    Persist 和 Cache,不会丢掉RDD间的依赖链/依赖关系,CheckPoint会斩断依赖链。

Spark中共享变量(广播变量和累加器)的基本原理与用途

  • 广播变量
    在Driver端使用broadcast()将一些大变量(List、Array)持久化,Executor根据broadcastid拉取本地缓存中的Broadcast对象,如果不存在,则尝试远程拉取Driver端持久化的那份Broadcast变量。
    在这里插入图片描述
    这样所有的Executor均存储了一份变量的备份,这个executor启动的task会共享这个变量,节省了通信的成本和服务器的资源。注意不能广播RDD,因为RDD不存储数据;同时广播变量只能在Driver端定义和修改,Executor端只能读取。

  • 累加器
    Spark累加器支持在Driver端进行全局汇总的计算需求,实现分布式计数的功能。累加器在Driver端定义赋初始值,在Excutor端更新,最终在Driver端读取最后的汇总值。
    在这里插入图片描述

Spark 中的 OOM 问题?

  1. map 类型的算子执行中内存溢出如 flatMap,mapPatitions
    • 原因:map 端过程产生大量对象导致内存溢出:这种溢出的原因是在单个 map 中产生了大量的对象导致的针对这种问题。
    • 解决方案:
      • 增加堆内内存。
      • 在不增加内存的情况下,可以减少每个 Task 处理数据量,使每个 Task 产生大量的对象时,Executor 的内存也能够装得下。具体做法可以在会产生大量对象的 map 操作之前调用 repartition 方法,分区成更小的块传入 map。
  2. shuffle 后内存溢出如 join,reduceByKey,repartition。
    • shuffle 内存溢出的情况可以说都是 shuffle 后,单个文件过大导致的。在 shuffle 的使用,需要传入一个 partitioner,大部分 Spark 中的 shuffle 操作,默认的 partitioner 都是 HashPatitioner,默认值是父 RDD 中最大的分区数.这个参数 spark.default.parallelism 只对 HashPartitioner 有效.如果是别的 partitioner 导致的 shuffle 内存溢出就需要重写 partitioner 代码了.
  3. driver 内存溢出
    • 用户在 Dirver 端口生成大对象,比如创建了一个大的集合数据结构。解决方案:将大对象转换成 Executor 端加载,比如调用 sc.textfile 或者评估大对象占用的内存,增加 dirver 端的内存
    • 从 Executor 端收集数据(collect)回 Dirver 端,建议将 driver 端对 collect 回来的数据所作的操作,转换成 executor 端 rdd 操作。

Spark调优

Spark 的基本工作流程

在这里插入图片描述
在这里插入图片描述

Spark - submit 参数

在这里插入图片描述

Spark 应用的基本概念

在这里插入图片描述

在这里插入图片描述

Spark 提供的存储级别及其具体含义

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值