自定义排序
定义类,实现序列化和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())
})
}
}