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