1。 RDD 上的算子
RDD 上的所有的算子示例:
http://homepage.cs.latrobe.edu.au/zhe/ZhenHeSparkRDDAPIExamples.html
1.1。 加入左外联盟右外联盟cogroup
#join 只能作用于 k,v RDD [(Int,Int)]
相当于 SQL 中的内关联join ,只返回两个RDD 根据K 可以关联上的结果
在类型为( K,V)和(K,W)类型的数据集上调用时,返回一个相同的密钥对应的所有元素对在一起的(K,(V,W))数据集
leftOuterJoin
rightOuterJoin
val rdd3 = rdd1.leftOuterJoin(rdd2)RDD [(String,(V,Option [W]))]
val rdd3 = rdd1.rightOuterJoin(rdd2)RDD [(String,(Option [V],W))]
#cogroup
在类型为(K,V)和(K,W)的数据集上调用,返回一个(K, (Iterable[V], Iterable[W]))元组的数据集。这个操作也可以称之为groupwith
相当于SQL中的全外关联full outer join,返回左右RDD中的记录,关联不上的为空。
val sc: SparkContext = MySpark(this.getClass.getSimpleName)
|
1.2. 笛卡尔积
cartesian笛卡尔积
scala> val rdd1 = sc.makeRDD(List("tom","cat","jim")) rdd1: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at makeRDD at <console>:24
scala> val rdd2 = sc.makeRDD(List(1,3)) rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:24
scala> val rdd3 = rdd1.ca cache canEqual cartesian
scala> val rdd3 = rdd1.cartesian(rdd2) rdd3: org.apache.spark.rdd.RDD[(String, Int)] = CartesianRDD[2] at cartesian at <console>:28
scala> rdd3.collect [Stage 0:> (0 + 14) / 14[Stage 0:==> (6 + 13) / 14[Stage 0:=================================> (87 + 12) / 14[Stage 0:================================================> (130 + 12) / 14 res0: Array[(String, Int)] = Array((tom,1), (tom,3), (cat,1), (cat,3), (jim,1), (jim,3)) |
1.3. 修改分区数量的算子
repartition(分区数量)
coalesce(分区数量)
repartition会对数据进行重写的分发(shuffle) 同一个分区的数据,会被分发到不同的分区中去。
coalesce 默认是没有进行shuffle的,所以当用coalesce来扩大分区的数量,是失败的,分区数量不变。
scala> val rdd1 = sc.makeRDD(List(1,3,5,6,7,8),3) rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[3] at makeRDD at <console>:24
scala> rdd1.repartition(1) res1: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[7] at repartition at <console>:27
scala> rdd1.repartition(2) res2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[11] at repartition at <console>:27
scala> rdd1.coalesce(2) res3: org.apache.spark.rdd.RDD[Int] = CoalescedRDD[12] at coalesce at <console>:27
scala> res2.partitions.size res4: Int = 2
scala> res3.partitions.size res5: Int = 2
scala> val f =(i:Int,it:Iterator[Int])=> | it.map(t=> s"p=$i,v=$t") f: (Int, Iterator[Int]) => Iterator[String] = <function2>
scala> res2.mapPartitionsWithIndex(f).collect res6: Array[String] = Array(p=0,v=5, p=0,v=1, p=0,v=7, p=1,v=3, p=1,v=8, p=1,v=6)
scala> res3.mapPartitionsWithIndex(f).collect res7: Array[String] = Array(p=0,v=1, p=0,v=3, p=1,v=5, p=1,v=6, p=1,v=7, p=1,v=8)
scala> rdd1.repartition(10) res8: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[18] at repartition at <console>:27
scala> rdd1.coalesce(10) res9: org.apache.spark.rdd.RDD[Int] = CoalescedRDD[19] at coalesce at <console>:27
scala> res8.partitions.size res10: Int = 10
scala> res9.partitions.size res11: Int = 3 |
总结:
repartition(10) = rdd1.coalesce(10,true)
repartition会对数据进行重新的shuffle。 coalesce主要用于合并分区,不会进行数据的shuffle。
实际使用:
如果数据需要shuffle,选择 repartition。
repartition 常用于 扩大分区数量。 提升任务的并行度。
coalesce常用于合并分区(减少分区数量) 不能用于扩大分区数量。除非加shuffle为true。
rdd1.sortByKey(false).coalesce(1).foreach(println) |
1.4. aggregate,aggregateByKey
aggregate 是action算子,aggregateByKey 是转换算子。
第一个参数,是初始值。 初始值 ,参与分区内聚合, 还参与全局聚合
第二个参数: 是两个函数参数。第一个函数,表示分区内聚合,第二个函数,全局聚合。
scala> val rdd1 = sc.makeRDD(List(1,3,4,5),2) rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[24] at makeRDD at <console>:24
scala> rdd1.aggregate(0)(_+_,_+_) res14: Int = 13
scala> rdd1.aggregate(0)(_+_,_+_) res15: Int = 13
scala> rdd1.aggregate(10)(_+_,_+_) res16: Int = 43
scala> rdd1.aggregate(10)((a,b)=>math.max(a,b),_+_) res17: Int = 30
scala> val rdd2 = sc.parallelize(List("a","b","c","d","e","f"),2) rdd2: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[25] at parallelize at <console>:24
scala> rdd2.aggregate("")(_ ++ _, _ ++ _) res18: String = defabc
scala> rdd2.aggregate("")(_ ++ _, _ ++ _) res19: String = abcdef |
aggregateByKey:
转换类的算子
第一个参数:初始值 初始值 只参与分区聚合
第二个参数: 两个函数 第一个是分区聚合函数 第二个函数 是全局聚合函数
scala> val pairRDD = sc.parallelize(List( ("cat",2), ("cat", 5), ("mouse", 4),("cat", 12), ("dog", 12), ("mouse", 2)), 2) pairRDD: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[26] at parallelize at <console>:24
scala> pairRDD.aggregate aggregate aggregateByKey
scala> pairRDD.aggregateByKey(0)(_+_,_+_) res20: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[27] at aggregateByKey at <console>:27
scala> res20.collect res21: Array[(String, Int)] = Array((dog,12), (cat,19), (mouse,6))
scala> pairRDD.aggregateByKey(10)(_+_,_+_).collect res22: Array[(String, Int)] = Array((dog,22), (cat,39), (mouse,26))
scala> cat 17 mouse 14 dog 22 cat 22 mouse 12 |
1.5. 算子的总结
转换类的算子:
RDD之间的依赖
普通的算子: map fliter flatMap
数据是一对一的,
shuffle类的算子:
分区内的数据会进行重新的分发
reduceByKey join distinct
action类的算子:
算子,作用于RDD[k]
算子: 必须作用于 RDD[K,V]
2. 全局TopK和分组的TopK
2.1. 全局TopK
|
2.2. 分组的TopK-groupBy
object SubFacTeacher {
|
2.3. 分组的topK—过滤实现
|
3. AB数据集的聚合
补充:
数据的切分:
val str = "a,b,,,"
|
利用reduceByKey + leftOuterJoin实现AB数据集的聚合
object ABTest {
|
4. RDD上的5大特性
RDD是spark中的基本的计算模型。是一个抽象的概念。
RDD 抽象类
5大特性:
分别对应着RDD的成员属性或者方法。
4.1. 分区列表:
RDD的数据集的基本组成单位。
每一个RDD都有一到多个分区。
数据,
每一个分区,会记录读取的数据在哪里
4.2. compute方法
计算方法。
用于计算的
MapPartitionsRDD中的compute 方法:
f: map((_,1))
把父RDD中的数据,组装成iterator,然后直接传递到函数中。
父RDD的数据到子RDD的数据的逻辑转换。
4.3. RDD之间的依赖关系
RDD之间的依赖关系分为两类:
OneToOneDependency 窄依赖
ShuffleDependency 宽依赖
4.4. 可选的,分区器
在RDD[K,V] 类型上才有分区器
RDD[K] 的分区器 None
一种: HashPartitioner key 的hashcode % 分区数量
一种: RangePartitioner key 的范围 分区的数量
如果需要自定义分区器:
就定义类 ,继承 Partitioner,重写抽象方法即可。
4.5. 可选的,优先位置
理念: 移动数据不如移动计算。
优先,在有数据的节点上,启动计算任务。
分区列表,分区器。
依赖关系
compute方法,优先位置。
4.5.1. RDD中的分区器
只有RDD[K,V] 才有分区器 RDD[K] 分区器是None
HashPartitioner
RangePartitioner
reduceByKey HashParititoner
sortBy sortByKey RangePartitioner
HashPartitioner的判断相同的标准: 1,是否是同一个分区器;2 分区的数量是否相同
scala> val rdd1 = sc.makeRDD(List(1,2,3,12,15,18,21,27),3) rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:24
scala> val rdd2 = rdd1.zipWI zipWithIndex zipWithUniqueId
scala> val rdd2 = rdd1.zipWithIndex rdd2: org.apache.spark.rdd.RDD[(Int, Long)] = ZippedWithIndexRDD[1] at zipWithIndex at <console>:26
scala> val rdd3 = rdd2.groupByKey() rdd3: org.apache.spark.rdd.RDD[(Int, Iterable[Long])] = ShuffledRDD[2] at groupByKey at <console>:28
scala> val f = (i:Int,it:Iterator[(Int,Long)])=> | it.map(t=> s"p=$i,v=${t._1}") f: (Int, Iterator[(Int, Long)]) => Iterator[String] = <function2>
scala> rdd2.mapPartitionsWithIndex(f).collect res0: Array[String] = Array(p=0,v=1, p=0,v=2, p=1,v=3, p=1,v=12, p=1,v=15, p=2,v=18, p=2,v=21, p=2,v=27)
scala> val f2 = (i:Int,it:Iterator[(Int, Iterable[Long])])=> | it.map(t => s"p=$i,v= ${t._1}") f2: (Int, Iterator[(Int, Iterable[Long])]) => Iterator[String] = <function2>
scala> rdd3.mapPartitionsWithIndex(f2).collect res1: Array[String] = Array(p=0,v= 21, p=0,v= 15, p=0,v= 27, p=0,v= 18, p=0,v= 3, p=0,v= 12, p=1,v= 1, p=2,v= 2)
scala>
scala>
scala> val rdd5 = sc.makeRDD(List(11,101,10001,1001,9,9,11,121,141,9,9,11,11,11),3) rdd5: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[5] at makeRDD at <console>:24
scala>
scala>
scala> val rdd6 = rdd5.zipWithIndex rdd6: org.apache.spark.rdd.RDD[(Int, Long)] = ZippedWithIndexRDD[6] at zipWithIndex at <console>:26
scala> val rdd7 = rdd6.sortByKey() rdd7: org.apache.spark.rdd.RDD[(Int, Long)] = ShuffledRDD[9] at sortByKey at <console>:28
scala> rdd6.mapPartitionsWithIndex(f).collect res2: Array[String] = Array(p=0,v=11, p=0,v=101, p=0,v=10001, p=0,v=1001, p=1,v=9, p=1,v=9, p=1,v=11, p=1,v=121, p=1,v=141, p=2,v=9, p=2,v=9, p=2,v=11, p=2,v=11, p=2,v=11)
scala> rdd7.mapPartitionsWithIndex(f).collect res3: Array[String] = Array(p=0,v=9, p=0,v=9, p=0,v=9, p=0,v=9, p=0,v=11, p=0,v=11, p=0,v=11, p=0,v=11, p=0,v=11, p=1,v=101, p=2,v=121, p=2,v=141, p=2,v=1001, p=2,v=10001)
scala> val rdd7 = rdd6.sortByKey() rdd7: org.apache.spark.rdd.RDD[(Int, Long)] = ShuffledRDD[14] at sortByKey at <console>:28
scala> rdd7.mapPartitionsWithIndex(f).collect res4: Array[String] = Array(p=0,v=9, p=0,v=9, p=0,v=9, p=0,v=9, p=0,v=11, p=0,v=11, p=0,v=11, p=0,v=11, p=0,v=11, p=1,v=101, p=2,v=121, p=2,v=141, p=2,v=1001, p=2,v=10001) |
如果自定义分区器:
直接继承Partitioner,重写抽象方法。
5. 以wordcount为示例,查看spark任务的运行流程
5.1. 一个wordcount中产生了几个rdd
// 创建SparkContext
|
问题:
最简版的wordcount,一共产生了几个RDD?
这个wordcount的任务,一共产生了6个RDD
可以通过toDebugString 方法来查看rdd的依赖关系图。
// 可以查看RDD的依赖关系
println(result.toDebugString)
5.2. spark任务运行的数据流向图
scala> val rdd1 = sc.textFile("hdfs://hdp-01:9000/wordcount/input") rdd1: org.apache.spark.rdd.RDD[String] = hdfs://hdp-01:9000/wordcount/input MapPartitionsRDD[1] at textFile at <console>:24
scala> val rdd2 = rdd1.flatMap(_.split(" ")).map((_,1)) rdd2: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[3] at map at <console>:26
scala> val rdd3 = rdd2.reduceByKey(_+_,2) rdd3: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[4] at reduceByKey at <console>:28
scala> rdd3.saveAsTextFile("hdfs://hdp-01:9000/wordcount/output11") |
逻辑图
5.3. 任务调度的流程:
applicationà job -à stage à task
5.4. 物理执行流程:
当执行我们自定义程序(main)的时候,并没有真正的执行程序。
Driver会记录rdd之间的依赖关系,每一个rdd传递了什么函数,记录rdd要去哪里读取数据。
当触发action算子的时候,真正开始执行任务。
DAG à 切分stage -à 组装Task à 把Task提交给executor去执行。
任务正式的开始按照我们的业务逻辑执行。
spark-submit 开始
今日总结:
rdd算子
rdd上的5大特性
分区器 –》 自定义分区器