( transformation算子中的shuffle算子)
目录
1、cogroup算子:(协分组、或者称为联合分组)
(1)group与cogroup:
①group:是将一个RDD里面的多个分区中的key相同的数据,通过shuffle进入到同一个分区的同一组中;
②cogroup:是将两个或多个RDD联合起来,通过shuffle将key相同的数据进入到同一个分区的同一个组中;得到的结果是一个RDD;
(2)cogroup的返回值:
①经过cogroup返回的新的RDD是一个对偶元组,key相同的value不是放入一个迭代器,而是两个或多个迭代器中;
②第一个迭代器:是第一个RDD中要分组的key的value;第二个迭代器:是第二个RDD中要分组的key的value;
③如果其中一个RDD中没有对应key的value值,则结果是一个空;或者打印出来是CompactBuffer()的样子;
(3)用cogroup做wordCount:(尽量不要使用)
简单实现如下:
import org.apache.spark.rdd.RDD
import org.apache.spark.{
SparkConf, SparkContext}
//cogroup:协分组(或叫联合分组),很多算子底层都调用cogroup算子,会有shuffle产生,当处理大量数据时,会有大量shuffle,效率低。
object CoGroupDemo1 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("CoGroupDemo").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
//准备rdd
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2)))
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))
val rdd3: RDD[(String, Double)] = sc.parallelize(List(("tom", 1.0), ("shuke", 2.0)))
//调用cogroup实现wordCount功能
//注意:cogroup的几个RDD的类型必须保持一致
val rdd4: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
val rdd5: RDD[(String, Int)] = rdd4.mapValues(tp => (tp._1.sum + tp._2.sum))
//调用Action算子,并打印输出结果
val result1: Array[(String, Int)] = rdd5.collect()
println(result1.toBuffer)
}
}
注:如果数据量大,会有大量的shuffle产生,尽量不要使用cogroup做wordCount。
(4)用union和reduceByKey代替cogroup做wordCount:
①最好不要用cogroup做wordCount,没有进行局部聚合,效率低;
②解决办法:如果多个RDD进行聚合做wordCount,可以先使用Union算子,然后调用reduceByKey;
Union算子:合并到一起,union后的分区数是几个RDD相加得到的分区数量;几个RDD的类型必须保持一致。
简单实现如下:
import org.apache.spark.rdd.RDD
import org.apache.spark.{
SparkConf, SparkContext}
//cogroup:协分组(或叫联合分组),很多算子底层都调用cogroup算子,会有shuffle产生,当处理大量数据时,会有大量shuffle,效率低。
object CoGroupDemo2 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("CoGroupDemo").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
//准备rdd
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2)))
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))
val rdd3: RDD[(String, Double)] = sc.parallelize(List(("tom", 1.0), ("shuke", 2.0)))
//调用union和reduceByKey做wordCount
//注意:union的几个RDD的类型必须保持一致
val rdd4: RDD[(String, Int)] = rdd1.union(rdd2)
val rdd5: RDD[(String, Int)] = rdd4.reduceByKey(_ + _)
//调用Action算子,并打印输出结果
val result2: Array[(String, Int)] = rdd5.collect()
println(result2.toBuffer)
}
}
(5)cogroup对RDD的基本要求:
①cogroup不仅可以对两个RDD进行协分组,还可以对多个RDD进行协分组,cogroup中最多能传4个RDD,即:一个RDDcogroup四个RDD;如果有多于四个RDD进行cogroup,那就调用多次cogroup。
②cogroup可以自己cogroup自己(得到的两个迭代器是相同的)。
③cogroup要求的RDD类型必须是一致的,不一致会报错(union要求的RDD的类型也要是相同的)。
(6)cogroup与shuffle:
①cogroup中有几个stage?
cogroup有3个;
cogroup先进行了局部分组聚合,在全局聚合;
简单示意图如下:
注:图中写的几个stage不代表先后顺序;
stage的先后顺序看资源情况与调度分配,如果资源充足几个左面的两个stage同时进行,完成后右面的stage执行。
②什么是job?
Driver向Executor提交的作业;
触发一次Acition形成一个完整的DAG;
一个DAG对应一个Job;
一个Job中有一到多个Stage,一个Stage对应一个TaskSet,一个TaskSet中有一到多个Task。
③什么是DAG?
概念:有向无环图;
DAG是对多个RDD转换过程和依赖关系的描述;
触发Action就会形成一个完整的DAG,一个DAG就是一个Job;
④为什么有stage?
stage是任务执行阶段;
Stage执行是有先后顺序的,先执行前的,再执行后面的;如果不按顺序执行,结果会出错;
一个Stage对应一个TaskSet;一个TaskSet中的Task的数量取决于Stage中最后一个RDD分区的数量。
⑤什么是shuffle?
shuffle就是将上游数据按照一定规律(规律由分区器决定)将数据打散,分给下游的多个分区;严格地说,是下游的task到上游拉取数据;只要存在这种可能性,就是shuffle(跟数据有关)。
Shuffle的功能是将具相一定规律的数据按照指定的分区器的分区规则,通过网络,传输到指定地一台机器的一个分区中即Task中。
简单示意图如下:
(7)使用cogroup算子的简单案例:
import org.apache.spark.rdd.RDD
import org.apache.spark.{
SparkConf, SparkContext}
//cogroup:协分组(或叫联合分组),很多算子底层都调用cogroup算子,会有shuffle产生,当处理大量数据时,会有大量shuffle,效率低。
object CoGroupDemo {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("CoGroupDemo").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
//准备rdd
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2)))
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))
val rdd3: RDD[(String, Double)] = sc.parallelize(List(("tom", 1.0), ("shuke", 2.0)))
//1、调用CoGroup算子,得到的是一个对偶元组,value中放的是两个或多个迭代器
val rdd4: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
val result: Array[(String, (Iterable[Int], Iterable[Int]))] = rdd4.collect()
println(result.toBuffer)
/*
cogroup与group的区别是:
①group:是将一个RDD里面的多个分区中的key相同的数据,通过shuffle进入到同一个分区的同一组中;
②cogroup:是将两个或多个RDD联合起来,通过shuffle将key相同的数据进入到同一个分区的同一个组中;得到的结果是一个RDD;
*/
/*
//2、调用cogroup实现wordCount功能,不推荐,效率低
val rdd4: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
val rdd5: RDD[(String, Int)] = rdd4.mapValues(tp => (tp._1.sum + tp._2.sum))
//调用Action算子,并打印输出结果
val result1: Array[(String, Int)] = rdd5.collect()
println(result1.toBuffer)
*/
/*
//3、调用union和reduceByKey实现wordCount功能
//注意:union的几个RDD的类型必须保持一致
val rdd4: RDD[(String, Int)] = rdd1.union(rdd2)
val rdd5: RDD[(String, Int)] = rdd4.reduceByKey(_ + _)
//调用Action算子,并打印输出结果
val result2: Array[(String, Int)] = rdd5.collect()
println(result2.toBuffer)
*/
/*
4、cogroup多个RDD
cogroup要求key的类型一定相同,但是value的类型可以不同
一个rdd最多可以cogroup 3个RDD
*/
val cogroup3: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Double]))] = rdd1.cogroup(rdd2, rdd3)
//调用Action算子,并打印输出结果
val result3: Array[(String, (Iterable[Int], Iterable[Int], Iterable[Double]))] = cogroup3.collect()
println(result3.toBuffer)
}
}
(8)cogroup的底层实现:
PairRDDFunction类中:
/**
* For each key k in `this` or `other1` or `other2` or `other3`,
* return a resulting RDD that contains a tuple with the list of values
* for that key in `this`, `other1`, `other2` and `other3`.
*/
def cogroup[W1, W2, W3](other1: RDD[(K, W1)],
other2: RDD[(K, W2)],
other3: RDD[(K, W3)],
partitioner: Partitioner)
: RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2], Iterable[W3]))] = self.withScope {
if (partitioner.isInstanceOf[HashPartitioner] && keyClass.isArray) {
throw new SparkException("HashPartitioner cannot partition array keys.")
}
val cg = new CoGroupedRDD[K](Seq(self, other1, other2, other3), partitioner)
cg.mapValues {
case Array(vs, w1s, w2s, w3s) =>
(vs.asInstanceOf[Iterable[V]],
w1s.asInstanceOf[Iterable[W1]],
w2s.asInstanceOf[Iterable[W2]],
w3s