进阶算子:
import org.apache.spark.rdd.RDD import org.apache.spark.{HashPartitioner, SparkConf, SparkContext} /** * 进阶算子 */ object FunctionDemo2 { def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setAppName("CreateRDD").setMaster("local") val sc = new SparkContext(conf) //1.遍历RDD获取RDD中每一个分区中元素的值并进行计算,然后返回一个新的RDD val rdd1 = sc.makeRDD(List(1,2,3,4,5,6),3) /* ps: RDD = 1,2,3,4,5,6 --> 分了3个区 --> (1,2)分区1 (3,4)分区2 (5,6)分区3 f: Iterator[T] => Iterator[U], 第一个参数是一个迭代器对象,这个迭代器对象获取的是对应分区中值 preservesPartitioning: Boolean = false 第二参数是否保留父RDD的Partition分区信息,会使用默认值(这个作用会关系宽窄依赖) 如果RDD的数据量不大,而且存在分区,建议使用mapPartition算子代替map算子,可以加快对数据的处速度 如果RDD中数量过于庞大例如10亿条,不建议使用mapPartition,因为出现oom ps: 一条日志数据大小 1KB~2KB左右 --> 可以估算数据量 */ //第一个下划线是迭代器对象 第二个下划线是迭代器对象中存储的值 val rdd2: RDD[Int] = rdd1.mapPartitions(_.map(_*10)) println(rdd2.collect.toList) //对RDD中每个分区中数据进行遍历操作(可以打印 或 可以计算) //写一个迭代方法或函数 执行迭代操作 /* f: (Int, Iterator[T]) => Iterator[U], 第一采参数是操作当前分区数据的函数,元组汇总第一个参数是分区序号 第二个参数是分区数据封装到的迭代器对象 preservesPartitioning: Boolean = false 第二参数是否保留父RDD的Partition分区信息,会使用默认值(这个作用会关系宽窄依赖) */ val Iter =(index:Int,iter:Iterator[(Int)])=>{ iter.map(x => "[partID:"+index+" value:"+x+"]") } val rdd3:RDD[String] = rdd1.mapPartitionsWithIndex(Iter) println(rdd3.collect.toList) //1.排序 //1.1 sortByKey: //根据key值进行排序,但是key必须实现Ordered接口,必须是一个对偶元组 //这个算子有第二参数,第二个参数是boolean值决定了 是升序true 还是降序 false //默认是true val rdd4 = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd"))) val sorted: RDD[(Int, String)] = rdd4.sortByKey() println(sorted.collect.toList) //1.2 sortBy: //与sortByKey类似,不同点在于sortBy可以根据传入的参数决定谁来排序 //第一个参数就是 根据谁排序 //第二个参数就是 决定升序还是降序 是升序true 还是降序false 默认是true val rdd5 = sc.parallelize(List(23,432,123,5234,5423,5413,23,623,623,6423,623,25)) val sorted_1: RDD[Int] = rdd5.sortBy(x => x,false) println(sorted_1.collect.toList) //2.重新分区 /* 一般在计算阶段即转换阶段轻易不会分区,在触发job,才会对分区进行一些修改(控制输出) 如果在计算修改分区,绝对不会在发生shuffle的位置上修改的,而且不要频繁修改分区(只要修改分区就会触发shuffle) */ val rdd6 = sc.parallelize(List(1,2,3,4,5,6),3) //2.1 partitions: //通过partitions这个算子触发RDD中分区存储到一个数组中,然后获取数据长度即当前RDD分区的个数 println("初始分区值"+rdd6.partitions.length) //2.2 repartition:更改分区repartition可以更改分区的数量 --> 会发生shuffle val rdd6_1 = rdd6.repartition(1)//缩小分区 println("修改分区个数为:"+rdd6_1.partitions.length) val rdd6_2 = rdd6.repartition(6)//扩大分区 println("修改分区个数为:"+rdd6_2.partitions.length) //2.3 coalesce:允许发生将分区个数修改为小值,但是不允许将分区个数为大值 //ps:coalesce 算子默认shuffle是false 不会发生shuffle,所以不能修改为大的分区值 //一定要使用这个算子修改分区,建议可以开启shuffle但是没有repartition好用 //这个算子是repartition底层实现 默认开启shuffle val rdd7_1 = rdd6.coalesce(1) //缩小分区是可以 println("修改分区个数为:"+rdd7_1.partitions.length) val rdd7_2 = rdd6.coalesce(6) //扩大分区是不可以 println("修改分区个数为:"+rdd7_2.partitions.length) //2.4 HashPartitioner val rdd8 = sc.parallelize(List(("e",5),("c",3),("d",4),("a",2),("b",1)),2) //更改分区,这个算子是需要传入自定分区器的,因为还没有学习自顶分区,所以这里可以使用一个万能分区 HashPartitioner val rdd8_1 = rdd8.partitionBy(new HashPartitioner(4)) println(rdd8_1.partitions.length) //更改分区 repartitionAndSortWithinPartitions 是repartition的一个变种,这个算子相当于集合了分区和排序 //这个算子只能对 对偶元组使用 --> 对偶元组(二元组),会根据key的值进行排序, //如果需要在repartition之后再进行排序,此时就可以使用这个算子代替 //要去传入的是一个自定义分区器即可 rdd8.repartitionAndSortWithinPartitions(new HashPartitioner(1)).foreach(println) //3.求和 //Spark中是很少使用Scala中求和方法sum reduce fold //Scala中的reduce方法在Spark中是action算子 //3.1.根据相同key为一组进行求和(必须要有key) val rdd9 = sc.parallelize(Array(("tom",1),("jerry",3),("kitty",2),("jerry",2),("tom",2),("dog",10))) //返回值是一个全新的RDD,并且这个RDD中存储的是元组, key是原来的key value值时key所有value值的和 val rdd9_1: RDD[(String, Int)] = rdd9.reduceByKey(_+_) println(rdd9_1.collect.toList) //3.2 /* aggregateByKey(zeroValue: U)(seqOp: (U, V) => U,combOp: (U, U) => U) 根据相同key 为一组计算value的值 在kv对的RDD中,按照key将value进行分区合并,合并完成后在进行全局合并 计算流程: 1.先做每个分区内数据的计算,zeroValue会参与到每个分区的计算 2.zeroValue会和seqOP的逻辑进行计算,将聚合的结果会传递到combOp这个计算逻辑中也是根据相同key一组进行全局聚合 zeroValue 是初始值(默认值) seqOP 局部聚合(分区内) combOp 全局聚合(聚合分区计算的值) */ val rdd10 = sc.parallelize(List(("cat",2),("cat",5),("pig",10),("dog",3),("dog",4),("cat",4)),2)
/**
* combineByKey 根据key进行求和计算看相同key为一组计算 分区值 ,然后在计算全局的值(分区计算值+分区计算值)
* createCombiner: V => C,遍历分区中所有元素,因此每个元素的key要么是还没有遇到,要么就是已经遇到
* 如果这个key是第一次出现,此时会调用createCombiner的函数来创建这各key的对应累加初始值
* mergeValue: (C, V) => C, 如果这各key在当前分区中遇到多个(多个相同key),它会使用mergeValue这各函数通过之前的累加初始值
* 和其他相同key的value进行求和计算 [分区求和]
* mergeCombiners: (C, C) => C 由于每个分区独立的,因此对于同一个key可以有多个累加器(不用分区内),如果有两个获取多个都有key累加器
* 就使用mergeCombiners函数进行分区结果聚合 [全局聚合]
*/
val rdd11= sc.parallelize(List(("cat",2),("cat",5),("pig",10), ("dog",3),("dog",4),("cat",4)),2)
//这个算子不能使用下划线
val rdd11_1 = rdd11.combineByKey(x=>x,(a:Int,b:Int)=>a+b,(m:Int,n:Int)=>m+n)
} }