RDD算子
- 一.RDD转换算子
- 1)map
- 2)mapPartitions
- 3)mapPartitionsWithIndex
- 4)flatMap
- 5)glom
- 6)groupBy
- 7)filter
- 8)sample
- 9)distinct
- 10)coalesce
- 11)repartition
- 12)sortBy
- 13)双Value类型{ intersection , union , subtract , zip }
- 14)Key-Value类型{ partitionBy , reduceByKey , groupByKey , aggregateByKey , foldByKey , combineByKey }
- 15)join
- 16)leftOuterJoin和rightOuterJoin
- 17)cogroup
- 二. RDD行动算子
- 1)reduce
- 2)collect
- 3)count
- 4)first
- 5)take
- 6)takeOrdered
- 7) aggregate
- 8) fold
- 9) countByKey
- 10) save相关算子
- 11) foreach
算子:Operator(操作)
RDD的方法和Scala集合对象的方法不一样
集合对象的方法都是在同一个节点的内存中完成的
RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行
为了区分不同的处理效果,所以将RDD的方法称为算子
RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行
一.RDD转换算子
1)map
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-map
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//1,2,3,4
//2,4,6,8
//转换函数
// def mapFunction(num:Int):Int={
// num*2
// }
// val mapRDD: RDD[Int] = rdd.map(mapFunction)
// val mapRDD: RDD[Int] = rdd.map((num:Int)=>{num*2}) //匿名函数
val mapRDD: RDD[Int] = rdd.map(_*2)
mapRDD.collect().foreach(println)
sc.stop()
}
}
2
4
6
8
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-map
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//1,2一个分区 3,4一个分区
//1.rdd的计算一个分区内的数据是一个一个执行逻辑
// 只有前面一个数据全部的逻辑执行完毕后,才会执行下一个数据
// 分区内数据的执行是有序的
//2.不同分区数据计算是无序的
val mapRDD1= rdd.map(
x => {
println("List:"+x)
x
}
)
val mapRDD2= mapRDD1.map(
x => {
println("转换后List:" + x)
x
})
mapRDD2.collect()
sc.stop()
}
}
List:1
List:3
转换后List:1
转换后List:3
List:2
List:4
转换后List:2
转换后List:4
2)mapPartitions
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-mapPartitions
//mapPartitions:可以以分区为单位进行数据转换操作
// 但是会将整个分区的数据加载到内存进行引用
// 如果处理完的数据是不会被释放掉,存在对象的引用
// 在内存较小,数据量较大的场合下,容易出现内存溢出
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val rdd1: RDD[Int] = rdd.mapPartitions(iter => {
println("一个分区执行一次")
iter.map(_ * 2)
})
rdd1.collect().foreach(println)
sc.stop()
}
}
一个分区执行一次
一个分区执行一次
2
4
6
8
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-mapPartitions
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//[1,2] [3,4]
//可以得出各分区内的最大值
val rdd1 = rdd.mapPartitions(iter => {
List(iter.max).iterator
})
rdd1.collect().foreach(println)
sc.stop()
}
}
2
4
map和mapPartitions的区别
- 数据处理角度
Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子是以分区为单位进行批处理操作。 - 功能的角度
Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变, 所以可以增加或减少数据 - 性能的角度
Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处理,所以性能较高。但是mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用mapPartitions,使用 map 操作。
3)mapPartitionsWithIndex
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-mapPartitionsWithIndex
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
// [1,2] [3,4]
// [3,4]
val rdd1: RDD[Int] = rdd.mapPartitionsWithIndex((index, iter) => {
if (index == 1) {
iter
} else {
Nil.iterator //Nil返回空集合
}
})
rdd1.collect().foreach(println)
sc.stop()
}
}
3
4
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-mapPartitionsWithIndex
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 1,2,3,4
// (分区号,数字)
val rdd1: RDD[(Int, Int)] = rdd.mapPartitionsWithIndex((index, iter) => {
iter.map(x => (index, x))
})
rdd1.collect().foreach(println)
sc.stop()
}
}
(2,1)
(5,2)
(8,3)
(11,4)
4)flatMap
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-flatMap
val rdd: RDD[List[Int]] = sc.makeRDD(List(
List(1, 2), List(3,4)))
val rdd1: RDD[Int] = rdd.flatMap(x=>x)
rdd.collect().foreach(println)
rdd1.collect().foreach(println)
sc.stop()
}
}
List(1, 2)
List(3, 4)
1
2
3
4
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-flatMap
val rdd: RDD[String] = sc.makeRDD(List(
"hello spark", "hello java"))
val rdd1: RDD[String] = rdd.flatMap(x => {
x.split(" ")
})
rdd1.collect().foreach(println)
sc.stop()
}
}
hello
spark
hello
java
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-flatMap
//数据类型不一致时,使用模式匹配
val rdd: RDD[Any] = sc.makeRDD(List(List(1,2),3,List(4,5)))
val rdd1: RDD[Any] = rdd.flatMap(x => {
x match {
case x: List[Int] => x
case x => List(x)
}
})
rdd1.collect().foreach(println)
sc.stop()
}
}
1
2
3
4
5
5)glom
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-glom
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//List => Int
//Int => Array
val glomRdd: RDD[Array[Int]] = rdd.glom()
glomRdd.collect().foreach(x=>println(x.mkString(",")))
sc.stop()
}
}
1,2
3,4
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-glom
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//[1,2],[3,4] 分区取最大值
//[2] [4] 最大值求和
//[6]
val glomRDD: RDD[Array[Int]] = rdd.glom()
val maxRDD: RDD[Int] = glomRDD.map(
array => {
array.max
}
)
println(maxRDD.collect().sum)
sc.stop()
}
}
6
6)groupBy
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-groupBy
val rdd: RDD[String] = sc.makeRDD(List("hello","spark","hadoop","scala","java"),2)
val groupRDD: RDD[(Char, Iterable[String])] = rdd.groupBy(_.charAt(0))
groupRDD.collect().foreach(println)
sc.stop()
}
}
(h,CompactBuffer(hello, hadoop))
(j,CompactBuffer(java))
(s,CompactBuffer(spark, scala))
1.txt
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:02:05:03 +000
uu wr erw 17/05/2015:12:05:03 +000
uu wr erw 17/05/2015:03:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:03:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:02:05:03 +000
uu wr erw 17/05/2015:02:05:03 +000
uu wr erw 17/05/2015:12:05:03 +000
uu wr erw 17/05/2015:12:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[String] = sc.textFile("datas\\1.txt")
val timeRDD: RDD[(String, Iterable[(String, Int)])] = rdd.map(line => {
val datas: Array[String] = line.split(" ")
val time: String = datas(3)
val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
/*
* parse()返回的是一个Date类型数据
* parse方法可以把String型的字符串转换成特定格式的date类型,
* 使用parse时字符串长度要和定义的SimpleDateFormat对象长度一致
*/
val date: Date = sdf.parse(time)
println(date)
println("----------------")
val sdf1 = new SimpleDateFormat("HH")
/*
* format返回的是一个String类型的数据
* format方法可以把Date型字符转换成特定格式的String类型,
* 如果Date类型和定义的SimpleDateFormat长度不一致会自动在后面补0
*/
val hour: String = sdf1.format(date)
println(hour)
(hour, 1)
// 法二:字符串截取
// val str: String = datas(3).substring(11, 13)
//(str, 1)
}).groupBy(_._1)
timeRDD.map {
//模式匹配
case (hour, iter) => (hour, iter.size)
}.collect().foreach(println)
sc.stop()
}
}
Sun May 17 11:05:03 CST 2015
Sun May 17 10:05:03 CST 2015
----------------
----------------
11
10
Sun May 17 02:05:03 CST 2015
----------------
Sun May 17 11:05:03 CST 2015
----------------
02
11
Sun May 17 11:05:03 CST 2015
----------------
11
Sun May 17 02:05:03 CST 2015
----------------
02
Sun May 17 02:05:03 CST 2015
----------------
02
Sun May 17 12:05:03 CST 2015
----------------
12
Sun May 17 12:05:03 CST 2015
----------------
12
Sun May 17 03:05:03 CST 2015
----------------
03
Sun May 17 10:05:03 CST 2015
----------------
10
Sun May 17 10:05:03 CST 2015
----------------
10
Sun May 17 12:05:03 CST 2015
----------------
Sun May 17 10:05:03 CST 2015
----------------
12
10
Sun May 17 03:05:03 CST 2015
----------------
Sun May 17 10:05:03 CST 2015
----------------
03
10
Sun May 17 11:05:03 CST 2015
----------------
11
Sun May 17 10:05:03 CST 2015
----------------
10
(02,3)
(11,4)
(03,2)
(12,3)
(10,6)
7)filter
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-filter
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
rdd.filter(num=>(num%2!=0)).collect().foreach(println)
sc.stop()
}
}
1
3
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-filter
val rdd: RDD[String] = sc.textFile("datas\\1.txt")
//过滤10点的数据
rdd.filter(line=>{
line.split(" ")(3).startsWith("17/05/2015:10")
}).collect().foreach(println)
sc.stop()
}
}
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
8)sample
根据指定的规则从数据集中抽取数据
(应用场景:数据倾斜)
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-sample
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6,7,8,9,10))
//sample算子需要传递三个参数
//1.第一个参数withReplacement表示,抽取数据后是否将数据返回 true(放回),false(丢弃)
//2.第二个参数fraction表示:如果抽取不放回的场合:数据源中每条数据被抽取的概率,基准值的概念
// 如果抽取放回的场合:表示数据源中的每条数据别抽取的可能次数
//3.第三个参数seed表示,抽取数据时随机算法的种子
// 如果不传递第三个参数,那么使用的是当前系统时间
println(rdd.sample(
false,
0.4,
1
).collect().mkString(","))
println("-------------------")
println(rdd.sample(
true,
2
).collect().mkString(","))
sc.stop()
}
}
1,2,3,7,9
-------------------
2,2,2,3,3,3,3,4,4,4,5,6,7,7,7,8,8,8,9,9,9,9,10,10,10,10,10,10,10
9)distinct
将数据集中重复的数据去重
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-distinct
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,2,3,2))
println(rdd.distinct().collect() mkString (","))
sc.stop()
}
}
1,2,3,4
10)coalesce
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-coalesce
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
val newRDD: RDD[Int] = rdd.coalesce(2)
newRDD.saveAsTextFile("output")
sc.stop()
}
}
coalesce方法默认情况下不会将分区的数据打乱重新组合
这种情况下的缩减分区可能会导致数据不均衡,出现数据倾斜
如果想要让数据均衡,可以进行shuffle处理,第二个参数设为true
coalesce算子可以扩大分区的,但是如果不进行shuffle操作,是没有意义的,不起作用如果
想要实现扩大分区的效果,需要使用shuffle操作
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-coalesce
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
val newRDD: RDD[Int] = rdd.coalesce(2,true)
newRDD.saveAsTextFile("output")
sc.stop()
}
}
11)repartition
该操作内部其实执行的是 coalesce 操作,底层是coalesce函数,参数 shuffle 的默认值为 true。无论是将分区数多的RDD 转换为分区数少的RDD,还是将分区数少的 RDD 转换为分区数多的RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-repartition
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),2)
val newRDD: RDD[Int] = rdd.repartition(3)
newRDD.saveAsTextFile("output")
sc.stop()
}
}
12)sortBy
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原RDD 的分区数一致。中间存在 shuffle 的过程
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-sortBy
//sortBy方法可以根据指定的规则对数据源中的数据进行排序,默认是升序
//第二个参数可以改变排序的方式,用false
//sortBy默认情况下,不会改变分区,但是中间存在shuffle操作
val rdd: RDD[Int] = sc.makeRDD(List(4,3,2,6,4,1))
println(rdd.sortBy(x => x,false).collect().mkString(","))
println("---------------")
val rdd1: RDD[(String, Int)] = sc.makeRDD(List(("1",1),("11",2),("2",3)))
println(rdd1.sortBy(x => x._1).collect().mkString(","))
println("---------------")
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("1",1),("11",2),("2",3)))
println(rdd2.sortBy(x => x._1.toInt).collect().mkString(","))
sc.stop()
}
}
6,4,4,3,2,1
---------------
(1,1),(11,2),(2,3)
---------------
(1,1),(2,3),(11,2)
13)双Value类型{ intersection , union , subtract , zip }
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子-双Value类型
//交集,并集和差集要求两个数据源数据类型保持一致
//拉链操作两个数据源的类型可以不一致
//拉链操作时,两个数据源要求分区数量要保持一致,分区中数据数量保持一致
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))
//交集
println(rdd1.intersection(rdd2).collect().mkString(","))
//并集
println(rdd1.union(rdd2).collect().mkString(","))
//差集
println(rdd1.subtract(rdd2).collect().mkString(","))
//拉链
println(rdd1.zip(rdd2).collect().mkString(","))
sc.stop()
}
}
3,4
1,2,3,4,3,4,5,6
1,2
(1,3),(2,4),(3,5),(4,6)
14)Key-Value类型{ partitionBy , reduceByKey , groupByKey , aggregateByKey , foldByKey , combineByKey }
partitionBy
将数据按照指定Partitioner 重新进行分区。Spark 默认的分区器是HashPartitioner
object Test {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),2)
val mapRDD: RDD[(Int, Int)] = rdd.map((_,1))
//partitionBy根据指定的分区规则对数据进行重分区
mapRDD.partitionBy(new HashPartitioner(2))
.saveAsTextFile("output")
sc.stop()
}
}
reduceByKey
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- Key-Value类型
//reduceByKey:相同的key的数据进行value数据的聚合操作
//reduceByKey分区内和分区间计算规则是相同的
//Scala语言中一般的聚合操作都是两两聚合,spark基于Scala开发的,所以它的聚合也是两两聚合
//reduceByKey中如果key的数据只有一个,是不会参与运算的
val rdd: RDD[(String, Int)] =sc.makeRDD(List(("a",1),("a",2),("a",3),("b",4)))
val rdd1: RDD[(String, Int)] = rdd.reduceByKey((x,y)=>{
println(s"x=${x},y=${y}")
x+y
})
rdd1.collect().foreach(println)
sc.stop()
}
}
x=1,y=2
x=3,y=3
(a,6)
(b,4)
groupByKey
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- Key-Value类型
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("a",3),("b",4)))
//groupByKey: 将数据源中的数据,相同key的数据分在一个组中,形成一个对偶元组
// 元组中的第一个元素就是key
// 元组中的第二个元素就是相同key的value的
val rdd1: RDD[(String, Iterable[Int])] = rdd.groupByKey()
rdd1.collect().foreach(println)
println("----------------------")
val rdd2: RDD[(String, Iterable[(String, Int)])] = rdd.groupBy(_._1)
rdd2.collect().foreach(println)
sc.stop()
}
}
(a,CompactBuffer(1, 2, 3))
(b,CompactBuffer(4))
----------------------
(a,CompactBuffer((a,1), (a,2), (a,3)))
(b,CompactBuffer((b,4)))
groupByKey和reduceByKey的区别
- 从 shuffle 的角度:
reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。 - 从功能的角度:
reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那么还是只能使用groupByKey
aggregateByKey
将数据根据不同的规则进行分区内计算和分区间计算
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- Key-Value类型
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",6),("b",4),("a",6),("b",2)),2)
//取出每个分区内相同 key 的最大值然后分区间相加
//aggregateByKey存在函数柯里化,有两个参数列表
//第一个参数列表,需要传递一个参数,表示为初始值
// 主要用于当碰见第一个key的时候,和value进行分区内计算
//第二个参数列表需要传递两个参数:
// 第一个参数表示分区内计算规则
// 第二个参数表示分区间计算规则
val rdd1: RDD[(String, Int)] = rdd.aggregateByKey(5)(
(x, y) => math.max(x, y),
(x, y) => x + y
)
rdd1.collect().foreach(println)
sc.stop()
}
}
(b,11)
(a,11)
foldByKey
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为foldByKey
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- Key-Value类型
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",6),("b",2)),2)
val rdd1: RDD[(String, Int)] = rdd.foldByKey(0)(_+_)
rdd1.collect().foreach(println)
sc.stop()
}
}
(b,6)
(a,9)
combineByKey
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- Key-Value类型
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",6),("b",2)),2)
//combineByKey:方法需要三个参数
//第一个参数表示:将相同key的第一个数据进行结构的转换,实现操作
//第二个参数表示:分区内的计算规则
//第三个参数表示:分区间的计算规则
val rdd1: RDD[(String, (Int, Int))] = rdd.combineByKey(
v => (v, 1),
(t: (Int, Int), v) => {
(t._1 + v, t._2 + 1)
},
(t1: (Int, Int), t2: (Int, Int)) => {
(t1._1 + t2._1, t1._2 + t2._2)
}
)
rdd1.collect().foreach(println)
println("---------------------")
val rdd2: RDD[(String, Int)] = rdd1.mapValues {
case (num, cnt) => {
num / cnt
}
}
rdd2.collect().foreach(println)
sc.stop()
}
}
(b,(6,2))
(a,(9,3)
---------------------
(b,3)
(a,3)
reduceByKey、foldByKey、aggregateByKey、combineByKey的区别
- reduceByKey: 相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同
- FoldByKey: 相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同
- AggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则可以不相同
- CombineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构。分区内和分区间计算规则不相同。
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- Key-Value类型
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",6),("b",2)),2)
//wordcount
println(rdd.reduceByKey(_ + _).collect().mkString(","))
println(rdd.aggregateByKey(0)(_+_,_+_).collect().mkString(","))
println(rdd.foldByKey(0)(_+_).collect().mkString(","))
println(rdd.combineByKey(v=>v,(x:Int,y)=>(x+y),(x:Int,y:Int)=>(x+y)).collect().mkString(","))
sc.stop()
}
}
15)join
join会导致数据量几何增长,并且会影响shuffle的性能,不推荐使用
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- join
//join:两个不同数据源的数据,相同的key的value会连接在一起,形成元组
//如果两个数据源中key没有匹配上,那么数据不会出现在结果中
//如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔乘积,数据量会几何性增长,会导致性能降低
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",4),("c",2)))
val rdd1: RDD[(String, Any)] = sc.makeRDD(List(("a","g"),("b",5),("d",6),("a",2)))
val rdd2: RDD[(String, (Int, Any))] = rdd.join(rdd1)
rdd2.collect().foreach(println)
sc.stop()
}
}
(a,(1,g))
(a,(1,2))
(b,(4,5))
16)leftOuterJoin和rightOuterJoin
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- 左右连接
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",4),("c",2)))
val rdd1: RDD[(String, Any)] = sc.makeRDD(List(("a","g"),("b",5)))
val rdd2: RDD[(String, (Int, Option[Any]))] = rdd.leftOuterJoin(rdd1)
val rdd3: RDD[(String, (Option[Int], Any))] = rdd.rightOuterJoin(rdd1)
rdd2.collect().foreach(println)
println("---------------------")
rdd3.collect().foreach(println)
sc.stop()
}
}
(a,(1,Some(g)))
(b,(4,Some(5)))
(c,(2,None))
---------------------
(a,(Some(1),g))
(b,(Some(4),5))
17)cogroup
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
//TODO 算子- cogroup
//cogroup-connection+group
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",4),("c",2)))
val rdd1: RDD[(String, Any)] = sc.makeRDD(List(("a","g"),("b",5),("b","m")))
val rdd2: RDD[(String, (Iterable[Int], Iterable[Any]))] = rdd.cogroup(rdd1)
rdd2.collect().foreach(println)
sc.stop()
}
}
(a,(CompactBuffer(1),CompactBuffer(g)))
(b,(CompactBuffer(4),CompactBuffer(5, m)))
(c,(CompactBuffer(2),CompactBuffer()))
二. RDD行动算子
- 所谓的行动算子,其实就是触发作业(job)执行的方法,通俗点就是触发整个计算的执行
- 底层代码调用的是环境对象的runJob方法
- 底层代码中会创建ActiveJob,并提交执行
1)reduce
聚集RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//TODO-行动算子-reduce
val i: Int = rdd.reduce(_+_)
println(i)
sc.stop()
}
}
10
2)collect
在驱动程序中,以数组Array 的形式返回数据集的所有元素
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//TODO-行动算子-collect
//collect:方法会将不同分区数的数据按照分区顺序采集到Driver端内存中,形成数组
val ints: Array[Int] = rdd.collect()
println(ints.mkString(","))
sc.stop()
}
}
1,2,3,4
3)count
返回RDD 中元素的个数
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//TODO-行动算子-count
println(rdd.count())
sc.stop()
}
}
4
4)first
返回RDD 中的第一个元素
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//TODO-行动算子-first
println(rdd.first())
sc.stop()
}
}
1
5)take
返回一个由RDD 的前 n 个元素组成的数组
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//TODO-行动算子-take
println(rdd.take(3).mkString(","))
sc.stop()
}
}
1,2,3
6)takeOrdered
返回该 RDD 排序后的前 n 个元素组成的数组
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(4,2,3,1))
//TODO-行动算子-takeOrdered
println(rdd.takeOrdered(2).mkString(","))
sc.stop()
}
}
1,2
7) aggregate
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//TODO-行动算子-aggregate
//aggregateByKey:初始值只会参加分区内计算
//aggregate:初始值只会参与分区内计算,并且会参与分区间计算
val i: Int = rdd.aggregate(2)(_+_,_*_)
println(i)
sc.stop()
}
}
90
8) fold
折叠操作,aggregate 的简化版操作,分区内和分区间操作一致时
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//TODO-行动算子-fold
val i: Int = rdd.fold(2)(_+_)
println(i)
sc.stop()
}
}
16
9) countByKey
统计每种 key 的个数
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.makeRDD(List(1,1,1,4))
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",1)))
//TODO-行动算子-countByKey
val intToLong: collection.Map[Int, Long] = rdd1.countByValue()
val stringToLong: collection.Map[String, Long] = rdd2.countByKey()
println(intToLong)
println(stringToLong)
sc.stop()
}
}
Map(1 -> 3, 4 -> 1)
Map(a -> 2, b -> 1)
10) save相关算子
将数据保存到不同格式的文件中
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",3)),1)
//TODO -行动算子 save相关算子
rdd.saveAsTextFile("output")
rdd.saveAsObjectFile("output1")
//saveAsSequenceFile方法要求数据的格式必须为K-V类型
rdd.saveAsSequenceFile("output2")
sc.stop()
}
}
output
output1
output2
11) foreach
分布式遍历RDD 中的每一个元素,调用指定函数
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
//foreach其实是Driver端内存集合的循环遍历方法
//先采集再循环
rdd.collect().foreach(println)
println("---------")
//foreach其实是Executor端内存数据打印
//不是按顺序采集数据
rdd.foreach(println)
sc.stop()
}
}
1
2
3
4
---------
2
3
4
1
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val user = new User()
rdd.foreach(
num=>{
println("age="+(user.age+num))
}
)
sc.stop()
}
// class User extends Serializable {
//样例类在编译时,会自动混入序列化特质(实现可序列化接口)
case class User(){
var age:Int=30
}
}
age=32
age=33
age=34
age=31
闭包检测
object Spark_rdd_01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd: RDD[Int] = sc.makeRDD(List[Int]())
//RDD算子中传递的函数是会包含闭包(算子内使用到外部变量)操作
// 那么就会进行检测功能,检测是否序列化,称为闭包检测
val user = new User()
//因为rdd没有值,所以传递的外部变量所在类没有进行序列化,看是否报错
rdd.foreach(
num=>{
println("age="+(user.age+num))
}
)
sc.stop()
}
//未进行序列化,看是否报错
class User {
var age:Int=30
}
}
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:403)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:393)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:162)
at org.apache.spark.SparkContext.clean(SparkContext.scala:2326)
at org.apache.spark.rdd.RDD$$anonfun$foreach$1.apply(RDD.scala:926)
at org.apache.spark.rdd.RDD$$anonfun$foreach$1.apply(RDD.scala:925)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
at org.apache.spark.rdd.RDD.withScope(RDD.scala:363)
at org.apache.spark.rdd.RDD.foreach(RDD.scala:925)
at spark.test.spark.core.rdd.transform.Spark_rdd_01$.main(Spark_rdd_01.scala:18)
at spark.test.spark.core.rdd.transform.Spark_rdd_01.main(Spark_rdd_01.scala)
Caused by: java.io.NotSerializableException: spark.test.spark.core.rdd.transform.Spark_rdd_01$User
Serialization stack:
- object not serializable (class: spark.test.spark.core.rdd.transform.Spark_rdd_01$User, value: spark.test.spark.core.rdd.transform.Spark_rdd_01$User@4f824872)
- field (class: spark.test.spark.core.rdd.transform.Spark_rdd_01$$anonfun$main$1, name: user$1, type: class spark.test.spark.core.rdd.transform.Spark_rdd_01$User)
- object (class spark.test.spark.core.rdd.transform.Spark_rdd_01$$anonfun$main$1, <function1>)
at org.apache.spark.serializer.SerializationDebugger$.improveException(SerializationDebugger.scala:40)
at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:46)
at org.apache.spark.serializer.JavaSerializerInstance.serialize(JavaSerializer.scala:100)
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:400)
... 11 more