spark----自定义排序 分组TopN 类加器 序列化问题 Task线程安全问题 spark程序反面教材

本文介绍了三种在Spark中对RDD进行自定义排序的方法,包括使用元组、实现Comparable接口的case class以及借助隐式排序。此外,还讨论了在排序过程中可能出现的线程安全和序列化问题,以及如何利用累加器进行数据统计。同时,展示了如何通过分组和过滤进行数据处理,以及在处理大数据时避免内存溢出的策略。
摘要由CSDN通过智能技术生成

自定义排序

定义类,实现序列化和Comparable/Ordereded接口

隐式转换

借助元组来排序

第一种方法

package cn.doit.spark.day07.demo01

import cn.doit.SparkUtil
import org.apache.spark.rdd.RDD

object CustomSort01 {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()

    val data: RDD[String] = sc.parallelize(List("zss,38,99.9", "ls,48,999.9", "www,38,9999.9"), 2)

    //先按照颜值的降序进行排序,如果颜值相等,再按照年龄的升序进行排序
    val tpRdd: RDD[(String, Int, Double)] = data.map(e => {
      val fields = e.split(",")
      (fields(0), fields(1).toInt, fields(2).toDouble)
    })

    val sorted: RDD[(String, Int, Double)] = tpRdd.sortBy(t => (-t._3, t._2))

    val res = sorted.collect()

    println(res.toBuffer)
  }
}

第二种方法 

package cn.doit.spark.day07.demo01

import cn.doit.SparkUtil
import org.apache.spark.rdd.RDD

object CustomSort02 {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()

    val data: RDD[String] = sc.parallelize(List("zss,38,99.9", "ls,48,999.9", "www,38,9999.9"), 2)

    //先按照颜值的降序进行排序,如果颜值相等,再按照年龄的升序进行排序
    val tpRdd: RDD[Boy] = data.map(e => {
      val fields = e.split(",")
      new Boy(fields(0), fields(1).toInt, fields(2).toDouble)
    })

    val res = tpRdd.sortBy(t => t)
    println(res.collect().toBuffer)
  }


  //可以使用case class,默认实现序列化,默认重写toString
//  class Boy(val name: String, val  age: Int, val fv:Double) extends Ordered[Boy] with Serializable{
//
//    //继承Ordered,重写比较方法
//    override def compare(that: Boy): Int = {
//      if (this.fv == that.fv){
//        this.age - that.age
//      }else {
//        -java.lang.Double.compare(this.fv , that.fv)
//      }
//    }
//    //重写toString
//    override def toString = s"Boy($name, $age, $fv)"
//  }

case class Boy(name: String, age: Int, fv:Double) extends Ordered[Boy]{

    //继承Ordered,重写比较方法
    override def compare(that: Boy): Int = {
      if (this.fv == that.fv){
        this.age - that.age
      }else {
        -java.lang.Double.compare(this.fv , that.fv)
      }
    }
  }
}

第三种方法

package cn.doit.spark.day07.demo01

import cn.doit.SparkUtil
import org.apache.spark.rdd.RDD

object CustomSort03 {

  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()

    val data: RDD[String] = sc.parallelize(List("zss,38,99.9", "ls,48,999.9", "www,38,9999.9"), 2)

    //先按照颜值的降序进行排序,如果颜值相等,再按照年龄的升序进行排序
    val tpRdd: RDD[Boy] = data.map(e => {
      val fields = e.split(",")
      Boy(fields(0), fields(1).toInt, fields(2).toDouble)
    })

    import cn.doit.OrderContext.OrderingBoy
    val sorted: RDD[Boy] = tpRdd.sortBy(t => t)

    val res = sorted.collect()

    println(res.toBuffer)
  }

case class Boy(name: String, age: Int, fv:Double)

}
package cn.doit

import cn.doit.spark.day07.demo01.CustomSort03.Boy

object OrderContext {

  //这是一个Function1类型的函数
//  implicit val manOrdering: Ordering[Any] = (man: Boy) => new Ordering[Boy] {
//
//    override def compare(x: Boy, y: Boy): Int = {
//
//      if (x.fv == y.fv) {
//        x.age - y.age
//      }else {
//        java.lang.Double.compare(y.fv, x.fv)
//      }
//    }
//  }


  //SortBy需要的是一个Ordering类型的隐式参数
  implicit object OrderingBoy extends Ordering[Boy] {
    override def compare(x: Boy, y: Boy): Int = {
      if(x.fv == y.fv) {
        x.age - y.age
      } else {
        java.lang.Double.compare(y.fv, x.fv)
      }
    }
  }
}

分组topN

先分组,toList然后在内存中排序, 每个组中的数据比较大, 可能会产生内存溢出

自定义分区器, 然后在分区内排序, 可以使用TreeSet

先过滤,然后使用RDD排序, 会触发多次Acton

 

累加器

用来统计数据处理的条数

package cn.doit.spark.day07.demo03

import cn.doit.SparkUtil
import org.apache.spark.rdd.RDD
import org.apache.spark.util.LongAccumulator

object AccumulatorDemo01 {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext(true)

    val arr = Array(1,2,3,4,5,6,7,8,9)

    val lines = sc.parallelize(arr, 2)

    //存在闭包问题
    //定义的普通变量是没法将Executor中累加的结果返回到Driver端的
//    var acc = 0
//    val res: RDD[Int] = lines.map(e => {
//      if (e % 2 == 0) {
//        acc += 1
//      }
//      e * 10
//    })
//    println("触发Action之前:" + acc)
//    println(res.collect().toBuffer)
//    println("触发Action之后:" + acc)


    //累加器 longAccumulator  函数内部引用到了外部的一个引用类型
//    val accumulator: LongAccumulator = sc.longAccumulator("even_acc")
    //累加器本质就是在Driver端初始化放一个类的实例,并且在函数内部使用,存在闭包现象,
    //每个task都有自己的一个计数器引用
  val accumulator = new LongAccumulator()
    sc.register(accumulator, "even_acc")

    val res: RDD[Int] = lines.map(e => {
      if (e % 2 == 0) {
        accumulator.add(1)
      }
      e * 10
    })

    res.cache()  //res被反复使用,数据量小使用cache,数据量多使用persist

    res.saveAsTextFile("/acc")

    println("触发Action之前:" + accumulator.value)  //调用count也一样
    println(res.collect().toBuffer)
    println("触发Action之后:" + accumulator.value)
    sc.stop()
  }
}

sc.collectionAccumulator[String]:  数据收集器

package cn.doit.spark.day07.demo03

import cn.doit.SparkUtil
import com.alibaba.fastjson.{JSON, JSONException}
import org.apache.spark.rdd.RDD
import org.apache.spark.util.CollectionAccumulator

/*
    集合累加器(数据收集器)
 */
object AccumulatorDemo02 {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()

    val lst = List("\"name\" : \"laoduan\", \"age\" : 35, \"fv\": 99.99}",
                  "{\"name\" : \"nianhang\", \"age\" : 30, \"fv\": 99.99}",
                  "{\"name\" : \"laozhao\", \"age\" : 34, \"fv\": 9999.99}",
                  "{\"name\" : \"laoduan\", \"age\" : 35, \"fv\": 99.99}",
                  "{\"name\" : \"nianhang\", \"age\" : 30, \"fv\": 99.99}",
                  "{\"name\" : \"laozhao\", \"age\" : 34 \"fv\" 9999.99}")

    val rdd1: RDD[String] = sc.parallelize(lst)

    //使用累加器收集有用的数据
    val acc: CollectionAccumulator[String] = sc.collectionAccumulator[String]("err_data_collector")

    val beanRDD: RDD[Teacher] = rdd1.map(line => {
      var bean: Teacher = null
      try {
        bean = JSON.parseObject(line, classOf[Teacher])
      } catch {
        case e: JSONException => {
          //有问题的数据添加到集合累加器
          acc.add(line)
        }
      }
      bean
    })

    //过滤出不为null的数据
    val filtered: RDD[Teacher] = beanRDD.filter(_ != null)

    val res = filtered.collect()

    //正确的数据
    println(res.toBuffer)
    //有问题的数据
    println(acc.value)

    sc.stop()
  }
}
package cn.doit.spark.day07.demo03

case class Teacher(name: String,
                   age: Int,
                   fv: Double)

 

序列化问题

Dirver实例化一个类的实例, 在函数内部引用了这个实例, 伴随着Task发送到Executor, 每个Task都要一个单例的实例,必须实现序列化接口

如果在Driver端初始化了一个object或new一个class实例, 然后在函数中使用, 必须实现序列化接口

Dirver初始化一个object, 这个单例对象要伴随着Task发送到Executor, 但是一个Executor中只有一份, 必须实现序列化接口, 有可能会出现线程安全问题

在函数内部使用一个object,这个object不用实例化, 这个object在Executor中初始化的, 并且一个Executor中初始化的,并且一个Executor中只有一份,有可能出现线程安全问题

使用mapPartitions和foreachPartition, new一个类的实例, 该类不用实例化, 一个Task中有一份该类的实例

闭包引用Driver端的Object问题 

object在一个Executor中只有一份, 因为object是一个单例对象,一个进程中只能有一个

package cn.doit.spark.day07.demo02
/*
    只有一个实例
 */
object RulesMapObjectNoSer extends Serializable {

  val rulesMap = Map(
    "ln" -> "辽宁省",
    "bj" -> "北京市",
    "sh" -> "上海市",
    "sd" -> "山东省"
  )
}
package cn.doit.spark.day07.demo02

import java.net.InetAddress
import cn.doit.SparkUtil
import org.apache.spark.rdd.RDD

object SerTest {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()
    //数据存储在HDFS上
    //ln,2000 ===> ln,2000,辽宁
    //bj,3000
    //sh,5000
    val lines: RDD[String] = sc.textFile(args(0))

    //在Driver端初始化rulesMapObject
    //所有的task公用一个实例RulesMapObjectNoSer
    val ruleMapObj = RulesMapObjectNoSer   //地址

    //函数的内部使用了外部的一个引用类型(ruleMapObj),存在闭包问题
    //因为RulesMapObjectNoSer没有序列化,会导致task无法序列化,所以RulesMapObjectNoSer要实现序列化
    val result: RDD[(String, String, Double, Long, String, RulesMapObjectNoSer.type)] = lines.map(e => {
      val fields = e.split(",")
      val code = fields(0)
      //价格
      val money = fields(1).toDouble
      //名字,如果有返回名字,没有返回未知
      val name = ruleMapObj.rulesMap.getOrElse(code, "未知")
      //获取当前线程的ID
      val threadId = Thread.currentThread().getId
      //当前主机名(hostname)
      val hostname = InetAddress.getLocalHost.getHostName
      (code, name, money, threadId, hostname, ruleMapObj)
    })

    result.saveAsTextFile(args(1))

    sc.stop()
  }
}

闭包引用Driver端的class问题

new的class实例,一个task独享一个实例,有几个Task就有几个RulesMapClass实例

package cn.doit.spark.day07.demo02

/*
    每个task都持有一个独立的实例
 */
class RulesMapClass extends Serializable {
  val rulesMap = Map(
    "ln" -> "辽宁省",
    "bj" -> "北京市",
    "sh" -> "上海市",
    "sd" -> "山东省"
  )
}
package cn.doit.spark.day07.demo02

import java.net.InetAddress
import cn.doit.SparkUtil
import org.apache.spark.rdd.RDD

object SerTest2 {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext(true)
    val lines: RDD[String] = sc.textFile(args(0))

    //在Driver端初始化rulesMapObject
    //每个Task都持有自己独立的一个实例RulesMapClass
    val ruleMapObj = new RulesMapClass

    //函数的内部使用了挖鼻不的一个引用类型(ruleMapObj),存在闭包问题
    //因为RulesMapObjectNoSer没有序列化,会导致task无法序列化,所以RulesMapObjectNoSer要实现序列化
    val res: RDD[(String, String, Double, Long, String, RulesMapObjectNoSer.type)] = lines.map(e => {
      val fields = e.split(",")
      val code = fields(0)
      //价格
      val money = fields(1).toDouble
      //名字, 如果有返回名字, 没有返回未知
      val name = ruleMapObj.rulesMap.getOrElse(code, "未知")
      //获取当前线程的ID
      val threadId = Thread.currentThread().getId
      //当前主机名
      val hostname = InetAddress.getLocalHost.getHostName
      (code, name, money, threadId, hostname, ruleMapObj)
    })

    res.saveAsTextFile(args(1))

    sc.stop()
  }
}

Task线程安全问题

package cn.doit

import org.apache.commons.lang3.time.FastDateFormat

object DateUtils {

  //线程不安全的日期转换格式
//  val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

  //线程安全的
  val dateFormat: FastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")

  def parse(str: String): Long = {
    //2020-08-15  10:10:12
    val date = dateFormat.parse(str)
    date.getTime
  }
}
package cn.doit.spark.day07.demo02

import cn.doit.{DateUtils, SparkUtil}
import org.apache.spark.rdd.RDD
/*
    2020-08-15 10:10:10
    2020-08-15 10:10:10
    2020-08-15 10:10:12
    2020-08-15 10:10:13
    2020-08-15 10:10:13
    2020-08-15 10:10:13
    2020-08-15 10:10:10
    2020-08-15 10:10:10
    2020-08-15 10:10:12
 */
object ThreadNotSafeDemo {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()

    val lines: RDD[String] = sc.textFile("data/date.txt")

    val ts: RDD[Long] = lines.map(e => {
      DateUtils.parse(e)
    })

    val res = ts.collect()

    println(res.toBuffer)

    sc.stop()
  }
}

spark程序反面教材

package cn.doit.spark.day07.demo03

import cn.doit.SparkUtil

object BadStyle {
  def main(args: Array[String]): Unit = {

    val sc = SparkUtil.createContext()

    val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    val rdd2 = sc.parallelize(List("word", "spark", "hadoop", "word"))

    //map方法是在Driver端调用的
    rdd1.map(e => {
      //rdd2.count是在Executor端调用的
      (e, rdd2.count())
    })
  }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值