每日福利来一个。
没有看前面的同学可以回顾一下:
5.RDD常用算子用法训练(附习题答案)(aggregateByKey与combineByKey)!!!
4.Spark Rdd常用算子和RDD必备知识
3.spark core 核心知识
2.spark 之 wordcount入门
1.spark 入门讲解
在 Spark 程序中,当一个传递给 Spark 操作(例如 map 和 reduce)的函数在远程节点上面运行 时,Spark 操作实际上操作的是这个函数所用变量的一个独立副本。这些变量会被复制到每台机器上,并且这些变量在远程机器上的所有更新都不会传递回驱动程序。通常跨任务的读 写变量是低效的,但是,Spark 还是为两种常见的使用模式提供了两种有限的共享变量:
广播变(Broadcast Variable)和累加器(Accumulator)
如果我们要在分布式计算里面分发大对象,例如:字典,集合,黑白名单等,这个都会由 Driver 端进行分发,一般来讲,如果这个变量不是广播变量,那么每个 task 就会分发一份, 这在 task 数目十分多的情况下 Driver 的带宽会成为系统的瓶颈,而且会大量消耗 task 服务 器上的资源,如果将这个变量声明为广播变量,那么知识每个 executor 拥有一份,这个 executor 启动的 task 会共享这个变量,节省了通信的成本和服务器的资源。
如果没有使用广播变量如下图:
使用了广播变量之后:
//定义:
val a = 3
val broadcast = sc.broadcast(a)
//还原:
val c = broadcast.value
//注意:变量一旦被定义为一个广播变量,那么这个变量只能读,不能修改
注意事项 :
1、能不能将一个 RDD 使用广播变量广播出去? 不能,因为 RDD 是不存储数据的。可以将 RDD 的结果广播出去。
2、广播变量只能在 Driver 端定义,不能在 Executor 端定义。
3、在 Driver 端可以修改广播变量的值,在 Executor 端无法修改广播变量的值。
4、如果 executor 端用到了 Driver 的变量,如果不使用广播变量在 Executor 有多少 task 就有 多少 Driver 端的变量副本。
5、如果 Executor 端用到了 Driver 的变量,如果使用广播变量在每个 Executor 中都只有一份 Driver 端的变量副本。
在 Spark 应用程序中,我们经常会有这样的需求,如异常监控,调试,记录符合某特性的数 据的数目,这种需求都需要用到计数器,如果一个变量不被声明为一个累加器,那么它将在 被改变时不会在 driver 端进行全局汇总,即在分布式运行时每个 task 运行的只是原始变量的 一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分 布式计数的功能。
图解累加器:
正确的图解:
定义累加器:
val a = sc.longAccumulator(0)
还原累加器:
val b = a.value
注意事项
1、累加器在 Driver 端定义赋初始值,累加器只能在 Driver 端读取最后的值,在 Excutor 端更 新。
2、累加器不是一个调优的操作,因为如果不这样做,结果是错的
累加器代码案例:
object _03SparkRDDAccumulatorOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.project-spark").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_03SparkRDDAccumulatorOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
//创建一个累加器
val countAccu = sc.longAccumulator("localAccu")
val linesRDD:RDD[String] = sc.textFile("data/10w-line-data.txt")
val wordsRDD:RDD[String] = linesRDD.flatMap(_.split("\\s+"))
val pairsRDD:RDD[(String, Int)] = wordsRDD.map(word => {
if (word == "local") {
countAccu.add(1L)
}
(word, 1)
})//.map((_, 1))
val rbkRDD:RDD[(String, Int)] = pairsRDD.reduceByKey(_+_)
rbkRDD.foreach(println)
// val count = wordsRDD.filter(word => word == "local").count()
println("countAccu1=" + countAccu.value)
countAccu.reset()
println("单词个数:" + pairsRDD.reduceByKey(_+_).count())
println("countAccu2=" + countAccu.value)
// countAccu.
Thread.sleep(100000)
sc.stop()
}
}
object _02SparkRDDBroadcastOps {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.project-spark").setLevel(Level.WARN)
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName(s"${_02SparkRDDBroadcastOps.getClass.getSimpleName}")
val sc = new SparkContext(conf)
val stu = List(
"1 郑祥楷 1",
"2 王佳豪 1",
"3 刘鹰 2",
"4 宋志华 3",
"5 刘帆 4",
"6 OLDLi 5"
)
val cls = List(
"1 1807bd-bj",
"2 1807bd-sz",
"3 1807bd-wh",
"4 1807bd-xa",
"7 1805bd-bj"
)
// joinOps(sc, stu, cls)
/*
使用广播变量来完成上述操作
一般用户表都比较大,而班级表相对很小,符合我们在共享变量中提出的第一个假设
所以我们可以尝试使用广播变量来进行解决
*/
val stuRDD = sc.parallelize(stu)
//cls-->map---->
val map = cls.map{case line => {
(line.substring(0, line.indexOf(" ")), line.substring(line.indexOf(" ")).trim)
}}.toMap
//map--->broadcast
val clsMapBC:Broadcast[Map[String, String]] = sc.broadcast(map)
stuRDD.map{case line => {
val map = clsMapBC.value
val fields = line.split("\\s+")
val cid = fields(2)
// map.get(cid)
val className = map.getOrElse(cid, "UnKnown")
s"${fields(0)}\t${fields(1)}\t${className}"//在mr中学习到的map join
}}.foreach(println)
sc.stop()
}
private def joinOps(sc: SparkContext, stu: List[String], cls: List[String]) = {
val stuRDD = sc.parallelize(stu)
val clsRDD = sc.parallelize(cls)
val cid2STURDD: RDD[(String, String)] = stuRDD.map { case line => {
val fields = line.split("\\s+")
(fields(2), line)
}
}
val cid2ClassRDD: RDD[(String, String)] = clsRDD.map { case line => {
val fields = line.split("\\s+")
(fields(0), fields(1))
}
}
//两张表关联--join
println("---------inner join-------------")//reduce join
val cid2InfoRDD: RDD[(String, (String, String))] = cid2STURDD.join(cid2ClassRDD)
cid2InfoRDD.foreach(println)
}
}
需要注意的地方:
1.累加器的值得获取只能在driver端来进行,在transformation中不建议获取。
2.累加器的执行,必须要依赖一个action的触发,而且累加器值的获取只能在该action触发之后获取。
3.重复触发相关action,会造成累加器的值重复计算,所以在使用过程中要尽量避免。(可以通过累加器的重置解决该问题,accumulator.reset() )
4.如果这些累加器满足不了需求,请尝试自定义累加器。
自定义累加器需要继承AccumulatorParam,实现addInPlace和zero方法。
例1:实现Long类型的累加器
object LongAccumulatorParam extends AccumulatorParam[Long]{
override def addInPlace(r1: Long, r2: Long) = {
println(s"$r1\t$r2")
r1 + r2
}
override def zero(initialValue: Long) = {
println(initialValue)
0
}
def main(args: Array[String]): Unit = {
val sc = new SparkContext(new SparkConf().setAppName("testLongAccumulator"))
val acc = sc.accumulator(0L, "LongAccumulator")
sc.parallelize(Array(1L,2L,3L,4L,5L)).foreach(acc.add)
println(acc.value)
sc.stop()
}
}
记得点赞加关注!!! 不迷路 !!!
人活着真累:上车得排队,爱你又受罪,吃饭没香味,喝酒容易醉,挣钱得交税!