目录
③、spark.shuffle.memoryFraction
④、spark.shuffle.blockTransferService
⑤、spark.shuffle.consolidateFiles
⑥、spark.shuffle.compress和spark.shuffle.spill.compress
一、Spark集群架构
1、概述
1. Driver Program
用户编写的Spark程序称为Driver Program。每个Driver程序包含一个代表集群环境的SparkContext对象,程序的执行从Driver程序开始,所有操作执行结束后回到Driver程序中,在Driver程序中结束。如果你是用spark shell,那么当你启动 Spark shell的时候,系统后台自启了一个 Spark 驱动器程序,就是在Spark shell 中预加载的一个叫作 sc 的 SparkContext 对象。如果驱动器程序终止,那么Spark 应用也就结束了。
2. SparkContext对象
每个Driver Program里都有一个SparkContext对象,职责如下:
1)SparkContext对象联系 cluster manager(集群管理器),让 cluster manager 为Worker Node分配CPU、内存等资源。此外, cluster manager会在 Worker Node 上启动一个执行器(专属于本驱动程序)。
2)和Executor进程交互,负责任务的调度分配。
3. cluster manager 集群管理器
它对应的是Master进程。集群管理器负责集群的资源调度,比如为Worker Node分配CPU、内存等资源。并实时监控Worker的资源使用情况。一个Worker Node默认情况下分配一个Executor(进程)。
从图中可以看到sc和Executor之间画了一根线条,这表明:程序运行时,sc是直接与Executor进行交互的。
所以,cluster manager 只是负责资源的管理调度,而任务的分配和结果处理它不管。
4.Worker Node
Worker节点。集群上的计算节点,对应一台物理机器
5.Worker进程
它对应Worder进程,用于和Master进程交互,向Master注册和汇报自身节点的资源使用情况,并管理和启动Executor进程
6.Executor
负责运行Task计算任务,并将计算结果回传到Driver中。
7.Task
在执行器上执行的最小单元。比如RDD Transformation操作时对RDD内每个分区的计算都会对应一个Task。
二、Saprk调度模块
1、概述
上面我们介绍了:Driver的sc负责和Executor交互,完成任务的分配和调度,在底层,任务调度模块主要包含两大部分:
1)DAGScheduler
2)TaskScheduler
它们负责将用户提交的计算任务按照DAG划分为不同的阶段并且将不同阶段的计算任务提交到集群进行最终的计算。整个过程可以使用下图表示
RDD Objects可以理解为用户实际代码中创建的RDD,这些代码逻辑上组成了一个DAG
DAGScheduler主要负责分析依赖关系,然后将DAG划分为不同的Stage(阶段),其中每个Stage由可以并发执行的一组Task构成,这些Task的执行逻辑完全相同,只是作用于不同的数据。
在DAGScheduler将这组Task划分完成后,会将这组Task提交到TaskScheduler。TaskScheduler通过Cluster Manager 申请计算资源,比如在集群中的某个Worker Node上启动专属的Executor,并分配CPU、内存等资源。接下来,就是在Executor中运行Task任务,如果缓存中没有计算结果,那么就需要开始计算,同时,计算的结果会回传到Driver或者保存在本地。
2、Scheduler的实现概述
任务调度模块涉及的最重要的三个类是:
1)org.apache.spark.scheduler.DAGScheduler 前面提到的DAGScheduler的实现。
将一个DAG划分为一个一个的Stage阶段(每个Stage是一组Task的集合)
然后把Task Set 交给TaskScheduler模块。
2)org.apache.spark.scheduler.TaskScheduler
它的作用是为创建它的SparkContext调度任务,即从DAGScheduler接收不同Stage的任务。向Cluster Manager 申请资源。然后Cluster Manager收到资源请求之后,在Worker为其启动进程
3)org.apache.spark.scheduler.SchedulerBackend
是一个trait,作用是分配当前可用的资源,具体就是向当前等待分配计算资源的Task分配计算资源(即Executor),并且在分配的Executor上启动Task,完成计算的调度过程。
4)AKKA是一个网络通信框架,类似于Netty,此框架在Spark1.8之后已全部替换成Netty
3、任务调度流程图
4、细化
三、Spark shuffle详解
1、概述
Shuffle,翻译成中文就是洗牌。之所以需要Shuffle,是因为具有某种共同特征的一类数据需要最终汇聚(aggregate)到一个计算节点上进行计算。这些数据分布在各个存储节点上并且由不同节点的计算单元处理。
Shuffle可以理解为按照某种特定的分组(分区)条件,将数据分发到正确的分区中,经过Shuffle后,数据变得有规律了。
举例:
以简单的wordcount为例,其中数据保存在Node1,2,3上面:
这个 数据重新打乱然后汇聚到不同节点的过程就是Shuffle。
实际上,产生Shuffle时,底层过程很复杂,需要考虑很多问题:
1、数据会分散到多台服务器,需要通过正确的分区机制(算法)确保数据分发到正确的分区
2、生产环境下,Shuffle时生成的数据量相当巨大,如果仅靠内存来处理,会有内存溢出的风险,为了避免出现这种问题,所以需要引入磁盘的溢写机制。所以产生Shuffle时,势必会发生多次和大量的磁盘I/O,以及Shuffle临时文件
3、需要考虑对Shuffle临时文件的管理
4、产生Shuffle时,还会产生大量的网络传出,所以需要考虑如何节省带宽。
常见的解决手段:
①、开启压缩机制
②、引入combiner机制,比如MapReduce的Combiner,先在Map端合并,再发给Reduce端,比如Spark的ReduceByKey方法就是这种思想
③、数据本地化策略,MapReduce有此机制,Spark也有,Spark底层是有DAG调度模块来负责
2、Hash Based Shuffle Manager
在Saprk1.0以前,Spark只支持Hash Based Shuffle。因为很多运算场景不需要排序,因此多余的排序只会导致性能变差,比如MapReduce就是这样实现的,reducer拿到的数据都是已经排序好的
实际上Spark的实现很简单:每个Shuffle Map Task 根据Key的哈希值,计算出每个Key需要写入的Partition然后将数据单独写入一个文件,这个Partition实际上就对应了下游的一个Shuffle Map Task或者Result Task。因此下游的Task在计算时会通过网络读取这个文件并进行计算,如果该Task与上游的Shuffle Map Task运行在同一个节点上那么此时就是一个本地的硬盘读写。
知识点:
①、Task对应分区,一个分区就是一个Task
②、Spark的Task分两种:
MapTask
ResultTask
Hash Base Shuffle Manager的特点:
①、每个MapTask都会生成对应ResultTask数量的Shuffle临时文件,后续每个ResultTask获取属于自己分区的数据
②、这种shuffle管理器,在产生Shuffle,没有任何排序过程,节省大量的cpu。性能很高(对比MapReduce)
注意:这种Shuffle管理器的缺点是,可能会产生大量的Shuffle临时文件。总的Shuffle临时文件数=MapTask*ResultTask
临时文件过多引发的问题:
①、同时打开大量的临时文件,会消耗较多的集群内存资源
②、同时打开大量的临时文件,意味着带来大量的磁盘I/O,引起性能的下降
3、Sort Based Shuffle Manager
在Spark2.0中,Spark Core的一个重要的升级就是将默认的Hash Based Shuffle 换成了Sort Based Shuffle,Sort Based Shuffle的模式是:每一个Shuffle Map Task不会为每个Reducer生成一个单独的临时文件,相反,他会将所有的结果写到一个文件里,同时会生成一个index文件,
知识点:
Sort based Shuffle Manager的特点:
①、每个MapTask只会生成一个Shuffle文件,可以极大的减少临时文件数。即总的临时文件数=MapTask数量。此外,为了便于ResultTask获取属于自己分区的片段,引入了索引机制。
②、这种管理器,在Shuffle过程有,是排序过程的。但注意,它的排序只对分区编号排序,不对分区内的数据排序。所以效率还是很高
四、Shuffle相关参数配置
1、概述
Shuffle是Spark Core比较复杂的模块,它也是非常影响性能的操作之一。因此,在这里整理了会影响Shuffle性能的各项配置。
2、常见配置
①、spark.shuffle.manager
在Spark 1.0之前仅支持Hash Based Shuffle。Spark 1.1引入了Sort Based Shuffle。Spark 1.2的默认Shuffle机制从Hash变成了Sort。如果需要Hash Based Shuffle,只需将spark.shuffle.manager设置成“hash”即可。
配置方式:
①进入spark安装目录的conf目录
②cp spark-defaults.conf.template spark-defaults.conf
③spark.shuffle.manager=hash
应用场景:
当产生的临时文件数不是很多(小于200个)的时候,可以使用Hash,因为没有排序,性能更高
②、spark.shuffle.spill
这个参数的默认值是true,用于指定Shuffle过程中如果内存中的数据超过阈值(参考spark.shuffle.memoryFraction的设置)时是否需要将部分数据临时写入外部存储。
补充:此参数可以配置为false。没有spill,会极大提高Spark的处理效率,但前提需要确保集群内存够用,关闭此参数潜在的风险:
1、内存溢出
2、宽依赖某个子分区数据丢失的时候,可能会带来过高的重新计算代价
③、spark.shuffle.memoryFraction
在启用spark.shuffle.spill的情况下,spark.shuffle.memoryFraction决定了当Shuffle过程中使用的内存达到总内存多少比例的时候开始spill。在Spark 1.2.0里,这个值是0.2。
此参数可以适当调大,可以控制在0.4~0.6。
通过这个参数可以设置Shuffle过程占用内存的大小,它直接影响了写入到外部存储的频率和垃圾回收的频率。可以适当调大此值,可以减少磁盘I/O次数。
④、spark.shuffle.blockTransferService
在Spark 1.2.0中这个配置的默认值是netty,而在之前的版本中是nio。它主要是用于在各个Executor之间传输Shuffle数据。netty的实现更加简洁,但实际上用户不用太关心这个选项。除非有特殊需求,否则采用默认配置即可。
⑤、spark.shuffle.consolidateFiles
这个配置的默认值是false。主要是为了解决在Hash Based Shuffle过程中产生过多文件的问题。如果配置选项为true,那么对于同一个Core上运行的Shuffle Map Task不会产生一个新的Shuffle文件而是重用原来的。
⑥、spark.shuffle.compress和spark.shuffle.spill.compress
这两个参数的默认配置都是true。spark.shuffle.compress和spark.shuffle.spill.compress都是用来设置Shuffle过程中是否对Shuffle数据进行压缩。其中,前者针对最终写入本地文件系统的输出文件;后者针对在处理过程需要写入到外部存储的中间数据,即针对最终的shuffle输出文件。
1. 设置spark.shuffle.compress
需要评估压缩解压时间带来的时间消耗和因为数据压缩带来的时间节省。如果网络成为瓶颈,比如集群普遍使用的是千兆网络,那么将这个选项设置为true可能更合理;如果计算是CPU密集型的,那么将这个选项设置为false可能更好。
2. 设置spark.shuffle.spill.compress
如果设置为true,代表处理的中间结果在spill到本地硬盘时都会进行压缩,在将中间结果取回进行merge的时候,要进行解压。因此要综合考虑CPU由于引入压缩、解压的消耗时间和Disk IO因为压缩带来的节省时间的比较。在Disk IO成为瓶颈的场景下,设置为true可能比较合适;如果本地硬盘是SSD,那么设置为false可能比较合适。
⑦、spark.reducer.maxMbInFlight
这个参数用于限制一个Result Task向其他的Executor请求Shuffle数据时所占用的最大内存数,默认是64MB。尤其是如果网卡是千兆和千兆以下的网卡时。默认值是 设置这个值需要综合考虑网卡带宽和内存。