上节课回顾:
(1)Spark on YARN 是不需要启动spark的任何东西的,找台机器布一下Spark就好提交作业。
(2)Spark on YARN的俩种模式的区别。取决于Driver跑在哪里,跑在client就local模式,跑在AM就是yarn
(3)Spark shell不能是cluster模式,当他使用cluster模式,是不是代表了Driver在cluster集群里面,那他和executor如何通信发代码?所以是不行的。
1.collect和take
(1)作用:将数据输出控制台,但是这个德慎用,因为,collect将数据存到driver端,如果数据量太大了,就会出现OOM。
driver的memory默认是一个1G,所以它是由弊端的。可以自己设置,--driver-memory
(2)类似函数:countBykey(求key的数量)countByvaluey(求value的数量)collectAsmap(返回键值对,要是Tulpe)
(3)take是部分扫,不是全部遍历,所以当你不知道数据有多少的时候,就用take抽样数据。
2..count
当你不确定文件的大小时,可以用conut的检测,多大就保存得到文件系统里面去。
3.groupBykey和reduceBykey的区别
reduceBykey:在map端先做预聚合,在shuffer中是聚合之后的在shuffer。数据量小一点。类似于MR里面的combiner
(a,1) ---->(a,1) 预聚合 (b,1) -- shuffle->(a,1) (a,2)
groupBykey:是直接到shuffer端聚合,shuffer的数据量大一点,效率也会更加低一点。
(a,1) ----shuffle-> (a,1) (a,1) (a,1)
4.共享变量
(1)默认情况下,如果在一个算子的函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。如果多个task想要共享某个变量,那么这种方式是做不到的。
Spark为此提供了两种共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点(executor)拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。
(2)Accumulators:主要用于多个节点对一个变量进行共享性的操作。Accumulator只提供了累加的功能。但是确给我们提供了多个task对一个变量并行操作的功能。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。
package SparkReview.Core4
import org.apache.spark.{SparkConf, SparkContext}
object Boradcast {
def main(args: Array[String]): Unit = {
val sparkconf=new SparkConf().setAppName("Boradcast").setMaster("local[2]")
val sc=new SparkContext(sparkconf)
val accum=sc.longAccumulator("My Accumulator")
sc.parallelize(Array(1,2,3,2,4,5)).foreach(x=>
accum.add(x)
) //这里foreach换成map为啥不行?
println(accum.value) //这就是一个累加,在生产中需要用它计数成功了多少条,失败了多少条。
//由于在map等算子内部使用了外部定义的变量和函数,从而引发Task未序列化问题。
// 然而,Spark算子在计算过程中使用外部变量在许多情形下确实在所难免,比如在filter算子根据外部指定的条件进行过滤,
// map根据相应的配置进行变换等。为了解决上述Task未序列化问题,这里对其进行了研究和总结。
sc.stop()
}
}
(3)Broadcast Variables:会将使用到的变量,仅仅为每个节点(executor)拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。不然就只能每个task复制一个,task肯定比executor多很多的
package SparkReview.Core4
import org.apache.spark.{SparkConf, SparkContext}
object BoradCastJoinApp {
def main(args: Array[String]): Unit = {
val sparkConf=new SparkConf().setAppName("BoradCastJoinApp").setMaster("local[2]")
val sc=new SparkContext(sparkConf)
// commonJoin(sc)
BroadcastJoin(sc)
sc.stop()
}
def commonJoin(sc:SparkContext):Unit={
val peopleInfo=sc.parallelize(Array(("G301","糊涂虫"),("G302","僧地"),("G303","Gordon"))).map(x=>(x._1,x))
val peopleDetail=sc.parallelize(Array(("G301","清华大学",18))).map(x=>(x._1,x))
//TODO...大表Join小标 join key
//peopleInfo.join(peopleDetail).foreach(println)//(G301,((G301,糊涂虫),(G301,清华大学,18))).结果明显不大好看
peopleInfo.join(peopleDetail).map(x=>{
x._1+","+x._2._1._2+","+x._2._2._2+","+x._2._2._3 //在去多个字段的时候一定要写好注释,不然一觉醒来就忘干净了
}).foreach(println)
}
def BroadcastJoin(sc:SparkContext):Unit={
val peopleInfo=sc.parallelize(Array(("G301","糊涂虫"),("G302","僧地"),("G303","Gordon"))).map(x=>(x._1,x))
.collectAsMap() //广播变量在大多数情况下都要转成collectAsMap
//DAG图是生活中必须要懂的
val peopleDetail=sc.parallelize(Array(("G301","清华大学",18))).map(x=>(x._1,x))
//通过SparkContext将变量广播出去
val people=sc.broadcast(peopleInfo)
peopleDetail.mapPartitions(x=>{
val map=people.value //得到广播变量的值
for ((key,value)<-x if (map.contains(key))) //
yield (key,map.get(key).getOrElse(""),value._2,value._3) //下面那段话的意义就是,for 循环中的 yield 会把当前的元素记下来,
// 保存在集合中,循环结束后将返回该集合。Scala 中 for 循环是有返回值的。如果被循环的是 Map,
// 返回的就是 Map,被循环的是 List,返回的就是 List,以此类推。
}).foreach(println)
}
}
5.shuffle和closures
(1)shuffle:它必须从所有分区中读取,以找到所有键的所有值,然后将跨分区的值组合在一起,以计算每个键的最终结果,这就叫shuffle。
(2)closures:跨作用域访问函数变量,又指的一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
package SparkReview.Core4
import org.apache.spark.{SparkConf, SparkContext}
object Closures {
def main(args: Array[String]): Unit = {
val sparkconf=new SparkConf().setMaster("local[2]").setAppName("Closures")
val sc=new SparkContext(sparkconf)
val rdd=sc.parallelize(List(1,2,3))
var counner=0 //val 不可变, var可变
//这个函数是在main函数里面的,这个是在Driver端运行的
rdd.foreach(x =>counner += x) //这个foreach函数是操作RDD的 ,也就是在worker节点上具体运行的
println("Counter value :"+ counner ) //输出结果为0
//当worker上的函数调用外面的变量时,就出现了闭包的问题,这时候就出现了广播的概念。
sc.stop()
}
}