简介
Spark是一个大数据分布式并行计算框架,不仅实现了MapReduce的算子,map函数和reduce函数形成了一个计算模型。还提供了更加丰富的算子,Spark中提到的算子的概念就可以简称为RDD。
RDD叫做分布式数据集,是Spark中最基本的数据抽象,它是一个不可变,可分区,里面的元素可以并行计算的集合。
RDD数据流模型的特点
1)自动容错
2)位置感知
3)可伸缩
RDD允许在执行多个查询时显示的将工作集缓存到内存中,后续的查询能够重用这个工作集,这样可以提高效率。
ps:Spark和很多其他分布式计算系统都借用了 “分而治之”思想来实现并行处理:
把一个超大的数据集,切分成N个小堆,找M个执行器,各自拿一块或多块数据执行,执行出结果后再进行汇合,spark的工作就是:凡是能被我处理的,都要符合我的严格要求,所以Spark无论在处理什么数据都会先整合成一个拥有多个分块的 数据集,而这个数据集就是RDD。
RDD就像操作本地集合一样,很多的方法可以调用(算子),使用方便,无序关系,底层实现
val lines:RDD[String] = sc.textFile(“路径”)
通过这个方法(算子)可以返回的数据类型是RDD,可以想成通过testFile这个方法获取文件中的数据存储到RDD类型中。
深入一些
实际RDD是一个引用(指针),引用这个文件,这个文件中的一些信息可以通过这个引用来进行操作。
Spark将分布式数据抽象为弹性分布式数据集(RDD),实现了应用程序之间的调度----RPC,序列化和反序列化,压缩,并未运行在其上的上层组件提供API,其底层实现采用了Scala这种函数式语言书写而成,并且所提供的API深度借鉴了Scala函数式编程思想,提供了Scala类型的编程接口。
RDD指的是一个只读,可分区的分布式数据集,这个数据集的全部或者部分可以缓存到内存中,并可以多次在计算中重用。
RDD叫做分布式数据集,是Spark中的基本数据抽象,它代表一个不可变,不分区 里面的元素,可以并行计算
RDD的五大特征
1.RDD可以看做是一系列 partition所组成的
2.RDD之间有依赖关系
3.算子是作用在partition之上的
4.分区是做用在KV形式的RDD上
5.partition提供的最佳计算位置,利于数据处理的本地化,计算向数据移动,而不是移动数据。
RDD本身是不存储数据的,这里为了方便理解,暂时理解成了存储数据
ps:RDD 是一个引用数据的
什么是KV格式的RDD
如果RDD里面存的数据都是二元组对象(“k”,“v”),那么这个RDD我们就叫做KV格式的RDD
RDD的容错
partition数量,大小是没有限制,体现了RDD的弹性特质
RDD之间的依赖关系,可以给予上一个RDD重新计算出RDD
RDD的分布式
RDD是有partition组成,Partition是分布在不同节点之上,RDD提供计算的位置,体现了数据本地化,计算向数据移动
详细的RDD特征
RDD的属性
RDD算子
RDD算子可以分为两种类型
1)Transformation(转换)
RDD中的所谓转换都是延迟加载,也就是他们不会直接 计算结果,相反,它们只是机组这些用应用到基础数据集上的转换动作,只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正的运行,这样的设计可以让spark更加有效率的运行。
2)Action(动作):在RDD上运行计算,并返回结果给Driver或写入文件系统
ps:一个Action相当于是一个job
Transformation属于延迟计算,当一个RDD转换为另一个RDD的时候并没有立即转换,仅仅是记住了数据集的逻辑操作,当Action出发Spark作业的时候,才会真正的执行。
算子简单使用
1) 初级算子
PrimaryOperatorDemo.java
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object PrimaryOperatorDemo {
def main(args: Array[String]): Unit = {
/**
* 因为spark-shell 可以分为两种运行模式
* 集群模式:spark-shell --master spark:..hadoop1:7077
* --executor-memeory 内存m --total--executor-cores 核数
* 本地模式: spark-shell
* 无论是本地还是集群都已经创建好了两个变量
* sparkContxt -->sc sqlContext-->sqlContext
*/
//在IDEA中需要自己创建sc对象并且指定运行模式
val conf = new SparkConf().setAppName("PrimaryOperatorDemo1").setMaster("local")
val sc = new SparkContext(conf)
//1.通过并行化生成RDD
val rdd = sc.parallelize(List(5,6,4,7,3,8,2,9,10))
//2.对rdd里面的每个元素*2 然后排序
/**
* sortBy:
* 第一个参数是遍历,第二个参数是排序方式,true--代表升序
* 会受线程影响
*/
val rdd2: RDD[Int] = rdd.map(_ * 2).sortBy(x => x,true)
println(rdd2.collect().toBuffer)//ArrayBuffer(4, 6, 8, 10, 12, 14, 16, 18, 20)
//3.过滤出结果大于10
val rdd3: RDD[Int] = rdd2.filter(_ > 10)
println(rdd3.collect().toBuffer)//ArrayBuffer(12, 14, 16, 18, 20)
//4.通过并行化生成新的RDD
val rdd4: RDD[String] = sc.parallelize(Array("a b c","b c d"))
//将rdd4中的数据切分后压平
val rdd5: RDD[String] = rdd4.flatMap(_.split(" "))
println(rdd5.collect().toBuffer)//ArrayBuffer(a, b, c, b, c, d)
//6.假如List(List("a,b","b c"),List("e c","i o"))
//压平 flatMap(_.flatMap(_.split(" ")))
val list: RDD[List[String]] = sc.parallelize(List(List("a b","b c"),List("e c","i o")))
val list1: RDD[String] = list.flatMap(_.flatMap(_.split(" ")))
println(list1.collect().toBuffer)//ArrayBuffer(a, b, b, c, e, c, i, o)
//7.求并集
val rdd6: RDD[Int] = sc.parallelize(List(5,6,7,8))
val rdd7: RDD[Int] = sc.parallelize(List(1,2,5,6))
val rdd8: RDD[Int] = rdd6 union rdd7
println(rdd8.collect.toBuffer)//ArrayBuffer(5, 6, 7, 8, 1, 2, 5, 6)
//8.求交集
val rdd9: RDD[Int] = rdd6 intersection rdd7
println(rdd9.collect().toBuffer)//ArrayBuffer(6, 5)
//9.去重 去重复
println(rdd8.distinct.collect.toBuffer)//ArrayBuffer(6, 8, 2, 1, 7, 5)
//10.join--只有有相同的才能连接,没有不显示
//通过并行化生成RDD
val rdd10_1: RDD[(String, Int)] = sc.parallelize(List(("tom",1),("jerry",3),("kitty",2)))
val rdd10_2: RDD[(String, Int)] = sc.parallelize(List(("jerry",2),("tom",2),("dog",10)))
//相同的key会被合并
val rdd10_3: RDD[(String, (Int, Int))] = rdd10_1 join rdd10_2
println(rdd10_3.collect().toBuffer)//ArrayBuffer((tom,(1,2)), (jerry,(3,2)))
//11.左连接和右连接
//除了基准值外是Option类型,因为可能存在空值所以使用Option
val rdd10_4: RDD[(String, (Int, Option[Int]))] = rdd10_1 leftOuterJoin rdd10_2 //以左边为基准没有是null
val rdd10_5: RDD[(String, (Option[Int], Int))] = rdd10_1 rightOuterJoin rdd10_2 //以右边为基准没有是null
println(rdd10_4.collect().toList) //List((tom,(1,Some(2))), (jerry,(3,Some(2))), (kitty,(2,None)))
println(rdd10_5.collect().toBuffer) //ArrayBuffer((tom,(Some(1),2)), (dog,(None,10)), (jerry,(Some(3),2)))
//12.求一个并集
val rdd11: RDD[(String, Int)] = rdd10_1 union rdd10_2
println(rdd11.collect().toList)//List((tom,1), (jerry,3), (kitty,2), (jerry,2), (tom,2), (dog,10))
//按照key进行分组
val rdd11_1: RDD[(String, Iterable[(String, Int)])] = rdd11.groupBy(_._1)
println(rdd11_1.collect().toBuffer)//ArrayBuffer((tom,CompactBuffer((tom,1), (tom,2))), (dog,CompactBuffer((dog,10))), (jerry,CompactBuffer((jerry,3), (jerry,2))), (kitty,CompactBuffer((kitty,2))))
//按照key进行分组,并且可以指定分区
val rdd11_1_1: RDD[(String, Iterable[Int])] = rdd11.groupByKey
println(rdd11_1_1.collect.toList)//List((tom,CompactBuffer(1, 2)), (dog,CompactBuffer(10)), (jerry,CompactBuffer(3, 2)), (kitty,CompactBuffer(2)))
//cogroup合并数据并根据相同key进行排序
//cogroup和groupBykey的区别
//cogroup输入的数据必须是(k,v)和另外一个(k,w)得到一个(k,(seq[v],seq[w]))的数据集
//groupByKey: 进行对已经合并好的数据根据相同key进行分组,得到一个(k,seq[v])
//分组的话要提供二元组(k,v)
val rdd12_1 = sc.parallelize(List(("tom",1),("jerry",3),("kitty",2)))
val rdd12_2 = sc.parallelize(List(("jerry",2),("tom",2),("dog",10)))
val rdd12_3: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd12_1.cogroup(rdd12_2)
println(rdd12_3.collect.toBuffer)//ArrayBuffer((tom,(CompactBuffer(1),CompactBuffer(2))), (dog,(CompactBuffer(),CompactBuffer(10))), (jerry,(CompactBuffer(3),CompactBuffer(2))), (kitty,(CompactBuffer(2),CompactBuffer())))
//相同key的value进行计算
val rdd13_2 = rdd11.reduceByKey((x,y) => x+y)
println(rdd13_2.collect.toList)//List((tom,3), (dog,10), (jerry,5), (kitty,2))
//求和
val rdd14 = sc.parallelize(List(1,2,3,4,5,6))
val rdd14_1 = rdd14.reduce(_+_)
println(rdd14_1)
//需求:List(("tom",1),("jerrry",3),("kitty",2)) List(("jerry",2),("tom",2),("dog",10))
//需要先合并数据,按照key进行聚合,根据key进行排序(根据数值而非字符串)
val rdd15_1 = sc.parallelize(List(("tom",1),("jerry",3),("kitty",2)))
val rdd15_2 = sc.parallelize(List(("jerry",2),("tom",2),("dog",10)))
//合并数据
val rdd15_3 = rdd15_1 union rdd15_2
//聚合
val rdd15_4: RDD[(String, Int)] = rdd15_3.reduceByKey(_+_)
//排序 sortBy
//现在数据key是String类型,后面Int类型进行排序
val rdd15_5: RDD[(Int, String)] = rdd15_4.map(t => (t._2,t._1))
val rdd15_6: RDD[(Int, String)] = rdd15_5.sortByKey(false)
val rdd15_7: RDD[(String, Int)] = rdd15_6.map(t => (t._2,t._1))
println(rdd15_7.collect.toBuffer)
//笛卡尔积
val rdd16: RDD[((String, Int), (String, Int))] = rdd15_1 cartesian rdd15_2
val rdd17 = sc.parallelize(List(2,5,1,62,7,3,267))
println(rdd17.count())//数据个数
println(rdd17.top(3).toBuffer)//取值 默认会降序排序,若输入0,会返回一个空数组
println(rdd17.take(3).toBuffer)//取值,去除对应数量的数值
println(rdd17.takeOrdered(3).toBuffer)//取值会进行排序,默认是升序,返回对等数量的数据
println(rdd17.first())//取出第一个值
}
}
详细说明groupByKey和reduceByKey
1.groupByKey:groupBykey会对每一个RDD中的value值进行聚合形成一个序列,此操作发生在reduce端之前,所以势必会将所有的数据通过网络传输,造成不必要的浪费 数据量十分巨大,可以造成OOM
2.reduceByKey: 会在结果发送给reduce之前进行一次合并计算,相同key所有的value相加,这个操作类似于MR中的Combiner,这样做的好处在于,map端会进行一次reduce,会减少数据量,从而减少传输,可以保证后期reduce更高效的计算。
建议:在大量数据进行计算的时候建议使用reduceByKey,不建议用groupByKey
高阶算子
1.PlusOperatorDemo1.scala
/**
* 高阶算子
*/
object PlusOperatorDemo1 {
//自定义打印方法
def printlns[T](rdd:RDD[T]):Unit = {
println(rdd.collect.toBuffer)
}
def fun1[T](index:Int,iter:Iterator[(T)]): Iterator[String]={
iter.map(x => "[partID: "+index+" value: "+x+"]")
}
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("PlusOperatorDemo1").setMaster("local")
val sc = new SparkContext(conf)
//1.遍历出集合中的每一个元素
//并行化生成RDD
val rdd1: RDD[Int] = sc.parallelize(List(1,2,3,4,5,6),3)
val rdd2: RDD[Int] = rdd1.map(_ * 10)
printlns(rdd2) //ArrayBuffer(10, 20, 30, 40, 50, 60)
//mapPartion是对每个分区中的数据进行迭代
/**
* 第一个参数是第一个迭代器对象,
* 第二个参数表示是否保留父RDD的partition分区信息
* 第二个参数的默认值是: false ,一般是不修改(父RDD就需要用到宽窄依赖的问题)
* (f: Iterator[T] => Iterator[U],preserve Partitioning:Boolean = false)
* ps:第一个 _ 是迭代器对象,第二个_ 是分区(集合)中数据
* map和mapPartitions的区别:
* map是对RDD中的每个元素进行操作
* mapPartitions则是对RDD中的每个分区中的迭代器进行操作
*/
val rdd3: RDD[Int] = rdd1.mapPartitions(_.map(_ * 10))
/**
*如果RDD数量不大,建议采用mapPartitions算子代替map算子,可以加快数据量的处理数据
* 但是如果RDD中数据量过大比如10亿条,不建议使用apPartitions今次那个数据遍历,可能出现OOM,内存溢出错误
*
*/
//mapWith:是对RDD中每个元素进行操作(是map方法的另外一种方法)
//计算每一个分区中的值
//(constructA: Int) => A,preservesPartitioning: Boolean = false)(f:(T,A)=>U)
//(1,2,3,4,5,6)
//参数是柯里化
/**
* 第一个参数是分区号(分区号是从0开始的)
* 第二个参数是否保留父RDD的partition分析信息
* 第二个参数T分区中的每一个元素 A是第一个柯里化参数中第一个参数得到的结果
* 柯里化第一个参数是分区号逻辑 柯里化第二个参数实际对应分区中元素的处理逻辑
* 这个方法已经过时
*/
println(rdd1.mapPartitionsWithIndex(fun1).collect().toBuffer)
val rdd4: RDD[Int] = rdd1.mapWith(i =>
{
println(i)
i * 10
})((a, b) => {
println("a = "+a)
//(constructA: Int) => A,preservesPartitioning: Boolean = false)(f:(T,A)=>U)
println("b = "+b) //其实是第一个柯里化参数处理的结果,b = i * 10---b 就是A
println("a + b + 2 ="+(a+b +2))
println("-------------------------------------")
a+b+2
})
printlns(rdd4) //ArrayBuffer(3, 4, 15, 16, 27, 28)
/***结果:
* ArrayBuffer([partID: 0 value: 1], [partID: 0 value: 2], [partID: 1 value: 3], [partID: 1 value: 4], [partID: 2 value: 5], [partID: 2 value: 6])
* 0
* a = 1
* b = 0
* a + b + 2 =3
* -------------------------------------
* a = 2
* b = 0
* a + b + 2 =4
* -------------------------------------
* 1
* a = 3
* b = 10
* a + b + 2 =15
* -------------------------------------
* a = 4
* b = 10
* a + b + 2 =16
* -------------------------------------
* 2
* a = 5
* b = 20
* a + b + 2 =27
* -------------------------------------
* a = 6
* b = 20
* a + b + 2 =28
* -------------------------------------
* ArrayBuffer(3, 4, 15, 16, 27, 28)
*/
/**
* flatMap是对rdd中每个元素进行操作,返回的结果是一个扁平化处理过后的
* (constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => Seq[U])
* 参数使用和上面是一样的,只不过最后的返回值还是一个序列
* 这个方法也过时
*
*/
println("------------------------rdd5-----------------------------")
println(rdd1.mapPartitionsWithIndex(fun1).collect().toBuffer)
val rdd5: RDD[Int] = rdd1.flatMapWith(i =>{
println("i = "+i)
i
})((x, y) => {
println("x = "+x)
println("y = "+ y)
println("-------------------------------------")
List(y,x)
})
rdd1.flatMapWith(i => i)((x,y) =>List(y,x))
printlns(rdd5)
/**
* 结果:
* i = 0
* x = 1
* y = 0
* -------------------------------------
* x = 2
* y = 0
* -------------------------------------
* i = 1
* x = 3
* y = 1
* -------------------------------------
* x = 4
* y = 1
* -------------------------------------
* i = 2
* x = 5
* y = 2
* -------------------------------------
* x = 6
* y = 2
* -------------------------------------
*/
/**
* mapPartitionsWithIndex 是对rdd中每个分区的遍历操作
* 第一个参数是一个柯里化
* 第二参数是一个隐式转换
* 函数的作用和mapPartitions是类似的,不过要提供两个采纳数,
* 第一个参数是分区号
* 第二个参数是分区中元素存储到Iterator中(就可以操作这个Iterator)
* 第三个参数是 是否保留RDD的partition
*/
val func = (index:Int,iter:Iterator[(Int)]) => {
iter.map(x => "[partID: "+index+",value: "+x+ "]")
}
val rdd6: RDD[String] = rdd1.mapPartitionsWithIndex(func)
println(rdd6.collect().toBuffer)
//结果:ArrayBuffer([partID: 0,value: 1], [partID: 0,value: 2], [partID: 1,value: 3], [partID: 1,value: 4], [partID: 2,value: 5], [partID: 2,value: 6])
/**
* Aggregate算子:聚合
* Aggregate函数将每个分区里面的元素进行聚合,然后用ombine函数将每个分区的结果和初始值进行combine操作
* ps:combine-->执行reduce,这个函数的最终返回值类型不要和RDD中元素的类型一致
* zeroValue--初始值(默认值) seqOP局部聚合(分区的),comOP(全局聚合)
* (zeroValue: U) (seqOP:(U,T)) (comOP:(U,U) => U)
*/
val rdd7 = sc.parallelize(List(1,2,3,4,5,6,7,8,9),2)
println(rdd7.mapPartitionsWithIndex(fun1).collect().toBuffer)
/**
* zeroValue是初始值
* 初始值会先和分区中的数据进行比较计算,最终全局聚合的时候,每个分区相加,然后再加上默认值
* ps:初始值会跟分区进行局部聚合(分区计算)
* 初始值会跟全局聚合在一起进行计算
* aggregate是action类型的算子
* 初始值会参与运算,刚开始选最大值的时候参与,到开始聚合运算的时候也参与
*/
val sumed: Int = rdd1.aggregate(0)(math.max(_,_),_+_)
}
}
2.AggregateDemo.scala
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Aggregate算子:聚合
* Aggregate函数将每个分区里面的元素进行聚合,然后用combine函数将每个分区的结果和初始值进行combine操作
* ps:combine--->执行以下reduce,这个函数最终的返回值类型不要和RDD中元素的类型一致
* zoroValue是初始值(默认值) seqOP局部聚合(分区的),comOP全局聚合
* (zoroValue : U) (seqOP :(U, T)) (comOP:(U,U)=>U)
*/
object AggregateDemo {
def fun1[T](index:Int,iter:Iterator[(T)]): Iterator[String]={
iter.map(x => "[partID: "+index+" value: "+x+"]")
}
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("AggregateDemo").setMaster("local[2]")
val sc = new SparkContext(conf)
val rdd1 = sc.parallelize(List(1,2,3,4,5,5,6,7,8,9),2)
println(rdd1.mapPartitionsWithIndex(fun1).collect().toBuffer)
//zeroValue是初始值(默认值)
/**
* 初始值会先和分区中的数据进行比较计算,最终全局聚合的时候每个分区值相加然后再加上默认值
* ps:初始值会跟分区进行局部聚合(分区计算)
* 初始值会跟全局聚合在一起进行计算
* aggregate是action类型的算子
* 初始值会参与运算,刚开始选最大值的时候参与,到开始聚合运算的时候也参与
*/
val sumed = rdd1.aggregate(0)(math.max(_,_),_+_)
println(sumed)//14
//(math.max(_,_),_+_)
/**
* 初始值也参加运算--1 2 3 4 5 | 5 6 7 8 9
* 局部聚合的时候参与了运算,全局操作也参与计算
* math.max(_,_)
* 刚开始第一个 _ 代表初始值,
* 第二个 _ 代表第一个分区里面的第一个元素
* 然后进行比较,得到最大值
* 并将最大值放在第一个_那里面,
* 分区中的下一个元素放在 第二个下划线
* ,_+_)
* 将在每个分区中取到的最大值进行聚合
*/
val sumed2 = rdd1.aggregate(5)(math.max(_,_),_+_)
println(sumed2)//5 + 5 +9 = 19
//abcdef 或者defabc 为什么?
/**
* aggregare是先计算分区的值,并行化处理的情况
* 两个分区可能出现两个线程在跑,哪个分区先完成不一定,所以就会出现
* 谁先执行完,谁就在前面,剩下的在后面
*/
val rdd2: RDD[String] = sc.parallelize(List("a","b","c","d","e","f"),2)
println(rdd2.mapPartitionsWithIndex(fun1).collect().toBuffer)
//初始值先分别和分区中的元素进行聚合运算,然后再全局聚合的时候,又再次参与聚合运算
val str: String = rdd2.aggregate("")(_+_,_+_)
println(str)
/**
* 初始值在进行局部聚合的时候,会和分区中的值进行一次计算,
* 所有分区计算完成后,会在全局聚合的时候再子进行一次计算
*/
val str1: String = rdd2.aggregate("=")(_+_,_+_)
println(str1)
val rdd3: RDD[String] = sc.parallelize(List("12","34","345","4567"),2)
println(rdd3.mapPartitionsWithIndex(fun1).collect().toBuffer)
val str2: String = rdd3.aggregate("")((x, y) => math.max(x.length,y.length).toString, (x, y)=>x+y)
println(str2) //24 42
val rdd4: RDD[String] = sc.parallelize(List("12","34","345",""),2)
println(rdd4.mapPartitionsWithIndex(fun1).collect().toBuffer)
val str3: String = rdd4.aggregate("")((x, y) => math.min(x.length,y.length).toString, (x, y)=>x+y)
println(str3)
println("".length.toString)//0
println("".length.toString.length)//1
val rdd5: RDD[String] = sc.parallelize(List("12","34","","345"),2)
println(rdd5.mapPartitionsWithIndex(fun1).collect().toBuffer)
val str4: String = rdd5.aggregate("")((x, y) => math.min(x.length,y.length).toString, (x, y)=>x+y)
println(str4)
//AggregateByKey
//相同key中的值进行聚合操作,通过aggregateByKey函数最终返回的类型还是RDD(PairRDD)
val rdd6: RDD[(String, Int)] = sc.parallelize(List(("cat",2),("cat",5),("dog",4),("dog",3),("pig",10),("cat",5)),2)
println("-------------------------------------------")
println(rdd6.mapPartitionsWithIndex(fun1).collect().toBuffer)
//AggregateNyKey对应的是二元组的计算,使用方式和Aggregate没有太大区别
//初始值 分区聚合(局部) 全局聚合 比较最大值,然后用最大值求和
//先计算对应分区的值,然后再全局聚合
//ps:因为第一次给的数值每个分区中是没有相同key,所有都是最大值,所有就相当于值相加了
//第二次将同一个分区中的key有相同
//首先会根据相同key进行计算,以cat为例子先会和初始值计算,比较留下最大值
//然后等待第二分区完成计算,然后再进行全局聚合
val value: RDD[(String, Int)] = rdd6.aggregateByKey(0)(math.max(_,_),_+_)
println(value.collect().toBuffer)
}
}
3.combineByKey.scala
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
//根据相同key进行聚合
//createCombiner: V => C,
// mergeValue: (C, V) => C,
// mergeCombiners: (C, C) => C)
/**
* 第一个参数是:遍历集合中value的值,V 和 C 的数据类型:由value的数据类型决定
* 相同与根据key生成一个类型 key [多value]的集合 hello[1,1,1,1,1,1,1,1,1,1]
*
* 第二个参数 局部聚合
* c 的数据类型时第一个参数返回值决定
* v 的数据类型时第一个参数的,数据类型决定
* ps: 若 key [多个value]集合,进行局部聚合
*
* 第三个参数 全局聚合
* 对第二个函数中每个分区操作产生的结果 再次进行聚合
* C 的数据类型时第二个函数得到的数据类型 最终聚合
*
*/
object combineByKey {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("AggregateDemo").setMaster("local")
val sc = new SparkContext(conf)
//计算单词的个数:reduceByKey,groupByKey,aggregateByKey,combineBykey
val rdd1: RDD[(String, Int)] = sc.textFile("dir/file.txt").flatMap(_.split(" ")).map((_,1))
//下划线不行,就写变量,写变量不行就加数据类型
//x :当前去的所有值
val rdd2: RDD[(String, Int)] = rdd1.combineByKey(x => x, (a:Int, b:Int)=>{
println("a = "+a)
println("b = "+b)
println("a+b = "+(a+b))
println("--------------------")
a+b
}, (m:Int, n:Int)=>{
println("m = "+m)
println("n = "+n)
println("m+n = "+(m+n))
println("------------------------")
m+n
})
println(rdd2.collect().toBuffer)
val rdd3: RDD[(String, Int)] = rdd1.combineByKey(x => x+10, (a:Int, b:Int)=>a+b, (m:Int, n:Int)=> m+n)
println(rdd3.collect().toBuffer)
val rdd4: RDD[String] = sc.parallelize(List("tom","jerry","kitty","cat","dog","pig","bird","bee","wolf"),3)
val rdd5: RDD[Int] = sc.parallelize(List(1,1,2,2,2,1,2,2,2),3)
val rdd6:RDD[(Int,String)]= rdd5.zip(rdd4)
//需求key 是1的放在一起,key是2的放在一起
//第一个参数是获取value的值
val rdd7= rdd6.combineByKey(List(_),(x:List[String],y:String)=>x :+ y,(m:List[String],n:List[String])=> m++ n)
println(rdd7.collect().toBuffer)
}
}
4.OtherOperaterdDemo.scala
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
object OtherOperaterdDemo {
def fun1[T](index:Int,iter:Iterator[(T)]): Iterator[String]={
iter.map(x => "[partID: "+index+" value: "+x+"]")
}
def main(args: Array[String]): Unit = {
//countByKey 属于action类型的算子 统计key的个数
val conf = new SparkConf().setAppName("OtherOperaterdDemo").setMaster("local[2]")
val sc = new SparkContext(conf)
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a",1),("b",1),("a",1)))
val key: collection.Map[String, Long] = rdd1.countByKey()
println(key)
val value: collection.Map[(String, Int), Long] = rdd1.countByValue()
//统计value的个数,但是会将集合中得到的一个元素看做是一个value
println(value)
//filterByRange: 对RDD中的元素进行过滤,返回指定范围内的数据
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("e",5),("c",3),("d",4),("c",2),("a",1)))
val rdd2_1: RDD[(String, Int)] = rdd2.filterByRange("c","e")
println(rdd2_1.collect().toList)
//flatMapValues对参数进行扁平化操作,是value的值
val rdd3: RDD[(String, String)] = sc.parallelize(List(("a","1 2"),("b","3 4")))
println(rdd3.flatMapValues(_.split(" ")).collect().toList)
//foldByKey根据相同key进行聚合
val rdd4: RDD[(String, Int)] = sc.textFile("dir/file.txt").flatMap(_.split(" ")).map((_,1)).foldByKey(0)(_+_)
println(rdd4.collect().toBuffer)
//foreachartition 循环的是分区数据
//3个分区 一个3个数
val rdd5 = sc.parallelize(List(1,2,3,4,5,6,7,8,9),3)
rdd5.foreachPartition(x => println(x.toList))
rdd5.foreachPartition(x =>println(x.reduce(_+_)))// 6 15 24
//foreachPartition 一般应用于数据的持久化,存入 数据库,可以进行分区的数据存储
//KeyBy 以传入的函数返回值作为key RDD 中的元素为Value元素的元组
val rdd6: RDD[String] = sc.parallelize(List("dog","cat","pig","wolf","bee"),3)
val rdd6_1: RDD[(Int, String)] = rdd6.keyBy(_.length)
println(rdd6_1.collect.toList)
//Keys获取所有的key ,values 获取所有的value
println(rdd6_1.keys.collect().toList)
println(rdd6_1.values.collect().toList)
//collectAsMap 将需要的二元组成Map
val map: collection.Map[String, Int] = rdd2.collectAsMap()
println(map)//去重
//重新分区算子: repatition coaesce partitionBy
val rdd7: RDD[Int] = sc.parallelize(1 to 10,4)
println(rdd7.partitions.length)
println(rdd7.mapPartitionsWithIndex(fun1).collect().toList)
//重新分区的时候,数据会进行Shuffle
//重新分区
val rdd7_1: RDD[Int] = rdd7.repartition(6)
println(rdd7_1.partitions.length)
//第二 参数是shuffle,默认不是false
//当前分区数大于原有分区数,若不Shuffle,不会进行修改,只有改变为true才会
//当前分区数小于原有分区数会直接分区,false不shuffle,true 可以shuffle
val rdd7_2 = rdd7.coalesce(6,true)
println(rdd7_2.partitions.length)
//partitionBy 必须是KV数据类型
val rdd7_3 = sc.parallelize(List(("e",5),("c",3),("d",4),("c",2),("a",1)))
//可以传入自定义分区器,也可以传入默认分区器 HashPartitioner
val rdd7_4: RDD[(String, Int)] = rdd7_3.partitionBy(new HashPartitioner(4))
println(rdd7_4.partitions.length)
//Checkpoint
/**
* 检查点,类似于快照,checkpoint的作用就是将DAG中的数据中比较重要的数据做一个检查点,将结果存储到一个高可用的地方
*
*/
//1.指定存储目录
sc.setCheckpointDir("hdfs://hadoop01:8020/ck")
//检查点的触发一定要使用action类型的算子
val rdd8: RDD[(String, Int)] = sc.textFile("hdfs://hadoop1:8020/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
//检查点的触发一定要使用这个action算子
rdd8.checkpoint()
rdd8.saveAsTextFile("hdfs://hadoop1:8020/out10")
println(rdd8.getCheckpointFile)//查看存储的位置
//查看是否可以设置检查点,rdd8.isCheckpointinted
}
}