Spark之RDD

--http://dblab.xmu.edu.cn/blog/1406/

版本对照表;maven的配置

运行示例: 此案例有两个参数   输入文件位置  输出文件位置

复制代码
package dblab.WordCount

 import org.apache.spark.SparkContext
 import org.apache.spark.SparkContext._
 import org.apache.spark.SparkConf
 import org.apache.spark.rdd.RDD
 import java.text.SimpleDateFormat
 import java.util.Date
 
object WordCount {
    def main(args: Array[String]): Unit = {
      
    /**
     * 改为传参的:输入地址及输出目录
     */
    val InURL = args(0)
    val OutURL = args(1)
    val conf = new SparkConf()
    //保存文件夹名随机时间/任务名随机时间标识
    val Da = System.currentTimeMillis()
    /**
      * 如果这个参数不设置,默认认为你运行的是集群模式
      * 如果设置成local代表运行的是local模式,这里我通过Saprk-submit提交时
      * 设置 --master 指定模式
      */
    //conf.setMaster("local")
    //设置任务名
    conf.setAppName(s"ScalaDemo$Da")
    //创建SparkCore的程序入口
    val sc = new SparkContext(conf)
    //读取文件 生成RDD
    val file: RDD[String] = sc.textFile(InURL)
    //把每一行数据按照空格分割
    val word: RDD[String] = file.flatMap(_.split(" "))
    //让每一个单词都出现一次
    val wordOne: RDD[(String, Int)] = word.map((_,1))
    //单词计数
    val wordcount: RDD[(String, Int)] = wordOne.reduceByKey(_+_)
    //按照单词出现的次数即上个RDD中第二列排序 
    val sortRdd: RDD[(String, Int)] = wordcount.sortBy(x => x._2)
    //输出结果
    println("--------------------------")
    sortRdd.foreach(print)
    println("--------------------------")
    //保存结果
    sortRdd.saveAsTextFile(s"$OutURL$Da")
    sc.stop()
  }
}
复制代码

 

//集群下运行 定位到spark的bin目录下执行
spark-submit --class dblab.WordCount.WordCount --conf spark.default.parallelism=6 --num-executors 2 --executor-cores 2 --master spark://cdh1:7077,cdh2:7077 /home/WordCount-0.0.1-SNAPSHOT.jar /home/Scala0701.txt hdfs://BigData/spark/Word

 

 

 

//输出文件阶段任务截图(其他两个job类似)

 

 

 

 

 

下图因前两步此前已运行过:参照https://www.cnblogs.com/qingyunzong/p/8899715.html

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

如:灰色部分

RDD属性:

1.一组分片(Partition),即数据集的基本组成单位,对RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度,用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值,即程序所分配到的CPU Core的数目;

2.一个计算每个分区的函数;Spark中RDD的计算是以分片为单位的,每个RDD都会实现Computer函数以达到这个目的.computer函数会对迭代器进行复合,不需要保存每次计算的结果;

3.RDD间的依赖关系;RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系;在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算;

4.一个partitioner;即RDD的分片函数,当前spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另一个是基于范围的RangerPartitioner;只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Partitioner的值是None;Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffer输出时的分片数量;

5.一个列表,存储存取每个Partitioner的优先位置(preferred location);对于一个HDFS文件来说,这个列表保存的就是每个Partition所在块的位置;按照移动'数据不如移动计算'的理念,Spark在进行任务调度的时候,会尽可能将计算任务分配到其所要处理数据块的储存位置;

代码运行抽象图:此处我是单机模式下运行

 RDD创建方式:

1.通过读取文件生成

scala> val file = sc.textFile("employee.txt")
file: org.apache.spark.rdd.RDD[String] = employee.txt MapPartitionsRDD[1] at textFile at <console>:23

2.通过并行化的方式创建RDD

如:由一个已经存在的Scala集合创建

scala> val arr = Array(1,2,3,4)
arr: Array[Int] = Array(1, 2, 3, 4)

scala> val rdd = sc.parallelize(arr)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[5] at parallelize at <console>:24

3.其他方式

如:读取数据库等其他操作;RDD是可以通过其他RDD转换而来的

RDD编程API:

Spark支持两个类型(算子)操作:Transformation和Action

1.Transformation

  主要做的就是将一个已有的RDD生成另外一个RDD;Transformation具有lazy特性(延迟加载);Transformation算子的代码不会被真正的执行,只有当我们的程序里遇到一个action算子的时候,代码才会被真正的执行,这种设计让Spark更加有效率的运行;

常用的Transformation:

实例详见:https://www.itfh.cn/post/4579.html

转换 含义
 map(func) 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
 filter(func) 返回一个新的RDD,该RDD由于经过func函数计算后返回值为true的输入元素组成 
 flatMap(func)

类似于map,但是每个输入元素可以被映射为0或者多个输出元素

(所以func应该返回一个序列,而不是单一元素) 

 mapPartitions(func)

 类似于map但独立的在RDD的每个分片上运行,因此在类型为T的RDD上运行时,

func的函数类型必须是Iterator[T] => Iterator[U]

 mapPartitionsWithIndex(func)

 类似于mapPartitions,但func带有一整个参数表示分片的索引值,

因此在类型为T 的RDD上运行时,func的函数类型必须是

(Int, Iterator[T]) => Iterator[U]

 sample(withReplacement, fraction, seed)

 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换

seed用于指定随机数生成器种子

 union(otherDataset)  对源RDD和参数RDD求并集返回一个新的RDD
 intersection(otherDataset)  对源RDD和参数RDD求交集返回一个新的RDD
 distinct([numTasks])  对源RDD进行去重后返回一个新的RDD
 groupByKey([numTasks])

在一个(K,V)的RDD上调用, 返回一个(K,Iterator[V])的RDD

reduceByKey(func, [numTasks])

在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数

将相同的key的值聚合到一起,与groupByKey类似,

reduce任务的个数可以通过第二个可选的参数来设置

aggregateByKey(zeroValue)(seqOp, compOp,[numTasks])        

先按分区聚合,再总的聚合,每次要跟初始值交流

例如: aggregateByKey(0)(_+_,_+_)对K/Y的RDD进行操作

sortByKey([ascending],[numTasks])

在一个(K,V)的RDD上调用,K必须实现Ordered接口,

返回一个按照K进行排序的(K,V)的RDD

 sortBy(func,[ascending],[numTasks])

 与sortByKey相似,但是更灵活 第一个参数是根据什么排序,

第二个是怎么排序,false倒序 第三个排序后分区数 默认与原RDD一样

join(otherDataset,[numTasks])

在类型为(K,V)和(K,WL)的RDD上调用,返回一个相同key对应的

所有元素对在一起的(K,(V,W))的RDD相当于内连接(求交集)

cogroup(otherDataset,[numTasks])

在类型为(K,V)和(K,W)的RDD上调用,返回一个

(K,(Iterable<V>,Iterable<W>))类型的RDD

cartesian(otherDataset) 两个RDD的笛卡尔积 形成很多个K/V

pipe(cimmand,[envVars])

这个也许是自定义化的核心使用

调用外部程序
coalesce(numPartitions,[shuffle])

重新分区,第一个参数为分区数量,第二个参数为是否shuffer

默认false,少分区变多分区 true

repartition(numPartitions)

重新分区 必须shuffle 参数是要分多少区,少变多

(多变少也可以,但是没意义)

repartitionAndSortWithinPartitions(partitioner) 重新分区+排序 比先分区再排序效率高,对(K/V)的RDD操作
 foldByKey(zeroValue)(seqOp)

 该函数用于K/V做折叠,合并处理,与aggregate类似,

第一个括号的参数应用于每个V值,第二个括号函数是聚合例如: _+_

combineByKey
第一个参数表示:将相同key的第一个数据进行结构转换,实现操作 
第二个参数:分区内的计算规则 
第三个参数:分区间的计算规则
合并相同的key的值 rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)

 partitionBy(partitioner)  对RDD进行分区,partitioner 是分区器,如 new HashPartitioner(2)
 cache

  RDD缓存,可以避免重复计算减少时间;

区别:cache内部调用了persist算子,cache默认就一个缓存级别

MEMORY-ONLY,而persist可以选择缓存级别

 persist
 subtract(rdd)  返回前rdd元素不在后rdd的 rdd
leftOuterJoin

类似SQL中的左外连接,返回结果以前边RDD为主,关联不上为空;

只能用于两个RDD之间关联,如果是多个就多关联几次

rightOuterJoin

类似SQL中的右外连接,返回结果以参数RDD为主,关联不上为空;

只能用于两个RDD之间关联,如果是多个就多关联几次

subtractByKey

类似subtract,这里是针对K操作;

返回主RDD中出现,并且不在otherRDD的元素

为触发代码的运行,一段spark代码中至少要一个action操作

常用的Action:

动作 含义
reduce(func) 通过func函数聚集RDD中所有元素,这个功能必须是可交换且可并联的
 collect()

 在驱动程序中,以数组的形式返回数据集所有元素

其二是使用偏函数保存符合的元素到MappedRDD(数据量大时避免使用)

详见:http://www.javashuo.com/article/p-zzymsdgp-mw.html

count() 返回RDD的元素个数
 first()  返回RDD的第一个元素类似take(1)
 take(n)  返回一个由是数据集前n个元素组成的数组
 takeSample(boolean,num,[seed])  返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足部分,see用于指定随机数生成器种子(这个随机数得属于RDD的其中一个,且不好把控,建议不选)
 takeOrdered(n,[ordering]) 与top(n)相反,获取RDD中最小的前num个值,返回一个集合
 saveAsTextFile(path)

将数据集元素以textFile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,spark会调用toString方法,将它转换为文件中的文本

(spark写文件到hdfs需要一个原本不存在的目录,sc.hadoopConfiguration.set("mapreduce.fileoutputcommitter.marksuccessfuljobs", "false") 取消_SUCCESS文件生成) 

 saveAsSequenceFile(path)  相应地址存放Sequence文件
 saveAsSequenceFile(path)  相应地址存放Object文件
countByKey()

针对(K,V)类型的RDD,返回(K,Int)类型的map,

Int表示每个key对应的元素个数

foreach(func)

在数据集每个元素上,运行函数func进行更新

https://blog.csdn.net/qq_43928549/article/details/107409775
rdd有foreach方法,rdd.collect之后也有foreacrdd有foreach方法,rdd.collect之后也有foreach,这两个方法却大不一样h,这两个方法却大不一样;

详见:https://blog.csdn.net/qq_30285985/article/details/110953259

aggregate

先对分区操作再对整体操作

--需实验

--https://blog.csdn.net/f_n_c_k/article/details/88698764?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-88698764-blog-81542580.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-88698764-blog-81542580.pc_relevant_default&utm_relevant_index=1

 reduceByKeyLocally(func)

 该算子将RDD(K,V)中每个K对应的V值根据映射函数func来运算,

运算结果映射到一个Map(K,V)中,而不是RDD(K,V)

 lookup(k)

 针对(K,V)型RDD,返回指key对应的元素形成的Seq,

这个函数处理的优化部分在于是否包含分区器,有则处理对应

K所在分区,返回(K,V)组成的Seq,无则对全RDD扫描,返回K对应V

有无分区器是指对于RDD,那么我计算得到这俩并无区别,--待实验

 top(N)  前N,相当于sortBy+take,排序取前几
 fold(num)(func)  

num为初始值,func为元素与初始值进行的操作

 foreachPartition

  foreachPartition传入的迭代器,foreach传入的是迭代器产生的所有值进行处理,

例:foreachpartion是每个分区执行一遍,比如说m个分区,n个数据,foreachpartion会执行m次,foreach会执行m*n次

 --常用于数据库连接等;这个后续实例中进行测试

 

 

 

 

 

 

 

 

 

 

 

RDD之宽窄依赖

RDD是粗粒度的操作数据集,每个Transformation操作都会生成一个新的RDD,所以RDD之间就会生成类似流水线的前后关系,RDD和他依赖的父RDD的关系有两种不同类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)

如图:

从图中可知:

窄依赖:是指每个父RDD的一个Partition最多被子RDD的一个Partition所使用,例如map,union,filter等操作,都会产生窄依赖;(独生)

宽依赖:是指一个父RDD的Partiton会被多个子RDD的Partition所使用,例如groupByKey,reduceByKey,sortByKey等操作都会产生宽依赖;(超生)

join有两种情况:

1.图左半部分join:两个RDD在进行join时,一个RDD的Partition仅仅和另一个已知个数的Partition进行join,那么这种类型的join为窄依赖

2.图右半部分join:除上述情况外的均为宽依赖,由于是需要父RDD所有Partition进行join转换,这就涉及到了shuffer,因此这种情况为宽依赖

复制代码
scala> val Demo = sc.textFile("/home/Scala0701.txt")
Demo: org.apache.spark.rdd.RDD[String] = /home/Scala0701.txt MapPartitionsRDD[68] at textFile at <console>:23

scala> val word = Demo.flatMap(_.split(" "))
word: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[69] at flatMap at <console>:23

scala> val wordOne = word.map((_,1))
wordOne: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[70] at map at <console>:23

scala> val wordCount = wordOne.reduceByKey(_+_)
wordCount: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[71] at reduceByKey at <console>:23

scala> val wordSort = wordCount.sortBy(x => x._2,false)
wordSort: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[76] at sortBy at <console>:23

scala> wordSort.saveAsTextFile("/spark/Word/")

scala>
复制代码

 总结:

这里我们从父RDD的Partition被使用个数来定义窄依赖和宽依赖,因此可以用一句话概括:如果父RDD的一个Partiiton被子RDD的一个Partiiton所使用就是窄依赖,否则就是宽依赖;如果是确定的partition数量依赖关系,那么RDD之间的关系就是窄依赖;由此得出:窄依赖不仅包含一对一的窄依赖,还包含一对固定个数的窄依赖;

一对固定个数窄依赖详解:子RDD的partition对父RDD依赖的Partition的数量不会随着RDD的数据规模而改变;如:无论是100T还是1G的数据量,在窄依赖中,子RDD所依赖的父RDD的partition的个数是确定的,而宽依赖是shuffle级别的,数据量越大,子RDD所依赖的父RDD个数就越多,从而子RDD所依赖的父RDD的partition个数越来越多;

宽窄依赖下的数据流视图:

在spark中,会根据RDD之间的依赖关系将DAG图(有向无环图)划分为不同的阶段,对于窄依赖,由于partition依赖关系的确定性,partition的转换处理就可以在同一个线程里完成,窄依赖就被spark划分到同个stage中,而对于宽依赖,只能等父RDD shuffle处理完成后,下一个stage才能开始接下来的计算;

因此spark中stage划分思路是:从后往前推,遇到宽依赖就断开,划分为一个stage;遇到窄依赖就将这个RDD加入该stage中;因此RDD C,D,E,F被构建在同个stage中,RDD A被构建在一个单独stage中,而RDD B,G又被构建在同个stage;

在spark中,Task的类型分为两种:ShuffleMapTask和ResultTask,

即,DAG的最后一个阶段会为每个结果的partition生成一个ResultTask,即每个Stage里的Task的数量是由该Stage中最后一个RDD的partition的数量决定的!而其余所有阶段都会生成ShuffleMapTask;之所以称为ShuffleMapTask是因为它需要将自己的计算结果通过shuffle到下一个stage中,也就是说上图中的stage1和stage2相当于mapreduce中的Mapper,而ResultTask所代表的stage3相当于mapreduce中的reducer;

如:此前操作的WordCount程序,因此可知,Hadoop中MapReduce操作中的Mapper和Reducer在spark中的基本等量算子是map和reduceByKey;其区别在于,Hadoop中的MapReduce天生就是排序的,而reduceByKey只是根据key进行reduce,但是spark除了这俩算子外还有很多其他算子,因此某种意义上说spark比Hadoop的计算算子更为丰富

shuffle知识点开始----------------------------------------------------------------------

--什么情况下会涉及shuffer呢(两个对照查看)

https://www.jianshu.com/p/542b243d24e9

http://t.zoukankan.com/devos-p-4795338.html

 Shuffle在Spark中即是把父RDD中的KV对按照Key重新分区,从而得到一个新的RDD。也就是说原本同属于父RDD同一个分区的数据需要进入到子RDD的不同的分区;这种数据打乱然后汇聚到不同节点的过程就是shuffle;

 shuffle流程:

spark是以shuffle为边界,将一个job划分为不同的stage,这些stage构成了一个大粒度的DAG;Spark的shuffle主要分为shuffle write和shuffle read两个阶段;

执行shuffle的主体是stage中的并发任务,这些任务分为shuffleMapTask和resuleTask两大类;shuffleMapTask要进行shuffle, resultTask负责返回计算结果,一个job中只有最后一个stage采用resultTask,其他的均为shuffleMapTask;

如果按照map端和reduce端分析;shuffleMapTask可以即是map端任务,又是reduce端任务;因为spark中的shuffle是可以串行的,reduceTask则只能充当reduce端任务的角色;

 

spark shuffle流程抽象为如下几个步骤:

shuffleWrite -> 如果需要,先在map端做数据预聚合->写入本地输出文件->shuffleRead->fetch数据块->reduce端做数据预聚合->如果需要,进行数据排序

shuffleWrite阶段: 发生于shuffleMapTask对该stage的最后一个RDD完成了map计算之后,首先会判断是否需要对计算结果进行聚合,然后将最终结果按不同的reduce端进行区分,写入前节点的本地磁盘;

shuffleRead阶段:开始于reduce端的任务读取shuffledRDD之后,首先通过远程或者本地数据拉取获得write阶段各个节点中属于当前任务的数据,根据数据的key进行聚合,然后判断是否需要排序,最后生成新的RDD

shuffle技术演进:

 在spark shuffle的具体实现上,主要经历了:hash-bashed shuffle,sort-bashed shuffle,Tungsten-sort shuffle三个大的阶段;

 1.hash-based shuffle V1

在spark 0.8之前版本采用此机制;

 (1).shuffle Write

在shuffle Write过程会按照hash的方式重组partition的数据,不进行排序;每个map端的任务为每个reduce端的任务都生成一个文件,通过大量的文件(假如map端task为m,reduce端task数量为n,则对应m * n个中间文件),其中伴随着大量的随机磁盘IO操作与大量的内存开销;

 (2)shuffle Read

reduce端任务首先将shuffle write生成的文件fetch到本地节点,如果shuffle Read阶段有combiner操作,则它会把拉到的数据保存在一个spark封装的哈希表(appendonlyMap)中进行合并

 

(3)源码结构

在代码结构上:

org.apache.spark.storage.ShuffleBlockManager 负责 Shuffle Write

org.apache.spark.BlockStoreShuffleFetcher负责shuffle Read

org.apache.spark.Aggregator负责combine,y依赖于AppendOnlyMap

(4)优缺点

该版本的spark Shuffle存在如下两个严重问题:

  1.生成大量文件,占用文件描述符,同时引入DiskObjectWrite带来的Writer Handler的缓存也非常消耗内存;

  2.如果在reduce task时需要合并操作的话,会把数据放在一个HashMap中进行合并,如果数据量较大,容易引发OOM;

2.Hash Shuffle V2

在spark0.8.1针对原来的hash-based shuffle机制,File Consolidation机制;

一个Executor上所有的Map Task生成的分区文件只有一份,即将所有的Map Task相同的分区文件合并,这样每个Excecutor上最多生成N个分区文件;

 

 

这样就减少了文件数,但是假如下游stage的分区数N很大,还是会在每个Executor上生成N个文件,同样,如果一个Executor上有K个Core,还是会开 K*N个Write Handler;所以这里仍然容易导致OOM;
是否采用File Consolidation机制,需要配置spark.shuffle.consolidateFiles

3.Hash Shuffle V3

在Saprk 0.9引入了ExternalAppendOnlyMap;

在combine的时候,可以将数据spill到磁盘,然后通过堆排序merge;

4.Sort Shuffle V1

为了更好的解决上边的问题,spark参考了mapreduce中的shuffle的处理方式;

在spark 1.1引入了sort-based shuffle,但是默认仍为hash-based shuffle.在spark 1.2将默认的shuffle方式修改为sort-based shuffle;

每个Task不会为后续的每个Task创建单独的文件,而是将所有的结果写入同一个文件;该文件中的记录首先是按照Partition Id排序,每个partition内部再按照Key进行排序,Map Task运行期间会顺序写每个partition的数据,同时生成一个索引文件记录每个partition的大小和偏移量;

在Reduce阶段,Reduce Task拉取数据做Combine时不再是采用HashMap,而是采用ExternalAppendOnlyMap,该数据结构在做Combine时,如果内存不足,会刷写磁盘,很大程度上保证了系统的鲁棒性,避免了大数据情况下的OOM;

总体看来,Sort Shuffle解决了Hash Shuffle的所有弊端,但是因为其在shuffle过程需要对记录进行排序,所以在性能上有所损失;

 

 

 1.代码结构

  从以前的shuffleBlockManager中分离出 shuffleManager专门管理shuffle Write 和shuffle Read,两种shuffle方式分别对应:

org.apache.saprk.shuffle.hash.HashSshuffleManager

org.apache.spark.shuffle.sort.SortShuffleManager

org.apache.spark.util.collection.ExternalSort实现排序功能,可通过spark.shuffle.spill参数配置,决定是否可以在排序时将临时数据spill到磁盘;

5.Tungsten-Sort Based Shuffle

从spark 1.5.0开始,spark开始了钨丝计划(Tungsten),目的是优化内存和CPU的使用,进一步提升spark的性能;由于使用了堆外内存,而它基于JDK Sun Unsafe API,故Tungsten-Sort Based Shuffle也被称为Unsafe Shuffle;

它的做法是将数据记录用二进制的方式存储,直接在序列化的二进制数据上Sort而不是在Java对象上,这样一方面可以减少内存的使用和GC的开销,另一方面避免了Shuffle过程中频繁的序列化和反序列化;在排序过程中,它提供cache-efficient sort,使用一个8 bytes的指针,把排序转化成一个指针数组的排序,极大的优化了排序性能;

但是使用Tungsten-Sort Based Shuffle有几个限制,Shuffle阶段不能有aggregate操作,分区数不能超过一定大小(2^24-1,这是可编码的最大Partition Id),所以像reduceByKey这种有aggregate操作的算子不能使用Tungsten-Sort Bashed Shuffle,它会退化的采用Sort Shuffle;

6.Sort Shuffle V2

从Spark-1.6.0开始,把Sort Shuffle和Tungsten-Sort Based Shuffle全部统一到Sort Shuffle中,如果检测到满足Tungsten-Sort Based Shuffle条件,会自动采用Tungsten-Sort Based Shuffle,否则采用Sort Shuffle;从spark-2.0.0开始,把Hash Shuffle移除,可以说目前Spark-2.0中只有一种shuffle;

shuffle总结:

1.Shuffle Read相关问题:

  (1).什么时候获取数据,Parent Stage的一个ShuffleMapTask执行完还是等全部ShuffleMapTask执行完?
      当Parent Stage的所有ShuffleMapTask结束后再fetch;

  (2).边获取边处理还是一次性获取完在处理?

    因为spark不要求Shuffle后的数据全局有序,因此没必要等到全部数据shuffle完成再处理,所以是边fetch边处理;

  (3).刚获取来的数据存放在哪里?

    刚获取来的数据存放在softBuffer缓冲区,经过处理后的数据放在内存+磁盘上;

  内存使用的是AppendOnlyMap,类似Java的HashMap,内存加磁盘使用的是ExternalAppendOnlyMap,如果内存空间不足,ExternalAppendOnlyMap可以将records进行port后spill(溢出)到磁盘上,等到需要它们的时候在进行归并;

  (4).怎么获得数据存放的位置?

    通过请求Driver端的MapOutPutTrackerMaster询问ShuffleMapTask输出的数据位置;

Shuffle触发机制

 以下算子会触发Shuffle:

  1.repartition类:repartition,coalesce

  2.*ByKey类:groupByKey,reduceByKey,combineByKey,aggregateByKey等

  3.join相关: cogroup等

Spark Shuffle版本变更

  • Spark 0.8 及以前 Hash Based Shuffle

  • Spark 0.8.1 为 Hash Based Shuffle引入File Consolidation机制

  • Spark 0.9 引入 ExternalAppendOnlyMap

  • Spark 1.1 引入 Sort Based Shuffle,但默认仍为 Hash Based Shuffle

  • Spark 1.2 默认的 Shuffle 方式改为 Sort Based Shuffle

  • Spark 1.4 引入 Tungsten-Sort Based Shuffle

  • Spark 1.6 Tungsten-Sort Based Shuffle 并入 Sort Based Shuffle

  • Spark 2.0 Hash Based Shuffle 退出历史舞台

 

 shuffle知识点结束----------------------------------------------------------------------

 原作:https://www.cnblogs.com/qingyunzong/p/8899715.html

posted @   M_Fight๑҉  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示