自定义排序和序列化的问题案例
import cn._51doit.demo03.SparkUtils
import org.apache.spark.rdd.RDD
object CustomSort01 {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext()
val data: RDD[String] = sc.parallelize(List("laoduan,36,99.99", "laozhao,18,999.99", "nianhang,30,99.99"))
//先按照研制降序进行排序,如果颜值相等,按照年龄的升序进行排序
val tpRdd= data.map(i => {
//将读到的一条一条的数据切割
val fields = i.split(",")
//根据索引放到元组里,注意要格式转换,因为切割后都是字符串类型的
//(fields(0), fields(1).toInt, fields(2).toDouble)
//调用boy一个封装的bean将数据传到bean里//不用new
Boy(fields(0), fields(1).toInt, fields(2).toDouble)
})
//将切割到的数据sortBy排序,指定根据颜值降序,后根据年龄降序
//val sorted: RDD[(String, Int, Double)] = tpRdd.sortBy(t => (-t._3, t._2))
//将他sortBy排序直接用里面写好的排序规则
val sorted = tpRdd.sortBy(i => i)
println(sorted.collect().toBuffer)
}
//封装数据继承Otdered比较器,因为是case class所以不用with Serializable实现序列化(自动实现)也不用toString
//不用写val修饰符因为默认是val的
//如果创建的这个类不是case class也没有继承Serializable无法序列化也会导致这个task无法序列化
case class Boy(name:String ,age : Int , fv :Double)extends Ordered[Boy]{
//重写排序规则
override def compare(that: Boy): Int = {
if(this.fv ==that.fv){
this.age - that.age
}else{
//符号是降序,java.lang类型规定Double类型否则会精度丢失conpare比较
-java.lang.Double.compare(this.fv,that.fv)
}
}
}
}
这是自定义的比较器类用来包装Man和下面的类是一套的
import cn._51doit.demo04.CustomSort03.Man
object OrderContext {
//这边用隐式转换把类包装一下,本质上就是对类进行增强 new一个Ordering比较器里面装Man类型重写比较方法
// 这是一个Function1类型的函数输入的是Man类型返回的是Ordering[Man]类型的函数
//应为返回的函数无法序列化导致这个task无法序列化
// implicit val manOrdering: Man=> Ordering[Man] =(man : Man) => new Ordering[Man]{
// override def compare(x: Man, y: Man): Int = {
// if(x.fv==y.fv){
// x.age - y.age
// }else{
// java.lang.Double.compare(y.fv,x.fv)
// }
// }
// }
//SortBy需要的是一个Ordering类型的隐式参数
implicit object manOrdering extends Ordering[Man] {
override def compare(x: Man, y: Man): Int = {
if (x.fv==y.fv){
x.age - y.age
}else{
java.lang.Double.compare(y.fv,x.fv)
}
}
}
}
import cn._51doit.demo03.SparkUtils
import org.apache.spark.rdd.RDD
object CustomSort03 {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext()
val data: RDD[String] = sc.parallelize(List("laoduan,36,99.99", "laozhao,18,9999.99", "nianhang,30,99.99"))
//先按照颜值的降序进行排序,如果颜值相等,再按照年龄的升序进行排序
val tfMan: RDD[Man] = data.map(e => {
val fields = e.split(",")
//(fields(2).toDouble, fields(1).toInt, fields(0))
//封装到man里
Man(fields(0), fields(1).toInt, fields(2).toDouble)
})
import OrderContext.manOrdering
val sorted: RDD[Man] = tfMan.sortBy(b => b)
println(sorted.collect().toBuffer)
}
case class Man(name: String, age: Int, fv: Double)
}
spark的序列化问题有两种
1.包装类返回的函数无法序列化导致这个task无法序列化
2.如果创建的这个类不是case class也没有继承Serializable无法序列化也会导致这个task无法序列化
Object在一个Excutor只有一份,因为Object是一个单例对象,一个进程中只能有一个new的class是,一个Task独享一个实例,有几个Task就有几个RulesMapClass
如果在Driver初始化了一个Object或new一个class实例,然后再函数中使用,必须实现序列化接口(extends Serializable).否则会报错,这样的话就会来一个new一个会浪费资源,所以要用mapPartitions在函数外面new,这样就是一个Task有一个实例不太占用资源,如果用到一些共享资源既有读又有写的可以用Class
如果是Object就是单例队相就不用实现序列化因为他默认实现序列化并且一个Excutor只用一个实例,而且还不用在Drive端初始化.如果就是只读的可以用Object
这个就是用Class并且用了mapPartitions在函数外面new
import org.apache.spark.rdd.RDD
import utils.SparkUtils
object SerTest05 {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(args(2).toBoolean)
//数据存储在HDFS上
//ln,2000 ===> ln,2000,辽宁
//bj,3000
//sh,5000
val lines: RDD[String] = sc.textFile(args(0))
//不在Driver端初始化
//在Driver端初始化RulesMapClass
//val ruleMapObj = new RulesMapClass
//println("========================================>>>>>>>" + ruleMapObj)
val result = lines.mapPartitions(it => {
//放在map外面new
val rulesMapClass = new RulesMapClassNotSer
it.map(e => {
val fields = e.split(",")
val code = fields(0)
val money = fields(1).toDouble
val name = rulesMapClass.rulesMap.getOrElse(code, "未知")
//获取当前线程的ID
val threadId = Thread.currentThread().getId
//当前主机名(hostname)
val hostname = InetAddress.getLocalHost.getHostName
(code, name, money, threadId, hostname, rulesMapClass)
})
})
result.saveAsTextFile(args(1))
sc.stop()
}
}
task线程安全问题案例
2020-08-15 10:10:10 读于很多个字符串这样文件转化成时间戳
这是自己封装的字符串转换成时间戳的方法
import java.text.SimpleDateFormat
import java.util.Date
import org.apache.commons.lang3.time.FastDateFormat
object DateUtils {
//日期格式化把传过来的2020-08-15 10:10:10字符串转换成日期格式//这个不好线程不安全//可以synchronized加锁只能由一个线程进来但是效率又低了也不好
//如果一定要用这个可以把那边用mapPartitions调用方法在外面
//val dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss")
//用这个方法线程安全
private val dateFormat: FastDateFormat = FastDateFormat.getInstance("yyyy-mm-dd HH:mm:ss")
def parse(str: String):Long={
//调用上面的格式化方法,把日期转换成Date类型的格式
val date = dateFormat.parse(str)
//把Date类型的格式转换成long类型的时间戳20090121000132095572000一长串数字
date.getTime
}
}
这是读文件的主要代码
import cn._51doit.demo03.SparkUtils
import org.apache.spark.rdd.RDD
object ThreadNotSafeDemo {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(true)
//2020-08-15 10:10:10
val lines = sc.textFile("D:\\bbb\\data\\date.txt")
val ts: RDD[Long] = lines.map(e => {
DateUtils.parse(e)
})
val res = ts.collect()
println(res.toBuffer)
sc.stop()
}
}
下面是另外一种用class封装的方法
import java.text.SimpleDateFormat
//这边可以不实现序列化因为那边在调用方法实在计算逻辑在Excutor端
class DateUtilsClass {
//线程不安全的日期转换格式
val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
def parse(str: String): Long = {
//2020-08-15 10:10:12
val date = dateFormat.parse(str)
date.getTime
}
}
import org.apache.spark.rdd.RDD
import utils.{DateUtils, DateUtilsClass, SparkUtils}
object ThreadNotSafeDemo2 {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(true)
val lines: RDD[String] = sc.textFile("data/date.txt")
//这边要用mapPartitions在函数外面new
val ts: RDD[Long] = lines.mapPartitions(it => {
val dateUtils = new DateUtilsClass
it.map(e => {
dateUtils.parse(e)
})
})
val res = ts.collect()
println(res.toBuffer)
sc.stop()
}
}
累加器的使用场景案例
sc.longAccumulator("even_acc") 这是怎么调用累加器的参数是给累加器定义个名字随便写
accumulator.add(1) 这是累加的数值
import cn._51doit.demo03.SparkUtils
import org.apache.spark.util.LongAccumulator
object AccumulatorDemo01 {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(true)
val arr = Array(1,2,3,4,5, 6,7,8,9,10)
val rdd1 = sc.parallelize(arr, 2)
//1.要求先统计一下偶数的数量
//
// val rdd2 = rdd1.filter(e => e % 2 == 0)
// println(rdd2.count())
// //2.再将每一个元素乘以10
// val rdd3 = rdd1.map(_ * 10)
//
// println(rdd3.collect().toBuffer)
//触发一次Action,将要统计的的次数和最终计算好的结果都算出来
// var acc = 0 //定义的普通变量是没法将Executor中累加的结果返回到Driver端的
// val rdd2 = rdd1.map(e => {
// if (e % 2 == 0) {
// acc += 1
// }
// e * 10
// })
//
// println("触发Action之前:" + acc)
// println(rdd2.collect().toBuffer)
// println("触发Action之后:" + acc)
val accumulator: LongAccumulator = sc.longAccumulator("even_acc")
//在Driver端定义的
val func = (e: Int) => {
if (e % 2 == 0) {
accumulator.add(1)
}
e * 10
}
// val func2 = new Function1[Int, Int] {
// override def apply(v1: Int): Int = {
// if (v1 % 2 == 0) {
// accumulator.add(1)
// }
// v1 * 10
// }
// }
//func2()
val rdd2 = rdd1.map(func)
println("触发Action之前:" + accumulator.value)
println(rdd2.collect().toBuffer)
println("触发Action之后:" + accumulator.value)
}
}
下面是累加器的问题,如果触发一次Action累加的数是5,之后再触发因为用的是同一个累加器所以在5的基础上又加了5所以是10,但是如果cache一下就会把之前累加的结果缓存到内存里,在用累加器的时候就发现内存里又这个答案就之接把答案拿过来了所以还是5
import org.apache.spark.util.LongAccumulator
import utils.SparkUtils
object AccumulatorDemo02 {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(true)
val arr = Array(1,2,3,4,5, 6,7,8,9,10)
val rdd1 = sc.parallelize(arr, 2)
//val accumulator: LongAccumulator = sc.longAccumulator("even_acc")
//累加器本质就是在Driver端初始化的一个类的实例,并且在函数内部使用,存在闭包现象,每个task都有自由的一个计数器引用
val accumulator = new LongAccumulator()
sc.register(accumulator, "even_acc")
val rdd2 = rdd1.map(e => {
if (e % 2 == 0) {
accumulator.add(1)
}
e * 10
})
rdd2.cache()
rdd2.saveAsTextFile("data/acc2")
//第一次获取累加器的value
println(accumulator.value)
val res = rdd2.collect()
println(res.toBuffer)
//第二次获取累加器的value
println(accumulator.value)
}
}
import org.apache.spark.rdd.RDD
import utils.SparkUtils
//这是一个反面教材,不能在Transformation或Action中调用RDD再调用RDD的Transformation或Action
object BadStyle {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(true)
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10))
val rdd2: RDD[String] = sc.parallelize(List("word", "spark", "hadoop", "word"))
//map方法是在Driver调用的
val result = rdd1.map(e => {
//(1, 4)
//这个应该在函数外面用因为里面是在Executor端这边没有SparkContext只有在Drive端准备的时候才有所以要放在函数外面货期再里面调用
(e, rdd2.count())//rdd2.count()是在Executor中调用
})
println(result.collect().toBuffer)
}
}