一 Transformation算子
Transformation : 即转换算子 ,调用转换算子会生成一个新的RDD ,Transformation 是 Lazy 的,不会触发 job 执行 .
基本上分为两类 :
1) 不产生shuffle的算子(窄依赖)
2) 产生shuffle的算子(宽依赖)
二 对RDD的操作(创建,查看)
1 创建 RDD 的方法
1.1 通过并行化方法 ,将 Driver 端的集合转成 RDD
将Driver端的scala集合并行化成RDD,RDD中并没有真正要计算的数据,只是记录以后从集合中的哪些位置获取数据
val rdd1:RDD[Int] = sc.parallelize(Array(1,2,3,4,5,6,7),2) ---这里的分区数指定是 2
1.2 从 HDFS 指定的目录创建RDD
指定以后从哪里读取创建RDD,可以是多种文件系统,需要指定文件系统的协议,如hdfs://,flile://,s3://等
val lines:RDD[String] = sc.textFile("hdfs://linux04:8020/log" ,2) --- 2 可以省略不写 ,默认就是 2
log : 是linux04节点上 hdfs 中跟目录下的一个文件夹
8020 : 是linux04 节点hdfs 的端口号
注意 : 使用上面两种方式创建 RDD 都可以指定分区的数量.
1.3 使用 makeRDD 方法创建RDD
将Driver端的scala集合并行化成RDD,RDD中并没有真正要计算的数据,只是记录以后从集合中的哪些位置获取数据
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val arr = List(1,2,3,4)
val listRDD: RDD[Int] = sc.makeRDD(arr, 2)
--index 表示分区的索引 ;it 表示对应分区的迭代器,一个分区的迭代器抓取该分区的一条条数据
val result: Array[String] = listRDD.mapPartitionsWithIndex((index, it) => {
it.map(li => s"分区号为 :$index ,对应的迭代器为 :$li")
}).collect()
result.foreach(println)
结果为 : 分区号为 :0 ,对应的迭代器为 :1 分区号为 :0 ,对应的迭代器为 :2
分区号为 :1 ,对应的迭代器为 :3 分区号为 :1 ,对应的迭代器为 :4
2 查看RDD 的数量
val rdd1:RDD[Int] = sc.parallelize(Array(1,2,3,4,5,6,7),2)
rdd1.partitions.length
三 RDD 常用的 Transformation 算子
1 不产生shuffle的算子(窄依赖)
1.1 map 算子 ,功能是做映射
val rdd1:RDD[Int] = sc.parallelize(List(3,4,5,6,2,4,1,10)).map(_*2)
List集合中的每个元素都*2 ,然后返回一个新的RDD
1.2 flatMap 算子 ,先 map 再压平展开 ,spark 中没有 flatten 方法
1) val rdd2 = sc.parallelize(Array("a b c"," d e f", "h i j"))
val strings: Array[String] = rdd2.flatMap(_.split("\\s+")).collect()
println(strings.toBuffer) 结果为 : ArrayBuffer(a, b, c, d, e, f, h, i, j)
2) val rdd3 = sc.parallelize(List(List("a b c","d e g"),List("c f g"," i h j")))
val result: Array[String] = rdd3.flatMap(_.flatMap(_.split("\\s+"))).collect()
println(result.toBuffer) 结果为 : ArrayBuffer(A, B, C, D, E, F, a, b, c, d, e)
1.3 filter算子 ,功能为过滤数据
val rdd4: RDD[Int] = sc.parallelize(List(3, 4, 6, 5, 2, 7, 8, 9, 10))
val result: Array[Int] = rdd4.filter(_ % 2 == 0).collect()
println(result.toBuffer) ---ArrayBuffer(4, 6, 2, 8, 10)
1.4 mapPartition ,将数据以分区的形式返回进行map操作 ,一个分区对应一个迭代器 ,该方法和map方法类似 ,只不过该方法的参数由RDD中的每一个元素变成了RDD中的每一个分区的迭代器 ,如果在映射的过程中需要频繁创建额外的对象 ,使用 mapPartition要比map高效的多 .(map针对的是每一个元素 ,mapPartition针对的是每一个分区的迭代器)
object mapPartition {
def main(args: Array[String]): Unit = {
---获取conf
val conf = new SparkConf().setAppName("mapPartition").setMaster("local[*]")
---创建 sparkContext
val sc = new SparkContext(conf)
---使用 sc ,创建原始的RDD
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5), 2)
val partitions: RDD[Int] = rdd1.mapPartitions(it => it.map(x => x * 10))
val result: Array[Int] = partitions.collect()
println(result.toBuffer) ---ArrayBuffer(10, 20, 30, 40, 50)
}
}
object mapPartition {
def main(args: Array[String]): Unit = {
---获取conf
val conf = new SparkConf().setAppName("mapPartition").setMaster("local[*]")
---创建 sparkContext
val sc = new SparkContext(conf)
---使用sc ,创建一个原始的RDD ,不使用默认的2个分区 ,而是手动设置3个分区
val lines: RDD[String] = sc.textFile(args(0),3)
---将读取的元素遍历然后转大写
val upper: RDD[String] = lines.map(_.toUpperCase)
---将转大写的元素保存到指定 C:\Users\123\Desktop\spark\out1 目录下
upper.saveAsTextFile(args(1))
---释放资源
sc.stop()
}
}
C:\Users\123\Desktop\spark\out1 目录下的结果为 :
part-00000 ERROR SPARK FLINK / HIVE HBASE SPRING
part-00001 ERROR SPARK FLINK HBASE SPRING
part-00002 HBASE SPRING
可以看到 ,处理的结果被分在三个区中保存
1.4 mapPartitionsWithIndex ,类似于 mapPartitions 以分区为单位进行操作, 一个分区就是一个迭代器 ,并同时获取分区的编号 ;不过函数要输入两个参数 ,第一个参数为分区的索引, 第二个参数是对应分区的迭代器 .函数的返回的是一个经过函数转换的迭代器 .
object MapPartitionsWithIndex01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val listRDD: RDD[Int] = sc.parallelize(List(1,2,3,4))
val result: Array[String] = listRDD.mapPartitionsWithIndex((index, it) => {
--index 表示分区的索引 ;it 表示对应分区的迭代器,一个分区的迭代器抓取该分区的一条条数据
it.map(li => s"partition :$index , val :$li")
}).collect()
println(result.toBuffer)
--结果为 :ArrayBuffer(partition :0 , val :1, partition :0 , val :2,
-- partition :1 , val :3, partition :1 , val :4)
}
}
1.5 union (将两个RDD合并成一个RDD,生成的RDD分区的数量是原来两个RDD分区数量之和)
object Union01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
--创建两个RDD
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3), 2)
val rdd2: RDD[Int] = sc.parallelize(List(4, 5, 6), 3)
--将两个RDD合并成一个RDD ,并且将两个RDD的分区数量相加
val rdd3: RDD[Int] = rdd1.union(rdd2)
println(rdd3.collect().toBuffer) --- ArrayBuffer(1, 2, 3, 4, 5, 6)
println(rdd3.partitions.length) --- 5
}
}
1.6 keys 和 values 和 mapValues
keys : Rdd中的数据类型为对偶元组,可以获取全部对偶元组中的第一个
values : Rdd中的数据类型为对偶元组,可以获取全部对偶元组中的第二个
mapValues : RDD中的数据类型为对偶元组 ,可以将全部对偶元组中的第二个取出来,然后对其进行处理
object KeysAndValues {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val tupleRDD: RDD[(String, Int)] = sc.parallelize(List(("a" -> 1), ("b" -> 2), ("c", 3)))
--keys方法 :将对偶元组的第一个元素都取出来
val keysRes: RDD[String] = tupleRDD.keys
println(keysRes.collect().toBuffer) -- ArrayBuffer(a, b, c)
--values方法 :将对偶元组的第二个元素都取出来
val valuesRes: RDD[Int] = tupleRDD.values
println(valuesRes.collect().toBuffer) -- ArrayBuffer(1, 2, 3)
--mapValues方法:将对偶元组的第二个元素取出来 ,可以对其进行处理,如下的 将每个元素乘以10
val mapValuesRes: RDD[(String, Int)] = tupleRDD.mapValues(f => f * 10)
println(mapValuesRes.collect().toBuffer) -- ArrayBuffer((a,10), (b,20), (c,30))
--不使用 mapValues 算子也可以实现该算子相同的功能
val result: RDD[(String, Int)] = tupleRDD.map(it => (it._1, it._2 * 10))
println(result.collect().toBuffer) --ArrayBuffer((a,10), (b,20), (c,30))
}
}
1.7 flatMapValues : 只对对偶元组的value值进行操作 ,然后将value值展平
object FlatMapValues01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val list1 = List(("A","1,2,3"),("B","3,4"),("C","6,7"))
val tupleRDD: RDD[(String, String)] = sc.parallelize(list1)
--flatMapValues :只对对偶元组的value值进行切割操作 ,然后将value值展平
val result: RDD[(String, String)] = tupleRDD.flatMapValues(it => it.split(","))
println(result.collect().toBuffer)
结果为 :ArrayBuffer((A,1), (A,2), (A,3),
(B,3), (B,4),
(C,6), (C,7))
--不使用flatMapValues算子 ,也实现该算子的功能
val result1: Array[(String, String)] = tupleRDD.flatMap(t => {
val key = t._1 --这里的key没有使用到 ,只是拿出来又装进去,所以这个方法不是很好
val numbers = t._2
numbers.split(",").map(it => (key, it))
}).collect()
println(result1.toBuffer) --ArrayBuffer((A,1), (A,2), (A,3), (B,3), (B,4), (C,6), (C,7))
}
}
2 产生shuffle的算子(宽依赖)
2.1 groupBy 和 groupByKey(将指定元素或者key相同的放在一起/一组,先局部放一起,再全局放一起)
groupBy : 根据RDD的某个指定的元素进行分组 ,相同的指定的元素会被分到一组,返回新的RDD
groupByKey : 根据RDD的key进行分组 ,key相同的会被分到一组 ,返回新的RDD
需求:按照统计单词出现的次数
1) 使用spark自带的groupByKey和 mapValues算子
2) 使用reduceByKey算子实现按key分组 ,相同key按照value聚合的功能
3) 不使用groupByKey ,而是使用combineByKey实现groupByKey的分组功能,然后在使用mapValues算子将key相同的value聚合
4) 不使用groupByKey ,而是使用自己new shuffleRDD 实现groupByKey的分组功能,然后在使用mapValues算子将key相同的value聚合
object GroupByKey02 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val lines = sc.textFile(args(0))
val wordAndOne: RDD[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1))
-----------第一种方法 : groupByKey + mapValues + sum 实现单词出现次数统计---------------
--将新获得的RDD按照key进行分组
val grouped: RDD[(String, Iterable[Int])] = wordAndOne.groupByKey()
val grouped1: RDD[(String, Iterable[Int])] = new PairRDDFunctions[String, Int](wordAndOne).groupByKey()
--分组后,将key相同的value聚合起来
val sum: RDD[(String, Int)] = grouped.mapValues(it => it.sum)
val sum1: RDD[(String, Int)] = grouped1.mapValues(it => it.sum)
--sum.collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
--sum1.collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
-----------第二种方法 : reduceByKey--------------------------------------------------
--以上groupByKey+mapValues 的方法,如果数据较多时,因为groupByKey没有先局部聚合,会有更多的shuffle
--处理数据时效率低,用reduceByKey效果会稍微好点 ,根据key分组,key相同的会被分到一组,然后按照value进行聚合
val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
--reduced.collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
-----------第三种方法 : combineByKey+ mapValues + sum----实现groupByKey相同的功能-----
--使用combineByKey实现groupByKey相同的功能
val f1 = (v: Int) => ArrayBuffer(v) --将key的value值放到数组里面
val f2 = (arr: ArrayBuffer[Int], v: Int) => arr += v --将key相同的value值添加到数组里面
val f3 = (arr: ArrayBuffer[Int], arr1: ArrayBuffer[Int]) => arr ++= arr1 --将key相同的两个数组合并起来
--这里使用的是ArrayBuffer ,没有使用CombineBuffer高效(因为是private的),因为ArrayBuffer中的数据都在磁盘里面了
val grouped2: RDD[(String, ArrayBuffer[Int])] = wordAndOne.combineByKey(f1, f2, f3, new HashPartitioner(wordAndOne.partitions.length), false)
--grouped2.mapValues(_.sum).collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
-----------第四种方法 : new ShuffleRDD---------实现groupByKey相同的功能-------------
--自己new ShuffleRDD 实现groupByKey相同的功能
val shuffledRDD: ShuffledRDD[String, Int, ArrayBuffer[Int]] = new ShuffledRDD[String, Int, ArrayBuffer[Int]](wordAndOne, new HashPartitioner(wordAndOne.partitions.length))
shuffledRDD.setAggregator(new Aggregator[String, Int, ArrayBuffer[Int]](f1, f2, f3))
shuffledRDD.setMapSideCombine(false)
shuffledRDD.mapValues(_.sum).collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
}
}
2.2 reduceByKey
根据RDD的key值进行分组 ,相同key的RDD被分到一组 ,按照value值先局部聚合相加,再全局聚合相加,底层调用的是combineByKeyWithClassTag,并在方法中中new的ShuffleRDD
object ReduceByKey01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val wordAndOne: RDD[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1))
---------第一种方法 : reduceByKey------------------
val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
reduced.collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
---------第二种方法 : 使用combineByKey实现与reduceByKey 相同的功能-------------
val f1 =(v:Int) => v --得到key的value值
val f2 =(v1:Int ,v2:Int) => v1 + v2 --相同的key, 其value值进行局部聚合/累加
val f3 =(v3:Int, v4:Int) => v3 + v4 --相同的key, 其value值进行全部聚合/累加
mapSideCombine :true ,指在map端进行聚合操作 ,但不影响最终reduce端聚合的结果
val reduced1: RDD[(String, Int)] = wordAndOne.combineByKey(f1, f2, f3, new HashPartitioner(wordAndOne.partitions.length), true)
reduced1.collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
--------第三种方法 : 自己new shuffleRDD 实现与reduceByKey相同的功能---------
val shuffleRDD: ShuffledRDD[String, Int, Int] = new ShuffledRDD[String, Int, Int](wordAndOne, new HashPartitioner(wordAndOne.partitions.length))
shuffleRDD.setAggregator(new Aggregator[String,Int,Int](f1,f2,f3))
shuffleRDD.setMapSideCombine(true)
shuffleRDD.collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
}
}
2.3 distinct : 去重,先调用map将数据变成K,V类型,Value是null,然后调用reduceByKey,先在每个分区进行局部去重,然后在全局去重
object Distinct01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 1, 1, 2, 4, 3, 5), 2)
--distinct : 去重的功能
val dis: RDD[Int] = rdd1.distinct()
println(dis.collect().toBuffer) --- ArrayBuffer(4, 2, 1, 3, 5)
--------不使用distinct算子 ,但是要实现去重的功能的方法--------------------
val numAndNull: RDD[(Int, Null)] = rdd1.map(i => (i, null))
--调用reduceByKey的方法聚合 ,将相同的元素聚合起来然后只取出一个
val reduced: RDD[(Int, Null)] = numAndNull.reduceByKey((a, _) => a)
--只要key
val distincted: RDD[Int] = reduced.map(_._1)
println(distincted.collect().toBuffer) --- ArrayBuffer(4, 2, 1, 3, 5)
}
}
2.4 aggregateByKey : 按照key进行聚合 , 指定一个初始值(初始值在局部聚合的时候使用 ,全局聚合的时候不再使用) ; 跟 reduceByKey 类似 , 可以输入两个参数 ,第一个函数是进行局部分区的操作/聚合 ,第二个函数是全局的数据操作/聚合
object AggregateByKey01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val list: List[(String, Int)] = List(("A",2),("B",3),("A",5),("C",6),("A",2),("B",3),("A",5),("C",6))
val listRDD: RDD[(String, Int)] = sc.parallelize(list, 2)
--aggregateByKey ,需要指定一个初始值 ,指定局部聚合函数和全局聚合函数 ;然后先局部聚合 ,再全局聚合 ;
--与reduceByKey效果一样
val reduced: RDD[(String, Int)] = listRDD.aggregateByKey(0)(_ + _, _ + _)
println(reduced.collect().toBuffer) --ArrayBuffer((B,6), (A,14), (C,12))
--取出局部的最大值 ,然后将每个局部的最大值再相加
val reduced1 = listRDD.aggregateByKey(0)(Math.max(_, _), _ + _)
println(reduced1.collect().toBuffer) --ArrayBuffer((B,6), (A,10), (C,12))
--aggregateByKey : 指定的初始值可以是任意数值 ,但初始值只在局部聚合的时候使用 ,全局聚合的时候就不会再使用了
val reduced2 = listRDD.aggregateByKey(100)(_ + _, _ + _)
--第一个区局部聚合 : A :(100+2)+(100+5)=207 B:100+3=103 C:100+6=106
--第二个区局部聚合 : A :(100+2)+(100+5)=207 B:100+3=103 C:100+6=106
--全局聚合 : A :207+207=214 B:103+103=206 C:106+106=212
println(reduced2.collect().toBuffer) --ArrayBuffer((B,206), (A,214), (C,212))
}
}
2.5 flodByKey : 先局部聚合 ,再全局聚合(底层调用的是combineByKeyWithClassTag, 并在方法中中new的ShuffleRDD)
object AggregateByKey01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val list: List[(String, Int)] = List(("A",2),("B",3),("A",5),("C",6),("A",2),("B",3),("A",5),("C",6))
val listRDD: RDD[(String, Int)] = sc.parallelize(list, 2)
--flodByKey :与reduceByKey 的功能相似
val reduced3: RDD[(String, Int)] = listRDD.foldByKey(0)(_ + _)
println(reduced3.collect().toBuffer) -- ArrayBuffer((B,6), (A,14), (C,12))
}
}
2.6 combineByKey :底层调用的是combineByKeyWithClassTag, 并在方法中中new的ShuffleRDD
需要输入三个参数 :
第一个参数为分组后 value 的第一个元素 ;
第二个函数为局部聚合函数 ;
第三个函数为全局聚合函数 .
import scala.collection.mutable.ArrayBuffer
object CombineByKey01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.parallelize(List("A", "B", "C", "D", "E", "F", "G", "H"),3)
val rdd2: RDD[Int] = sc.parallelize(List(1, 2, 1, 1, 1, 2, 2, 2), 3)
---拉链操作 ,将两个RDD两类关联 ,变成对偶元组(zip 操作的两个RDD的分区数必须要一致)-----
val ziped: RDD[(Int, String)] = rdd2.zip(rdd1)
println(ziped.collect().toBuffer) --ArrayBuffer((1,A), (2,B), (1,C), (1,D), (1,E), (2,F), (2,G), (2,H))
--使用combineByKey算子 ,将key相同的value收集起来
val f1 = (v:String) => ArrayBuffer(v)
val f2 = (arr:ArrayBuffer[String] ,v:String) => arr += v
val f3 = (arr1:ArrayBuffer[String] ,arr2:ArrayBuffer[String]) => arr1 ++= arr2
val reduced = ziped.combineByKey(f1, f2, f3)
println(reduced.collect().toBuffer)
--结果为 : ArrayBuffer((1,ArrayBuffer(A, C, D, E)), (2,ArrayBuffer(B, F, G, H)))
}
}
2.7 cogroup :协分组 ,跟fullOuterJoin有点类似 ,但是没有关联上的返回 CompactBuffer()
1) 可以将两个或者多个RDD合并到一起后再进行分组 ,要求是 : 合并的RDD的 key 数据类型必须要一致 ,value值的类型可以不一致
2) 可以自己cogroup自己 ,也可以cogroup多个RDD ,但一个RDD最多可以cogroup 3个RDD ,再多久只能在 cogroup一次
与group的区别 : group 只能对一个RDD进行分组
object CoGroup01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CoGroup01").setMaster("local[*]")
--创建 SparkContext
val sc = new SparkContext(conf)
--使用sc 创建原始的RDD
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 3), ("c", 2)), 2)
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("b", 2), ("c", 1)), 2)
--cogroup 是将两个或多个RDD合并到一起再进行分组[与group的区别是 ,group只能是对一个RDD进行分组,cogroup是对多个RDD进行分组]
val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
--cogroup 之后得到的是一个元组 ,相同的key被分到一组 ,value 是一个个迭代器 (抓取RDD 的value值),
val result: Array[(String, (Iterable[Int], Iterable[Int]))] = cogrouped.collect()
result.foreach(println(_)) --(b,(CompactBuffer(3),CompactBuffer(2))) (a,(CompactBuffer(1),CompactBuffer())) (c,(CompactBuffer(2),CompactBuffer(1)))
--cogroup实现Wordcount 功能,但是不推荐 ,因为效率低[多个RDD进行聚合到一起 ,先uinon ,再reduceByKey]
val wcRes: RDD[(String, Int)] = cogrouped.mapValues(t => (t._1.sum + t._2.sum))
wcRes.collect().foreach(println) --(b,5) (a,1) (c,3)
--不仅可以cogroup对两个RDD,还可以对多个RDD进行cogroup ,甚至可以自己cogroup自己---------
-------可以自己 cogroup 自己--------------
val cogrouped1: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd1)
cogrouped1.collect().foreach(println) //(b,(CompactBuffer(3),CompactBuffer(3))) (a,(CompactBuffer(1),CompactBuffer(1))) (c,(CompactBuffer(2),CompactBuffer(2)))
-----可以 cogroup 多个RDD ,但最多只能cogroup 3个RDD,在多只能再 cogroup 一次----------
--再建一个原始的RDD
val rdd3: RDD[(String, Int)] = sc.parallelize(List(("c", 7)), 2)
val cogrouped2: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2, rdd3)
cogrouped2.collect().foreach(println(_))
结果为 :
(b,(CompactBuffer(3),CompactBuffer(2),CompactBuffer()))
(a,(CompactBuffer(1),CompactBuffer(),CompactBuffer()))
(c,(CompactBuffer(2),CompactBuffer(1),CompactBuffer(7)))
--释放资源
sc.stop()
}
}
2.8 intersection (交集 : 取两个RDD 中都有的元素) 和 subtract (差集) 和 substractByKey (根据key取差集)
intersection : 交集 ,取两个RDD 中都有的元素
object Intersection01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("Intersection01").setMaster("local[*]")
val sc = new SparkContext(conf)
--创建原始的RDD
val rdd1: RDD[String] = sc.parallelize(List("a", "b", "c"), 2)
val rdd2: RDD[String] = sc.parallelize(List("a", "c", "f"), 2)
---取两个RDD的交集 ,并且有去重效果
val intersectioned: RDD[String] = rdd1.intersection(rdd2)
intersectioned.collect().foreach(println) -- a c
-----------不使用 intersection 方法也可以实现取交集的方法---------------
val rdd11: RDD[(String, Null)] = rdd1.map(s => (s, null))
val rdd22: RDD[(String, Null)] = rdd2.map(s => (s, null))
--调用 cogroup 方法,将这两个RDD合并到一起
val cogrouped: RDD[(String, (Iterable[Null], Iterable[Null]))] = rdd11.cogroup(rdd22)
--出结果看出 ,相同key被合并到一起,在哪个Rdd 中存在 ,就在其 value值位置被赋值null ,只有value值都被赋值null的说明在两个RDD中都存在
cogrouped.collect().foreach(println) --(b,(CompactBuffer(null),CompactBuffer())) (f,(CompactBuffer(),CompactBuffer(null)))...
--即将交集取出来[相同的key在第一个RDD中出现 ,并且也在第二个迭代器中出现,即两个迭代器都不是空迭代器,说明两个RDD中都有值]
---------第一种过滤形式---------
val result: RDD[String] = cogrouped.filter(t => t._2._1.nonEmpty && t._2._2.nonEmpty).keys
result.collect().foreach(println) --a c
-------第二中过滤形式----------
val result1: RDD[String] = cogrouped.filter { case (_, (t1, t2)) => t1.nonEmpty && t2.nonEmpty }.keys
println(result1.collect().toBuffer) --ArrayBuffer(a, c)
--如果想让线程不结束 ,可以让其睡一会
Thread.sleep(10000000)
--或者直接释放资源
sc.stop()
}
}
object Substract01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("Sbstract01").setMaster("local[*]")
val sc = new SparkContext(conf)
--创建原始的RDD
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 3, 4), 3)
val rdd2: RDD[Int] = sc.parallelize(List(4, 5, 6, 7, 8), 2)
--取差集 ,取rdd1 中与rdd2 不相同的元素
rdd1.subtract(rdd2).collect().foreach(println) ----- 3 3 1 2
--取差集 ,取rdd2 中与rdd1 不相同的元素
rdd2.subtract(rdd1).collect().foreach(println(_)) ----- 6 8 5 7
--创建原始的RDD
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("t", 1), ("j", 2), ("k", 3), ("k", 4)))
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("j", 9), ("t", 8), ("s", 7),("t",2)))
--根据key求两个RDD 的差集,只要rdd1中有与rdd2 中 key 相同的,就都移除掉 ,只留下rdd1中与rdd2中key不相同的元素
val rdd3: RDD[(String, Int)] = rdd1.subtractByKey(rdd2)
rdd3.collect().foreach(println(_)) -----(k,3) (k,4)
}
}
2.9 join(连接) ,leftOuterJoin(左外连接) ,rightOuterJoin(右外连接) ,fullOuterJoin(全外连接) :底层调用的都是 cogroup 方法
join :
object InnerJoin01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("InnerJoin01").setMaster("local[*]")
--创建sparkcontext
val sc: SparkContext = new SparkContext(conf)
--创建原始RDD
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 3)))
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("b", 2), ("a", 4), ("c", 1)))
--使用join 方法---根据key相同取两个RDD的交集 ,没有去重效果
val result: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
result.collect().foreach(println) -----(a,(1,4)) (b,(3,2))
// rdd1.intersection(rdd2).collect().foreach(println) --intersection 返回的是一个单个元素的RDD,元组形式的不能使用
---------不使用join ,但是需要实现join相同的功能------------
val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
--对元组的value值进行处理(过滤 ,只有两个迭代器都不为空 ,才将 cogrouped 打印出来)
val keyAndValues: RDD[(String, (Int, Int))] = cogrouped.flatMapValues(t => {
--for循环遍历 ,两个迭代器都有值才会进行到 yield
for (v <- t._1.iterator; w <- t._2.iterator) yield (v, w)
})
keyAndValues.collect().foreach(println) --(a,(1,4)) (b,(3,2))
Thread.sleep(1000000)
}
}
leftOuterJoin :
object LeftOuterJoin01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("LeftOuterJoin01").setMaster("local[*]")
--创建一个 sc
val sc = new SparkContext(conf)
--创建两个原始的RDD
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("TOM", 2), ("HI", 3), ("WO", 5)))
val rdd2: RDD[(String, Int)] = sc.parallelize(List(("HI", 4), ("KEY", 3), ("TOM", 5)))
----------join--------------
val joined: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
println(joined.collect().toBuffer) -- ArrayBuffer((HI,(3,4)), (TOM,(2,5)))
----------leftOuterJoin-----------
val leftjoined: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)
println(leftjoined.collect().toBuffer) -- ArrayBuffer((WO,(5,None)), (HI,(3,Some(4))), (TOM,(2,Some(5))))
--------------不使用leftOuterJoin也能实现其功能的方法------------
val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
将value压平
val leftOuterJoined: RDD[(String, (Int, Option[Int]))] = cogrouped.flatMapValues(t => {
if (t._2.isEmpty) {
t._1.iterator.map(v => (v, None))
} else {
for (v1 <- t._1.iterator; v2 <- t._2.iterator) yield (v1, Some(v2))
}
})
println(leftOuterJoined.collect().toBuffer) --ArrayBuffer((WO,(5,None)), (HI,(3,Some(4))), (TOM,(2,Some(5))))
}
}
rightOuterJoin :
object RightOuterJoin01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("RightOuterJoin01").setMaster("local[*]")
--创建 sc
val sc: SparkContext = new SparkContext(conf)
--创建原始的RDD
val rdd1: RDD[(String, Int)] = sc.parallelize(List(("TOM", 2), ("YUU", 1), ("KEY", 4)))
val rdd2 = sc.parallelize(List(("TOM", 2), ("hi", 1), ("KEY", 4)))
--使用rightouterjoin方法 ,得到以右表为主的新的RDD
val rigthjoined: RDD[(String, (Option[Int], Int))] = rdd1.rightOuterJoin(rdd2)
val tuples: Array[(String, (Option[Int], Int))] = rigthjoined.collect()
tuples.foreach(println(_)) --(hi,(None,1)) (TOM,(Some(2),2)) (KEY,(Some(4),4))
println(tuples.toBuffer) --ArrayBuffer((hi,(None,1)), (TOM,(Some(2),2)), (KEY,(Some(4),4)))
-----------------不使用rightOuterJoin 方法也可以实现其功能----------------
val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
--对元组的value值进行处理 ,但是以右边表为主
val result: RDD[(String, (Option[Int], Int))] = cogrouped.flatMapValues(t => {
if (t._1.isEmpty) {
t._2.iterator.map(v2 => (None, v2))
} else {
for (v1 <- t._1.iterator; v2 <- t._2.iterator) yield (Some(v1), v2)
}
})
result.collect().foreach(println) ---(hi,(None,1)) (TOM,(Some(2),2)) (KEY,(Some(4),4))
Thread.sleep(1000000)
}
}
2.10 cartesian : 笛卡尔积 (不推荐使用 ,产生的数据量太大 ,占空间 ,浪费资源 ,如果数据量很小的话还可以使用)
object cartesian01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.parallelize(List("tom", "jim"))
val rdd2: RDD[String] = sc.parallelize(List("tom", "jon", "shuke"))
val result: RDD[(String, String)] = rdd1.cartesian(rdd2)
println(result.collect().toBuffer)
结果为 :ArrayBuffer((tom,tom), (tom,jon), (tom,shuke),
(jim,tom), (jim,jon), (jim,shuke))
sc.stop()
}
}
3 RDD的Transformation算子实例操作(计算订单分类成交金额)
需求 : 在给定的订单数据,根据订单的分类ID进行聚合,然后按照订单分类名称,统计出某一天商品各个分类的成交金额
3.1 数据信息
--数据信息
{"cid": 1, "money": 600.0, "longitude":116.397128,"latitude":39.916527,"oid":"o123", }
"oid":"o112", "cid": 3, "money": 200.0, "longitude":118.396128,"latitude":35.916527}
{"oid":"o124", "cid": 2, "money": 200.0, "longitude":117.397128,"latitude":38.916527}
{"oid":"o125", "cid": 3, "money": 100.0, "longitude":118.397128,"latitude":35.916527}
{"oid":"o127", "cid": 1, "money": 100.0, "longitude":116.395128,"latitude":39.916527}
{"oid":"o128", "cid": 2, "money": 200.0, "longitude":117.396128,"latitude":38.916527}
{"oid":"o129", "cid": 3, "money": 300.0, "longitude":115.398128,"latitude":35.916527}
{"oid":"o130", "cid": 2, "money": 100.0, "longitude":116.397128,"latitude":39.916527}
{"oid":"o131", "cid": 1, "money": 100.0, "longitude":117.394128,"latitude":38.916527}
{"oid":"o132", "cid": 3, "money": 200.0, "longitude":118.396128,"latitude":35.916527}
--分类信息
1,家具
2,手机
3,服装
3.2 将计算结果保存到MySQL中 (dt,category,money)
3.3 在java的pom.xml文件中导入Maven依赖
<!-- 解析JSON的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.57</version>
</dependency>
<!-- mysql连接依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
3.4 代码实现需求程序
3.4.1 创建一个 case class ,将待处理的数据信息各个字段信息输入到case class中
case class OrderBean(
oid:String,
cid:String,
money:Double ,
longitude:Double,
latitude:Double)
3.4.2 创建一个object类 ,在里面写数据处理和计算分析逻辑代码
object Order01 {
private val logger = LoggerFactory.getLogger(this.getClass)
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("Order").setMaster("local[*]")
val sc = new SparkContext(conf)
--读取文件 ,获得数据
val lines: RDD[String] = sc.textFile(args(0))
--将 lines 数据进行转化
val beanRDD: RDD[OrderBean] = lines.map(line => {
--解析JSON数据 ,将解析后的数据封装到 orderbean 中
--因为json数据有脏数据 ,需要进行 try catch 捕获脏数据,不然程序一遇到脏数据就会停止运行
var bean: OrderBean = null
try {
bean = JSON.parseObject(line, classOf[OrderBean])
} catch {
case e: JSONException => {
--记录错误数据
logger.error("parse json error =>" + line) --捕获到的脏数据 : "o112", "cid": 3, "money": 200.0, "longitude":118.396128,"latitude":35.916527}
}
}
--返回bean
bean
})
--过滤有问题的数据
val filtered = beanRDD.filter(_ != null)
--将没有问题的数据组装成元组 ,然后进行分组聚合
val cidAndMoney: RDD[(String, Double)] = filtered.map(b => (b.cid, b.money)).reduceByKey(_ + _)
--将分类的数据 并行 化成RDD
val category: RDD[(String, String)] = sc.parallelize(List(("1", "家具"), ("2", "手机"), ("3", "服装")))
--将两个 RDD 关联起来 1 钱 家具 ,关联的字段名字和类型必须是一致的
val joined: RDD[(String, (Double, String))] = cidAndMoney.join(category)
--从关联的结果中获取 种类和对应的钱
val categoryAndMoney = joined.map(m => (m._2._2, m._2._1))
--将结果打印出来
--println(categoryAndMoney.collect().toBuffer) --ArrayBuffer((手机,500.0), (服装,600.0), (家具,800.0))
--将结果发送到 mysql 中
--调用一个 action 算子
categoryAndMoney.foreachPartition(it=>{
--在Navicat中开启一个连接 ,然后建立一个 db_data 数据库 ,在该数据库中建立一个tb_daily_category_income表格
--在该表格中有以下查询的几个字段 : dt,category,money ,这些字段的数据类型与查询指定类型一样 ,且字段的名字也要是一模一样的
val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_data?characterEncoding=UTF-8", "root", "123456")
val statement = connection.prepareStatement("insert into tb_daily_category_income(dt,category,money) values(?,?,?)")
it.foreach(t=>{
--查询数据处理出结果的当天日期
statement.setDate(1,new Date(System.currentTimeMillis()))
--查询的category 字段结果
statement.setString(2,t._1)
--查询出的 money 结果
statement.setDouble(3,t._2)
--这个方法是用一个链接,将数据"批量写入"到mysql中, 效率高(非常适合数据量大的时候使用)
statement.executeLargeUpdate()
})
--数据写完后 ,关流/通道/链接
statement.close()
connection.close()
})
--释放 sc 资源
sc.stop()
}
}
导入到数据库中的结果如下 :
将数据写入到mysql中的简约原理图 :