一、自定义累加器(Accumulator)
自定义累加器,可以任意累加不同类型的值,同时也可以在内部进行计算,或者逻辑编写,如果继承自定义累加器,那么需要实现内部的抽象方法,然后在每个抽象方法内部去累加变量值即可,主要是在全局性累加起到决定性作用。
累加器作为spark的一个共享变量的实现,在用于累加计数计算计算指标的时候可以有效的减少网络的消耗
累加器可以在每个节点上面进行Task的值,累加操作,有一个Task的共享性操作
新版累加器使用步骤: 1. 创建累加器 2. 注册累加器 3. 使用累加器
1、类继承extends AccumulatorV2[String, String],第一个为输入类型,第二个为输出类型
2、覆写抽象方法:
isZero: 当AccumulatorV2中存在类似数据不存在这种问题时,是否结束程序。copy
: 拷贝一个新的AccumulatorV2reset
: 重置AccumulatorV2中的数据add
: 操作数据累加方法实现merge
: 合并数据value
: AccumulatorV2对外访问的数据结果
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.util.AccumulatorV2
//在继承的时候需要执行泛型 即 可以计算IN类型的输入值,产生Out类型的输出值
//继承后必须实现提供的方法
class MyAccumulator extends AccumulatorV2[Int,Int]{
//创建一个输出值的变量
private var sum:Int = _
//必须重写如下方法:
//检测方法是否为空
override def isZero: Boolean = sum == 0
//拷贝一个新的累加器
override def copy(): AccumulatorV2[Int, Int] = {
//需要创建当前自定累加器对象
val myaccumulator = new MyAccumulator()
//需要将当前数据拷贝到新的累加器数据里面
//也就是说将原有累加器中的数据拷贝到新的累加器数据中
//ps:个人理解应该是为了数据的更新迭代
myaccumulator.sum = this.sum
myaccumulator
}
//重置一个累加器 将累加器中的数据清零
override def reset(): Unit = sum = 0
//每一个分区中用于添加数据的方法(分区中的数据计算)
override def add(v: Int): Unit = {
//v 即 分区中的数据
//当累加器中有数据的时候需要计算累加器中的数据
sum += v
}
//合并每一个分区的输出(将分区中的数进行汇总)
override def merge(other: AccumulatorV2[Int, Int]): Unit = {
//将每个分区中的数据进行汇总
sum += other.value
}
//输出值(最终累加的值)
override def value: Int = sum
}
object MyAccumulator{
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("MyAccumulator").setMaster("local[*]")
//2.创建SparkContext 提交SparkApp的入口
val sc = new SparkContext(conf)
val numbers = sc .parallelize(List(1,2,3,4,5,6),2)
val accumulator = new MyAccumulator()
//需要注册
sc.register(accumulator,"acc")
//切记不要使用Transformation算子 会出现无法更新数据的情况
//应该使用Action算子
//若使用了Map会得不到结果
numbers.foreach(x => accumulator.add(x))
println(accumulator.value)
}
}
总结:
1.累加器的创建:
1.1.创建一个累加器的实例
1.2.通过sc.register()注册一个累加器
1.3.通过累加器实名.add来添加数据
1.4.通过累加器实例名.value来获取累加器的值
2.最好不要在转换操作中访问累加器(因为血统的关系和转换操作可能执行多次),最好在行动操作中访问
作用:
1.能够精确的统计数据的各种数据例如:
可以统计出符合userID的记录数,在同一个时间段内产生了多少次购买,可以使用ETL进行数据清洗,并使用Accumulator来进行数据的统计
2.作为调试工具,能够观察每个task的信息,通过累加器可以在sparkIUI观察到每个task所处理的记录数
二、自定义排序
第一种排序方法,比如有数据“name,age,颜值”,我们想按照颜值降序,颜值相同按照年龄升序排。
思路:写个类,将切分出来的数据放入到这个类中,重写这个类的compare方法,用这个类调用sortBy方法。
package com.thy.d20190417
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 实现自定义排序
*/
object CustomSort1 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("IpLocation").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
//排序规则,先按颜值降序,相等再按年龄升序
val users=Array("laoduan 30 99","laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
//将driver的数据并行化变成 RDD
val lines = sc.parallelize(users)
//切分整理数据,将需要的数据切分出来,放到一个类中,
//这个类继承ordered,然后重写compare,这个类在调用相应的排序方法的时候就会执行重写后的排序规则
val userRDD: RDD[User] = lines.map(line => {
val fields: Array[String] = line.split(" ")
val name: String = fields(0)
val age: Int = fields(1).toInt
val fv: Int = fields(2).toInt
new User(name, age, fv)
})
//u => u表示不传入排序规则,即会调用这个类重写的排序规则
val sorted: RDD[User] = userRDD.sortBy(u => u)
println(sorted.collect().toBuffer)
}
class User(val name:String,val age:Int,val fv:Int)extends Ordered[User] with Serializable {
override def compare(that: User): Int = {
if(this.fv == that.fv){
this.age - that.age // 按照age升序
}else{
-(this.fv - that.fv) // 按照fv降序
}
}
override def toString: String = s"name:$name,age:$age,facavalue:$fv"
}
}
第二种方式:传入一个排序规则,不必将切分后的数据放入到一个类中,然后重写他的compare方法。
package com.thy.d20190417
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("IpLocation").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
val users = Array("nanyang 25 99","thy 26 9999", "mingming 28 98", "wazi 22 99")
val lines: RDD[String] = sc.parallelize(users)
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields: Array[String] = line.split(" ")
val name: String = fields(0)
val age: Int = fields(1).toInt
val fv: Int = fields(2).toInt
(name, age, fv)
})
// 传入一个排序规则,不改变数据格式,只改变顺序
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => new MySort(tp._2,tp._3))
println(sorted.collect().toBuffer)
sc.stop()
}
//排序规则,将age和fv两个排序用到的属性传入,然后进行重写compare方法,当调用sortBy时,new一个本规则传入相应比较属性即可实现排序。
class MySort(val age:Int,val fv:Int)extends Ordered[MySort] with Serializable {
override def compare(that: MySort): Int = {
if(this.fv == that.fv){
this.age - that.age
}else{
-(this.fv - that.fv)
}
}
}
}
第三种方式:使用case class。 case class与class区别:
package com.thy.d20190417
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort3 {
def main(args: Array[String]): Unit = {
/**
此段代码同上 tpRDD为:(name,age,fv)
*/
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => MySort(tp._2,tp._3))
println(sorted.collect().toBuffer)
}
//case class 默认实现了序列化,不用在 with seria...
case class MySort(age:Int,fv:Int) extends Ordered[MySort]{
override def compare(that: MySort): Int = {
if(this.fv == that.fv){
this.age - that.age
}else{
-(this.fv - that.fv)
}
}
}
}
第四种方式:单独写个SortRules.scala,继承ordering,重写比较规则。排序时import SortRules.类,
package com.thy.d20190417
import com.thy.d20190417.CustomSort4.MySort2
object SortRules {
implicit object OrderingMySort extends Ordering[MySort2]{
override def compare(x: MySort2, y: MySort2): Int = {
if(x.fv == y.fv){
x.age - y.age
}else{
-(x.fv - y.fv)
}
}
}
}
//---------------------------------------------------------------------------------------
package com.thy.d20190417
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object CustomSort4 {
def main(args: Array[String]): Unit = {
/**
此段代码同上 tpRDD为:(name,age,fv)
*/
import SortRules.OrderingMySort
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => MySort2(tp._2,tp._3))
println(sorted.collect().toBuffer)
sc.stop()
}
case class MySort2(age:Int, fv: Int)
}
第五种方式:仅需要排序时,利用元组的排序特性即可
//先降序比较fv,在升序比较age
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => (-tp._3,tp._2))
/**
其他代码同上
*/
第六种:充分利用元组排序
//充分利用元组的比较规则,元组的比较规则:先比第一,相等再比第二个
//比较前的数据格式
//on[(String,Int,Int)]
//最终比较的数据格式
//Ordering[(Int,Int)]
implicit val rules: Ordering[(String, Int, Int)] = Ordering[(Int,Int)].on[(String,Int,Int)](t => (-t._3,t._2))
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp =>tp)
原文链接:
三、自定义分区
object test1107_2 extends App {
def repl(arr: Array[(String, String)], cate: String): String = {
var start = 0
var end = arr.length - 1
while (start <= end) {
val middle = (start + end) / 2
if (cate.toInt == arr(middle)._1.toInt) {
return arr(middle)._2
}
if (cate.toInt < arr(middle)._1.toInt) {
end = middle - 1
} else {
start = middle + 1
}
}
"其他"
}
def changeTime(time: String): String = {
val array = time.toCharArray
if (time.startsWith("0")) {
array(1).toString
} else {
time
}
}
private val conf: SparkConf = new SparkConf().setAppName("test1107_2").setMaster("local")
private val sc = new SparkContext(conf)
private val sogouLog: RDD[String] = sc.textFile("C:\\Users\\potpof\\Desktop\\阶段三\\day19\\SogouQ.txt")
private val text: RDD[String] = sc.textFile("C:\\Users\\potpof\\Desktop\\阶段三\\day19\\ID.txt")
private val v1: RDD[(String, String)] = text.map(x => {
val strings = x.split(" ")
(strings(0), strings(1))
})
private val broad: Broadcast[Array[(String, String)]] = sc.broadcast(v1.collect())
private val value1: RDD[((String, String), Int)] = sogouLog.map(f = x => {
val strings = x.toString.split(",")
var hour = strings(0).split(":")(0)
val cate = strings(4)
((changeTime(hour), repl(broad.value, cate)), 1)
})
//创建自定义分区的对象
private val partition = new HourPartition
private val value2: RDD[((String, String), Int)] = value1.reduceByKey(_ + _)
private val value3: RDD[(String, (String, Int))] = value2.map(x => {
(x._1._1, (x._1._2, x._2))
})
//groupby的时候传入自定义的分区
private val value4: RDD[(String, Iterable[(String, Int)])] = value3.groupByKey(partition)
//对RDD[(String, Iterable[(String, Int)])]的类型的RDD进行排序的时候,将iterable转换为array,再使用sortwith对
//array内部的tuple _._2 > _._2 进行排序,再取前N个
val topN: RDD[(String, Iterable[(String, Int)])] = value4.map(t => {
val group = t._1
val list = t._2.toArray.sortWith(_._2 > _._2).take(3).toIterable
(group, list)
})
println(topN.collect().toBuffer)
// ArrayBuffer((0,WrappedArray((国际,13938), (社会,7154), (其他,6850))), (1,WrappedArray((国际,8193), (其他,4487), (社会,4005))), (2,WrappedArray((国际,5534), (其他,2975), (社会,2606))), (3,WrappedArray((国际,4035), (其他,1741), (社会,1703))), (4,WrappedArray((国际,3280), (社会,1353), (其他,1038))), (5,WrappedArray((国际,3654), (社会,1467), (其他,1089))), (6,WrappedArray((国际,5707), (社会,2474), (图片,1483))), (7,WrappedArray((国际,10191), (社会,4659), (图片,2782))), (8,WrappedArray((国际,19514), (社会,9282), (图片,5446))), (9,WrappedArray((国际,27481), (社会,13907), (图片,8427))), (10,WrappedArray((国际,30998), (社会,16404), (图片,10196))), (11,WrappedArray((国际,29537), (社会,15354), (图片,9562))), (12,WrappedArray((国际,27056), (社会,14227), (图片,8849))), (13,WrappedArray((国际,27992), (社会,14921), (图片,9353))), (14,WrappedArray((国际,29144), (社会,15829), (图片,9958))), (15,WrappedArray((国际,31227), (社会,16852), (其他,10590))), (16,WrappedArray((国际,32827), (社会,17947), (图片,11396))), (17,WrappedArray((国际,30882), (社会,16418), (图片,10151))), (18,WrappedArray((国际,28018), (社会,14636), (图片,9054))), (19,WrappedArray((国际,29603), (社会,15330), (图片,9374))), (20,WrappedArray((国际,33178), (社会,17560), (图片,10770))), (21,WrappedArray((国际,34375), (社会,18348), (图片,11329))), (22,WrappedArray((国际,28073), (社会,15202), (其他,10292))), (23,WrappedArray((国际,17369), (社会,9436), (其他,8167))))
// topN.saveAsTextFile("hdfs://server/topN")
sc.stop()
}
//自定义分区
class HourPartition() extends Partitioner {
override def numPartitions: Int = 24
override def getPartition(key: Any): Int = key.toString.toInt
}