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内部提供了HashPartitioner
和RangePartitioner
两种分区策略,这两种分区策略在很多情况下都适合我们的场景。但是有些情况下,Spark内部不能符合咱们的需求,这时候我们就可以自定义分区策略
要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个方法。
numPartitions: Int:返回创建出来的分区数。
getPartition(key: Any): Int:返回给定键的分区编号(0到numPartitions-1)。
equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同
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()
}
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)