1. 共享变量
1.1. 概述
所谓共享变量,是为了解决task中使用到外部变量造成相关问题而出现的。spark提供了有限的两种共享变量:广播变量Broadcast变量和累加器Accumulator。
1.2. Broadcast
1.2.1. 使用说明
使用的话,非常简单,只需要将普通的变量包装为Broadcast即可。
val xxBC:Broadcast[T] = sc.broadcast(t);其中T是被包装的变量t的类型。
在transformation中使用的时候,只需要xxBC.value既可以获取被包装的值。
1.2.2. 警告
广播变量不适合处理那些大变量,其二不适合处理那些频繁更新的值。
1.2.3. 案例
object _01BroadVariablesOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("_01BroadVariablesOps")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val list = 1 to 7
val listRDD = sc.parallelize(list)
/*val bs:Int = 3
val bsBC:Broadcast[Int] = sc.broadcast(bs)
listRDD.map(num => num * bsBC.value).foreach(println)
*/
joinOps(sc)
sc.stop()
}
/**
* 使用map或者flatmap来代替join操作
* join操作使用shuffle的
*
* 使用这种方式可以非常高效的完成类似大小表关联的操作,
* 也就是说将reduce的join---->map的join
*
* mr中的多表关联:
* map join
* reduce join
*/
def joinOps(sc: SparkContext): Unit = {
val stuMap = List(//小表
"1 刘梦男 22 bd-1901-bj",
"2 常国龙 25 bd-1901-bj",
"3 张湟熹 24 bd-1901-sz",
"4 胡盼盼(男) 18 bd-1901-wh"
).map(stuLine => {
val sid = stuLine.substring(0, 1)
val info = stuLine.substring(1).trim
(sid, info)
}).toMap
//创建广播变量
val stuBC:Broadcast[Map[String, String]] = sc.broadcast(stuMap)
//大表
val scores = List(
"1 1 math 82",
"2 1 english 0",
"3 2 chinese 85.5",
"4 3 PE 99",
"5 10 math 99"
)
val scoreRDD: RDD[String] = sc.parallelize(scores)
scoreRDD.map(scoreLine => {
val fields = scoreLine.split("\\s+")
val bcMap = stuBC.value
val sid = fields(1)
val info = bcMap.getOrElse(sid, null)
s"$sid\t$info\t${fields(2)}\t${fields(3)}"
}).foreach(println)
}
}
1.3. Accumulator
1.3.1. 使用说明
累加器的入口也是SparkContext,操作非常简单,通过sc.accumulator(初始化的值),返回值就是一个累加器,累加器允许的操作就是++(add)。并且我们应该在drive去获取对应的累加结果,
1.3.2. 警告
在指定累加器的时候可以根据需要指定一个累加器名称,方面我们可以在web-ui/4040页面查看具体信息,如果没有指定累加器名称,查看不到具体的累加信息。
累加器的执行,必须要使用action去触发,也就是说累加器的操作必须要在transformation来累加。
累加器值的调用,应该要在action之后。
多次触发action操作,可能会造成累加器的多次执行,所以需要做到一旦调用完累加器做好累加器数据的重置。
1.3.3. 案例
在将所有结果输出到文件的同时,额外的统计is和that出现了多少次,并将结果打印到控制台。
val conf = new SparkConf()
.setAppName("_02AccumulatorOps")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val lines = sc.textFile("file:///E:/work/1901-bd/workspace/spark-study-1901/data/1行数据.txt")
//统计is 和that出现多少次,然后将结果打印到控制台
val pairs = lines.flatMap(_.split("\\s+")).map((_, 1))
val ret = pairs.reduceByKey(_+_)
ret.filter{case (word, count) => {
word == "is" || word == "that"
}}.foreach(println)
ret.saveAsTextFile("file:///E:/data/out/accu")
Thread.sleep(100000L)
sc.stop()
经过这个操作我们触发了两次job
spark中为了解决这个问题,提供了一个累加器的操作。
object _02AccumulatorOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("_02AccumulatorOps")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val accu = sc.longAccumulator("isAccu")
val lines = sc.textFile("file:///E:/work/1901-bd/workspace/spark-study-1901/data/1行数据.txt")
//统计is 和that出现多少次,然后将结果打印到控制台
val pairs = lines.flatMap(_.split("\\s+"))
.map(word => {
if(word == "is" || word == "that") {
accu.add(1)
}
(word, 1)
})
val ret = pairs.reduceByKey(_+_)
ret.saveAsTextFile("file:///E:/data/out/accu")
println("累加的结果:" + accu.value)
Thread.sleep(100000L)
sc.stop()
}
}
问题说明:
object _02AccumulatorOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("_02AccumulatorOps")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val accu = sc.longAccumulator("isAccu")
val lines = sc.textFile("file:///E:/work/1901-bd/workspace/spark-study-1901/data/1行数据.txt")
//统计is 和that出现多少次,然后将结果打印到控制台
val pairs = lines.flatMap(_.split("\\s+"))
.map(word => {
if(word == "is" || word == "that") {
accu.add(1)
}
(word, 1)
})
val ret = pairs.reduceByKey(_+_)
println("触发之前的累加的结果:" + accu.value)
ret.saveAsTextFile("file:///E:/data/out/accu")
println("触发之后累加的结果:" + accu.value)
accu.reset()
ret.saveAsTextFile("file:///E:/data/out/accu1")
println("触发之后累加的结果2:" + accu.value)
Thread.sleep(100000L)
sc.stop()
}
}
Spark提供的累加器变量,Task(executor)只有增加的权利,只有Driver才能获得累加器变量的值;而且累加器变量的值只有再执行一次Action算子(map,foreach等)操作之后才会更新,这就意味着如果想获得一个准确的累加值,必须保证在获取到累加值之前只执行了一次Action操作。
多次调用出现重复累加的现象:
object _03SparkAccumuatorOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_03SparkAccumuatorOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val list = List(
"hello you",
"i hate you",
"i miss you",
"i love you",
"fuck you"
)
val words = sc.parallelize(list).flatMap(_.split("\\s+"))
/**
* //求多个word,you,i
* 为了完成上述操作,需要在原始操作基础之上,额外的提交一个spark作业
* 就需要额外的分配一批资源,就有可能有数据在网络中进行传输
* 效率不会特别高
* 此时就有这个一个共享变量——累加器
*/
// words.filter(word => word == "hello").count()
// val ab = Array("hello", "i", "you")
// words.filter(word => ab.contains(word)).map((_, 1)).reduceByKey(_+_)
//在计算words的过程中统计you出现了多少次
//创建累加器 选择无参版本,我们无法再sparkUI上面观察到相关的累加器的值
val acc = sc.longAccumulator("helloAcc")
val pairs = words.map(word => {
if(word == "you") {
acc.add(1)
}
(word, 1)
})
println("you出现的次数:" + acc.value + ", action执行之前")
println("pairs中总共有多少条记录:" + pairs.count())
println("you出现的次数:" + acc.value + ", action执行之后")
acc.reset()//重置累加器的值
1.3.4. 自定义累加器
/*
AccumulatorV2[IN, OUT]
IN代表的是执行累加器add加入的指的类型
OUT代表的是执行累加器value返回值的类型
本例中IN使用String,其实代表的就是对应得要累加的字符串word
返回值,不会是每一个word的次数k-v
*/
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] {
var map = mutable.Map[String, Int]()
//当前累加器是否有初始化的值
override def isZero: Boolean = true
//重置该累加器的值
override def reset(): Unit = map.clear()
//获取累加器的值
override def value: mutable.Map[String, Int] = map
//拷贝累加器
override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
val mAccu = new MyAccumulator
mAccu.map = this.map
mAccu
}
/**
* 进行累加的操作
* you
* hate
*/
override def add(word: String): Unit = {
// val vOption = map.get(word)
// var count = 1
/*if(vOption.isDefined) {//map中已经存在该word
map.put(word, 1 + vOption.get)
} else {//map中没有该word
map.put(word, 1)
}*/
/*if(vOption.isDefined) {
count += vOption.get
}
map.put(word, count)*/
map.put(word, map.getOrElse(word, 0) + 1)
}
//合并其他task中的对应累加器的值
override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
val otherMap = other.value
for ((word, count) <- otherMap) {
map.put(word, map.getOrElse(word, 0) + count)
}
}
}
使用:
/**
* 要处理哪些可能需要累加多个不同之的操作,是使用一个累加器解决不了的问题
* 自定义:
* 1、写一个类 extends某个类/trait
* 2、复写其中的方法
* 3、注册使用
*/
object _04SparkAccumuatorOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_04SparkAccumuatorOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val list = List(
"hello you",
"i hate you",
"i miss you",
"i love you",
"fuck you"
)
val words = sc.parallelize(list).flatMap(_.split("\\s+"))
//自定义累加器
val mAccu = new MyAccumulator
sc.register(mAccu, "sparkAccu")
val pairs = words.map(word => {
if(word == "you" || "hate" == word) {
mAccu.add(word)
}
(word, 1)
})
pairs.count()
println("you出现的次数:" + mAccu.value + ", action执行之后")
sc.stop()
}
}
2. 高级排序
2.1. 普通排序
2.1.1. sortByKey
/**
* Spark普通的排序
* sortByKey(只能处理那些[K, V]) 按照key进行排序
* sortBy(底层还是sortByKey)(可以处理没有key的数据)
*
*/
object _01SparkSortOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_01SparkSortOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val list = List(
"hello you",
"i hate you",
"i miss you",
"i love you",
"fuck you"
)
val words = sc.parallelize(list).flatMap(_.split("\\s+"))
val ret = words.map((_, 1)).reduceByKey(_+_)
/*
sortByKey接收两个参数:
ascending: Boolean = true
默认升序排序,false为降序
numPartitions:默认值为master中提供
参与排序操作的分区的个数
这是一个局部排序,也就是是分区内有序,分区间无序
但是如果想进行全局排序,所以就只能将numPartitions设置为1,但是风险很大,容易出现OOM异常
*/
// ret.sortByKey(numPartitions = 1).foreach(println)//按照word排序
ret.map{case (key, count) => (count, key)}//按照count进行排序
.sortByKey(false, 1)
.map{case (count, key) => (key, count)}
.foreach(println)
sc.stop()
}
}
2.1.2 sortBy
object _01SparkSortOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_01SparkSortOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val list = List(
"hello you",
"i hate you",
"i miss you",
"i love you",
"fuck you"
)
val words = sc.parallelize(list).flatMap(_.split("\\s+"))
val ret = words.map((_, 1)).reduceByKey(_+_)
/*
sortBy --->count
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K])
*/
var sortedRDD:RDD[(String, Int)] = ret.sortBy(t => t._2, true, 1)(
new Ordering[Int](){
override def compare(x: Int, y: Int) = {
y.compareTo(x)
}
},
ClassTag.Int.asInstanceOf[ClassTag[Int]]
)
sortedRDD.foreach(println)
println("------------------^_^-----------------------")
sortedRDD = ret.sortBy(t => t, true, 1)(
new Ordering[(String, Int)](){
override def compare(x: (String, Int), y: (String, Int)) = {
var ret = y._2.compareTo(x._2)
if(ret == 0) {
ret = y._1.compareTo(x._1)
}
ret
}
},
ClassTag.Object.asInstanceOf[ClassTag[(String, Int)]]
)
sortedRDD.foreach(println)
sc.stop()
}
2.2. TopN
就是获取集合中的前N个值。
take(n),要想有序,可以使用sortBy/sortByKey之后再获取,或者只用takeOrdered()来完成。
参见昨天的transformation–take
2.3. 二次排序
所谓二次排序,那就是按照两(多)列进行排序,如果第一列排序结果相同,就开始考虑第二列排序。
具体可以参见上述sortBy,但是如果要基于sortByKey来进行二次排序呢?
使用自定义的对象来实现二次排序:
object _02SparkSecondSortOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_01SparkSortOps.getClass.getSimpleName}")
.setMaster("local[2]")
// .set("spark.serializer", classOf[KryoSerializer].getName)
// .registerKryoClasses(Array(classOf[MySecondSort]))
val sc = new SparkContext(conf)
val lines = sc.textFile("file:///E:/data/spark/secondsort.csv")
//先按照第一列排序,第一列相同再按照第二列排序
val pairs:RDD[(MySecondSort, Int)] = lines.map(line => {
val fields = line.split("\\s+")
val mss = new MySecondSort(fields(0).toInt, fields(1).toInt)
(mss, 1)
})
pairs.sortByKey(numPartitions = 1).foreach{case (mss, c) => println(mss)}
// pairs.foreach(println)
sc.stop()
}
}
/**
* NotSerializableException: com.desheng.bigdata.job.p3.sort.MySecondSort
*/
class MySecondSort extends Comparable[MySecondSort] with Serializable {
var first:Int = _
var second:Int = _
def this(first:Int, second:Int) {
this()
this.first = first
this.second = second
}
override def toString: String = first + "\t" + second
override def compareTo(other: MySecondSort) = {
var ret = this.first.compareTo(other.first)
if(ret == 0) {
ret = other.second.compareTo(this.second)
}
ret
}
}
2.4. 分组TopN
在mr、hive中投处理过的操作,分组的topn
比如要从10个文件,每个文件都有100w个数字,找出最大的10数字。
比如有很多部分,比如研发部、设计部、市场部、行政部等等,要求找出每个部分年龄最小的三个小姐姐。
这就是分组TopN的问题。
object _03SparkGroupTopNOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_01SparkSortOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val lines = sc.textFile("file:///E:/data/spark/topn.txt")
val course2Info:RDD[(String, String)] = lines.map(line => {
val fields = line.split("\\s+")
val course = fields(0)
val name = fields(1)
val score = fields(2)
(course, s"$name|$score")
})
//就需要将每门课程的所有信息弄到一起才能排序
val course2Infos:RDD[(String, Iterable[String])] = course2Info.groupByKey()
/*
排序
k,是科目
v:该科目对应的所有的成绩信息
经过排序之后返回三个人的成绩信息,还是一个集合
[k, Iterable[String]] --> [k, Iterable[String]]只不过后面Iterable[String]的size为3
还是one-2-many的操作
one-2-one--->map
*/
val top3:RDD[(String, mutable.TreeSet[String])] = course2Infos.map{case (course, infos) => {
var top3Infos = mutable.TreeSet[String]()(new Ordering[String](){
//name|score
override def compare(x: String, y: String) = {
val xScore = x.substring(x.indexOf("|") + 1).toInt
val yScore = y.substring(y.indexOf("|") + 1).toInt
var ret = xScore.compareTo(yScore)
if(ret == 0) {
1
} else {
ret
}
}
})
//排序的操作 top3Infos是有序的,但是最后只要3个
// top3Infos.dropRight(top3Infos.size - 3)
for(info <- infos) {
top3Infos.add(info)
if(top3Infos.size > 2) {
top3Infos = top3Infos.dropRight(1)
}
}
(course, top3Infos)
}}
top3.foreach{case (course, infos) => {
println(s"$course---->$infos")
}}
/*
english---->TreeSet(ww|56, ys|67, mz|77, ts|87, zq|88, gk|96)
chinese---->TreeSet(sj|74, zl|76, zs|90, ls|91, wb|95, yj|98)
*/
sc.stop()
}
}
升级:
因为groupByKey的性能太差了,所以需要使用combineByKey模拟,怎么?
第一步,将groupByKey的处理方式,转化为combineByKey
object _04SparkGroupTopNOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_04SparkGroupTopNOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val lines = sc.textFile("file:///E:/data/spark/topn.txt")
val course2Info:RDD[(String, String)] = lines.map(line => {
val fields = line.split("\\s+")
val course = fields(0)
val name = fields(1)
val score = fields(2)
(course, s"$name|$score")
})
//就需要将每门课程的所有信息弄到一起才能排序
val course2Infos:RDD[(String, ArrayBuffer[String])] = course2Info
.combineByKey(createCombiner, mergeValue, mergeCombiners)
val top3:RDD[(String, mutable.TreeSet[String])] = course2Infos.map{case (course, infos) => {
var top3Infos = mutable.TreeSet[String]()(new Ordering[String](){
//name|score
override def compare(x: String, y: String) = {
val xScore = x.substring(x.indexOf("|") + 1).toInt
val yScore = y.substring(y.indexOf("|") + 1).toInt
var ret = xScore.compareTo(yScore)
if(ret == 0) {
1
} else {
ret
}
}
})
//排序的操作 top3Infos是有序的,但是最后只要3个
// top3Infos.dropRight(top3Infos.size - 3)
for(info <- infos) {
top3Infos.add(info)
if(top3Infos.size > 2) {
top3Infos = top3Infos.dropRight(1)
}
}
(course, top3Infos)
}}
top3.foreach{case (course, infos) => {
println(s"$course---->$infos")
}}
/*
english---->TreeSet(ww|56, ys|67, mz|77, ts|87, zq|88, gk|96)
chinese---->TreeSet(sj|74, zl|76, zs|90, ls|91, wb|95, yj|98)
*/
sc.stop()
}
def createCombiner(info:String):ArrayBuffer[String] = {
val ab = ArrayBuffer[String]()
ab.append(info)
ab
}
def mergeValue(ab:ArrayBuffer[String], info:String):ArrayBuffer[String] = {
ab.append(info)
ab
}
def mergeCombiners(ab1:ArrayBuffer[String], ab2:ArrayBuffer[String]):ArrayBuffer[String] = {
ab1 ++ ab2
}
}
第二步:
第一步和普通的groupByKey并没有什么两样,性能亦然很差,没有没有本地预聚合,所以在重写的时候做一下本地的top3,这样最后做分区间的top3的时候,每个分区最多提供3条记录,这样在网络中传输的数据量少很多,性能得到了提升。
object _05SparkGroupTopNOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_05SparkGroupTopNOps.getClass.getSimpleName}")
.setMaster("local[2]")
val sc = new SparkContext(conf)
val lines = sc.textFile("file:///E:/data/spark/topn.txt")
val course2Info:RDD[(String, String)] = lines.map(line => {
val fields = line.split("\\s+")
val course = fields(0)
val name = fields(1)
val score = fields(2)
(course, s"$name|$score")
})
//就需要将每门课程的所有信息弄到一起才能排序
val course2Infos:RDD[(String, mutable.TreeSet[String])] = course2Info
.combineByKey(createCombiner, mergeValue, mergeCombiners)
course2Infos.foreach{case (course, infos) => {
println(s"$course---->$infos")
}}
/*
english---->TreeSet(ww|56, ys|67, mz|77, ts|87, zq|88, gk|96)
chinese---->TreeSet(sj|74, zl|76, zs|90, ls|91, wb|95, yj|98)
*/
sc.stop()
}
def createCombiner(info:String):mutable.TreeSet[String] = {
val ab = mutable.TreeSet[String]()(new Ordering[String](){
//name|score
override def compare(x: String, y: String) = {
val xScore = x.substring(x.indexOf("|") + 1).toInt
val yScore = y.substring(y.indexOf("|") + 1).toInt
var ret = xScore.compareTo(yScore)
if(ret == 0) {
1
} else {
ret
}
}
})
ab.add(info)
ab
}
//分区内的排序,并获取top3
def mergeValue(ab:mutable.TreeSet[String], info:String):mutable.TreeSet[String] = {
ab.add(info)
if(ab.size > 3) {
ab.take(3)
} else {
ab
}
}
//分区间合并的时候,获取top3
def mergeCombiners(ab1:mutable.TreeSet[String], ab2:mutable.TreeSet[String]):mutable.TreeSet[String] = {
for(info<- ab2) {
ab1.add(info)
}
if(ab1.size > 3) {
ab1.take(3)
} else {
ab1
}
}
}
作业:
简答题:比较mr和spark的区别。
3.常见java关键字:
**native:**一个方法被native修饰,那么说明该方法就不属于java体系,是由c/c++编写的,在java中被调用而已。java中专门一个规范来处理和底层操作系统的交互–JNI(java native interface)。
**transient:**瞬时的、短暂的。一旦某个变量被transient所修饰,就意味着该变量不会被序列化。有啥用,实现了有选择的序列化!比如在客户端和服务端进行交互,支付过程需要将用户的敏感信息不序列化,以确保用户数据之安全,所以此时敏感信息就应该要使用transient修饰。
volatile:解决的问题,就是共享数据可见的问题,作用相当于一把轻量级的锁。
**Atomic:**原子性操作
AtomicInteger,AtomicLong、AtomicBoolean,底层没有使用锁,但是基于一个CAS算法实现了原子性操作。
代码:
public class AtomicOps {
public static void main(String[] args) {
AtomicThread vt = new AtomicThread();
for (int i = 0; i < 10; i++) {
new Thread(vt).start();
}
}
}
class AtomicThread implements Runnable {
private AtomicInteger number = new AtomicInteger();
@Override
public void run() {
// synchronized (AtomicThread.class) {
try {
Thread.sleep(200);
System.out.println(number.getAndIncrement());//i++
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}