Spark的二次排序以及aggregate

一、二次排序

  (1)输入与输出

  

  (2)需求:数据如file1.txt,要求根据第一列降序,如果第一列相等,则根据第二列升序

    分析:平时所使用的键值对是不具有比较意义的,也就说他们没法拿来直接比较,可以通过sortByKey,sortBy(pair._2)来进行单列的排序,但是没法进行两列的同时排序。可以自定义一个键值对的比较类来实现比较,类似于JAVA中自定义类实现可比较性实现comparable接口。我们需要继承Ordered和Serializable特质来实现自定义的比较类。

  (3)解答:先根据key进行排序,相同再根据value进行排序。可以把键值对当成一个数据有两个数字,先通过第一个数字比大小,再通过第二个数字比大小。

    1)我们定义两个Int参数的比较类
    2)继承Ordered 和 Serializable 接口 实现 compare 方法实现可以比较

  (4)代码如下:

 1 package SparkDemo
 2 import org.apache.spark.{SparkConf, SparkContext}
 3 class UDFSort (val first:Int,val second:Int) extends Ordered[UDFSort] with Serializable {//自定义比较类
 4     override def compare(that: UDFSort): Int = {
 5         if(this.first - that.first != 0){//第一个值不相等的时候,直接返回大小
 6             this.first - that.first //返回值
 7         }
 8         else {//第一个值相等的时候,比较第二个值
 9             this.second - that.second
10         }
11     }
12 }
13 object Sort{
14     def main(args:Array[String]): Unit ={
15         //初始化配置:设置主机名和程序主类的名字
16         val conf = new SparkConf().setAppName("UdfSort");
17         //通过conf来创建sparkcontext
18         val sc = new SparkContext(conf);
19         val lines = sc.textFile("file:///...")
20         //转换为( udfsort( line(0),line(1) ),line ) 的形式
21         val pair = lines.map(line => (new UDFSort(line.split(" ")(0).toInt,line.split(" ")(1).toInt),line))
22         //对key进行排序,然后取value
23         val result = pair.sortByKey().map( x => x._2)
24     }
25 }

二、combinerByKey函数

(1)主要接受5个参数,一般来说我们主要使用前2个参数

  1)createCombiner:V=>C  分组内的创建组合的函数。通俗点将就是对读进来的数据进行初始化,其把当前的值作为参数,可以对该值做一些转换操作,转换为我们想要的数据格式

  2)mergeValue:(C,V)=>C  该函数主要是分区内的合并函数,作用在每一个分区内部。其功能主要是将V合并到之前(createCombiner)的元素C上,注意,这里的C指的是上一函数转换之后的数据格式,而这里的V指的是原始数据格式(上一函数为转换之前的)

  3)mergeCombiners:(C,C)=>R  该函数主要是进行多分取合并,此时是将两个C合并为一个C.

  4)partitioner:自定义分区数,默认是hashPartitioner

  5)mapSideCombine:Boolean=true  该参数是设置是否在map端进行combine操作

(2)函数工作流程

  1)combinByKey会遍历rdd中每一个(k,v)数据对,对该数据对中的k进行判断,判断该(k,v)对中的k是否在之前出现过,如果是第一次出现,则调用createCombiner函数,对该k对应的v进行初始化操作(可以做一些转换操作),也就是创建该k对应的累加其的初始值

  2)如果这是一个在处理当前分区之前遇到的k,会调用mergeCombiners函数,把该k对应的累加器的value与这个新的value进行合并操作

(3)示例代码

import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
 
import scala.collection.mutable
 
object test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("testCombineByKey").setMaster("local[2]")
    val ssc = new SparkSession
    .Builder()
      .appName("test")
      .master("local[2]")
      .config(conf)
      .getOrCreate()
    val sc = ssc.sparkContext
    sc.setLogLevel("error")
 
    val initialScores = Array((("1", "011"), 1), (("1", "012"), 1), (("2", "011"), 1), (("2", "013"), 1), (("2", "014"), 1))
    val d1 = sc.parallelize(initialScores)
  d1.map(x => (x._1._1, (x._1._2, 1)))
    .combineByKey(
      (v: (String, Int)) => (v: (String, Int)),
      (acc: (String, Int), v: (String, Int)) => (v._1+":"+acc._1,acc._2+v._2),
          (p1:(String,Int),p2:(String,Int)) => (p1._1 + ":" + p2._1,p1._2 + p2._2)
    ).collect().foreach(println)
  }
}

1、map端将数据格式化为:(,(String,Int))->("1",("011",1))

2、接着combineByKye函数会逐个的读取map之后的每一个k,v数据对,当读取到第一个("1",("011",1)),此时回判断,“1”这个是否在之前的出现过,如果该k是第一次出现,则会调用createCombiner函数,经过转换,该实例中是对该value值么有做任何的改变原样返回,此时这个该value对应的key回被comgbineByKey函数创建一个累加其记录

3、当读取到第二个数据("1",("012",,1))的时候,回对“1”这个key进行一个判断,发现其在之前出现过,此时怎直接调用第二个函数,mergeValues函数,对应到该实例中,acc即为上一函数产生的结果,即("1",("011",1)),v即是新读进来的数据("1",("012",1))

4、此时执行该函数:(acc: (String, Int), v: (String, Int)) => (v._1+":"+acc._1,acc._2+v._2)将新v中的第一个字符串与acc中的第一个字符串进行连接,v中的第二个值,与acc中的第二个值进行相加操作

5、当所有的分区内的数据计算完成之后,开始调用mergeCombiners函数,对每个分区的数据进行合并,该实例中p1和p2分别对应的是不同分区的计算结果,所以二者的数据格式是完全相同的,此时将第一个分区中的字符串相连接,第二个字符相加得到最终结果

(2,(014:013:011,3))
(1,(012:011,2))

三、aggregate算子

(1)定义:aggregate接收两个函数,和一个初始化值。seqOp函数用于聚集每一个分区,combOp用于聚集所有分区聚集后的结果。每一个分区的聚集,和最后所有分区的聚集都需要初始化值的参与。

(2)代码如下:

 1 def seqOp(p1: Int, p2: Int): Int = {
 2     p1 * p2 
 3 }
 4 
 5 def combOp(p3: Int, p4: Int): Int = {
 6     p3 + p4 
 7 }
 8 
 9 //调用makeRDD()方法的时候,会为每个集合对象创建最佳分区,而这对后续的调用优化很有帮助。
10 //当调用parallelize()方法的时候,不指定分区数的时候,使用系统给出的分区数rdd=sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
11 
12 rdd=sc.makeRDD(List(1,2,3,4,5,6,7,8,9,10))
13 //分区为:(1 , 2 , 3 ) ,(4 , 5 , 6 ),(7 , 8 , 9 , 10 )
14 rdd2.aggregate(2)(pfun1, pfun2) //结果为10334
15 
16 解析:
17 seqOp 函数会分别在 RDD 的每个分区中应用一次,其中标红的为初始值
18 2 * 1 * 2 * 3       = 12
19 2 * 4 * 5 * 6       = 240
20 2 * 7 * 8 * 9 * 10  = 10080
21 
22 seqOp输出有3个值即12,240,10080,这三个值+初始值作为combOp的输入
23 2 + 12 + 240 + 10080  = 10334

posted on 2019-12-26 10:01  hdc520  阅读(449)  评论(0编辑  收藏  举报

导航