Spark Core 自定义排序、分区

自定义排序(重要)

spark中对简单的数据类型可以直接排序,但是对于一些复杂的条件以利用自定义排序来实现

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
//自定义排序
object CustomSortTest {
def main(args: Array[String]): Unit = {
	val conf = new SparkConf().setAppName("JDBCRDDTest").setMaster("local")
	val sc = new SparkContext(conf)
	val girlInfo = sc.parallelize(List(("xiaoming",90,32),("xiaohong",70,32),("xiaobai",80,34),("bobo",90,35)))
	//根据颜值排序(简单)
	val sorted: RDD[(String, Int, Int)] = girlInfo.sortBy(_._2,false)
		println(sorted.collect.toList)
	//第一种自定义排序
	import MyOrdering.girlOrdering
	val sorted1: RDD[(String, Int, Int)] = girlInfo.sortBy(g => Girl(g._2,g._3))
		println(sorted1.collect.toList)
	//第二种排序方式自定义排序
	val sorted2: RDD[(String, Int, Int)] = girlInfo.sortBy(g => Girls(g._2,g._3))
		println(sorted2.collect.toList)
} 
   //样例类来存储数据
case class Girl(faceValue:Int,age:Int)
//第二个种排序
case class Girls(faceValue:Int,age:Int) extends Ordered[Girls]{
		override def compare(that: Girls): Int = {
			if(this.faceValue == that.faceValue){
				that.age - this.age
			}else{
				this.faceValue - that.faceValue
			}
		}	
	}
}
//隐式转换类型
//若使用隐式转换函数必须使用单例类
object MyOrdering {
	//进行隐式转换 自定义比较(隐式转换函数)
	implicit val girlOrdering = new Ordering[Girl]{
		override def compare(x: Girl, y: Girl): Int = {
			if(x.faceValue != y.faceValue){
				x.faceValue - y.faceValue
			}else{		
				y.age - x.age
			}
		}
	}
}

自定义分区(重要)

我们都知道Spark内部提供了HashPartitionerRangePartitioner两种分区策略,这两种分区策略在很多情况下都适合我们的场景。但是有些情况下,Spark内部不能符合咱们的需求,这时候我们就可以自定义分区策略

要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个方法。

numPartitions: Int:返回创建出来的分区数。

getPartition(key: Any): Int:返回给定键的分区编号(0到numPartitions-1)。

equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同

基础案例1

class CustomerPartitoner(numPartiton:Int) extends Partitioner{
  // 返回分区的总数
  override def numPartitions: Int = numPartiton
  // 根据传入的Key返回分区的索引
  override def getPartition(key: Any): Int = {
    key.toString.toInt % numPartiton
  }
}
object CustomerPartitoner {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("CustomerPartitoner").setMaster("local[*]")
    val sc = new SparkContext(sparkConf)
    //zipWithIndex该函数将RDD中的元素和这个元素在RDD中的ID(索引号)组合成键/值对。
    val rdd = sc.parallelize(0 to 10,1).zipWithIndex()
    val func = (index:Int,iter:Iterator[(Int,Long)]) =>{
      iter.map(x => "[partID:"+index + ", value:"+x+"]")
    }
    val r = rdd.mapPartitionsWithIndex(func).collect()
    for (i <- r){
      println(i)
    }
    val rdd2 = rdd.partitionBy(new CustomerPartitoner(5))
    val r1 = rdd2.mapPartitionsWithIndex(func).collect()
    println("----------------------------------------")
    for (i <- r1){
      println(i)
    }
    println("----------------------------------------")
    sc.stop()
  }

案例2

import java.net.URL
import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import scala.collection.mutable
/**
* 自定分区
* 数据中有不同的学科,将输出的一个学科生成一个文件
*/
object SubjectDemo3 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SubjectDemo").setMaster("local")
val sc = new SparkContext(conf)
// 1.对数据进行切分
val tuples: RDD[(String, Int)] =
sc.textFile("C:\\Users\\Administrator\\Desktop\\subjectaccess\\access.txt").map(line => {
val fields: Array[String] = line.split("\t")
//取出url
val url = fields(1)
(url, 1)
})
//将相同url进行聚合,得到了各个学科的访问量
val sumed: RDD[(String, Int)] = tuples.reduceByKey(_+_).cache()
//从url中获取学科的字段 数据组成式 学科, url 统计数量
val subjectAndUC = sumed.map(tup => {
val url = tup._1 //用户url
val count = tup._2 // 统计的访问数量
val subject = new URL(url).getHost //学科
(subject, (url, count))
})
//将所有学科取出来
val subjects: Array[String] = subjectAndUC.keys.distinct.collect
//创建自定义分区器对象
val partitioner: SubjectPartitioner = new SubjectPartitioner(subjects)
//分区
val partitioned: RDD[(String, (String, Int))] = subjectAndUC.partitionBy(partitioner)
//取top3
val rs = partitioned.mapPartitions(it => {
val list = it.toList
val sorted = list.sortBy(_._2._2).reverse
val top3: List[(String, (String, Int))] = sorted.take(3)
//因为方法的返回值需要一个iterator
top3.iterator
})
//存储数据
rs.saveAsTextFile("out2")
sc.stop()
}
}
/**
* 自定义分区器需要继承Partitioner并实现对应方法
* @param subjects 学科数组
*/
class SubjectPartitioner(subjects:Array[String]) extends Partitioner{
//创建一个map集合用来存到分区号和学科
val subject = new mutable.HashMap[String,Int]()
//定义一个计数器,用来生成分区好
var i = 0
for(s <- subjects){
//存学科和分区
subject+=(s -> i)
i+=1 //分区自增
} /
/获取分区数
override def numPartitions: Int = subjects.size
//获取分区号(如果传入的key不存在,默认将数据存储到0分区)
override def getPartition(key: Any): Int = subject.getOrElse(key.toString,0)
}

总结:

1.分区主要面对KV结构数据,Spark内部提供了两个比较重要的分区器,Hash分区器和Range分区器

2.hash分区主要通过key的hashcode来对分区数求余,hash分区可能会导致数据倾斜问题,Range分区是通过水塘抽样的算法来将数据均匀的分配到各个分区中

3.自定义分区主要通过继承partitioner抽象类来实现,必须要实现两个方法:

numPartitions 和 getPartition(key: Any)

你可能感兴趣的:(Spark)