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 |
|
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(数据量大时避免使用) |
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 详见:https://blog.csdn.net/qq_30285985/article/details/110953259 |
aggregate |
先对分区操作再对整体操作 --需实验 |
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知识点结束----------------------------------------------------------------------
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库