一、自定义累加器(Accumulator)

自定义累加器,可以任意累加不同类型的值,同时也可以在内部进行计算,或者逻辑编写,如果继承自定义累加器,那么需要实现内部的抽象方法,然后在每个抽象方法内部去累加变量值即可,主要是在全局性累加起到决定性作用。

累加器作为spark的一个共享变量的实现,在用于累加计数计算计算指标的时候可以有效的减少网络的消耗

累加器可以在每个节点上面进行Task的值,累加操作,有一个Task的共享性操作

新版累加器使用步骤: 1. 创建累加器 2. 注册累加器 3. 使用累加器

1、类继承extends AccumulatorV2[String, String],第一个为输入类型,第二个为输出类型
2、覆写抽象方法:

isZero: 当AccumulatorV2中存在类似数据不存在这种问题时,是否结束程序。
copy: 拷贝一个新的AccumulatorV2
reset: 重置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
}