cache,StorageLevel,广播变量,计数器,spark on yarn

RDD持久化

spark重要特性,缓存数据在内存里。当持久化一个RDD的时候,RDD会把所有内存里的分区信息存储下来,这样就能够基于这个数据集做复用在以后的action里。

实验

scala> var info =sc.textFile("file:///home/hadoop/data/page_views.dat")
scala> info.count
res0: Long = 100000  

在这里插入图片描述
再执行一次
在这里插入图片描述
发现还是一样的大小,说明数据还是从头加载的

使用cache

注意cache是lazy的,遇到action才计算
但是注意cache在rdd里lazy,在sparkSQL不是
cache是容错的,如果rdd的分区丢失,会自动计算回来。

scala> info.cache
scala> info.count

发现缓存成功,相当于把action后的数据缓存起来了
在这里插入图片描述

再次执行

scala> info.count

在这里插入图片描述
惊了,出入反而变得更大了

cache源码

那么为什么变大了呢,查看下源码

在RDD.scala里搜索cache(快捷键ctrl+F12)
在这里插入图片描述
cache() 调用persist(),persist调用重载的persist(StorageLevel.MEMORY_ONLY),默认使用只内存的存储策略

查看存储策略StorageLevel源码

object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

   /**
   * :: DeveloperApi ::
   * Create a new StorageLevel object.
   */
  @DeveloperApi
  def apply(
      useDisk: Boolean,
      useMemory: Boolean,
      useOffHeap: Boolean,
      deserialized: Boolean,
      replication: Int): StorageLevel = {
    getCachedStorageLevel(
      new StorageLevel(useDisk, useMemory, useOffHeap, deserialized, replication))
  }
  

StorageLevel里面的参数

private var _useDisk: Boolean,
private var _useMemory: Boolean,
private var _useOffHeap: Boolean,  //对外,外部存储
private var _deserialized: Boolean,  //反序列化
private var _replication: Int = 1        //几个副本

发现MEMORY_ONLY是不序列化的,此乃缓存数据变大的原因
我们试一试序列化的MEMORY_ONLY

删除缓存数据

想不持久化RDD,从内存删除
注意uppersisit是立即执行的,不是lazy的

scala> info.unpersist()

发现之前缓存的数据都没了
在这里插入图片描述

手动设置StorageLevel策略

手动用persist设置,在spark-shell里要导入包

scala> import org.apache.spark.storage
scala> import org.apache.spark.storage.StorageLevel
scala> info.persist(StorageLevel.MEMORY_ONLY_SER)
res2: org.apache.spark.rdd.RDD[String] = file:///home/hadoop/data/page_views.dat MapPartitionsRDD[1] at textFile at <console>:24

有序列化的缓存结果
在这里插入图片描述
序列化可以节省内存开销,代价是cpu负载会变高。

storage level对比

正常来说,使用MEMORY_ONLY即可。
注意,spark也会自动的持久化一些即时数据在shuffle的过程中(比如reducebykey),而不需要用户自己cache。
这是为了避免shuffle过程中,如果有一个node挂了需要重新计算所有数据(理解是shuffle之前的算子计算的结果得以保存,如果shuffle失败,不需要从头开始,可以直接再shuffle)。

在这里插入图片描述
在这里插入图片描述第五个是外部存储

如何选择存储策略

存储策略的选择是在内存和cpu资源之间的权衡

MEMORY_ONLY跑的最快,但是耗费内存。
MEMORY_ONLY_SER,序列化的内存方式没那么耗内存,但是耗cpu呀
不要把cache的数据写到磁盘去,除非太耗内存,要过滤大量数据。因为重计算一个分区速度会受限于从磁盘读数据
使用副本策略会提高容错能力,但耗内存,通常不用

共享变量

通常情况下,当一个function函数传递给spark操作(比如map或reduce)上的远程集群节点

比如:

 val mapVar = new HashMap()
val rdd = sc.textFile("....")
rdd.map(x=>{
	...mapVar...
})	

我们知道算子是工作到executor端的,算子外面的是在driver端的。所以在executor上,map程序运行在每个task(1个分区对应一个task)都要拿到程序所要的mapVar变量,这个变量会被复制到需要这个的每个task上,并且不会做更新。

map > n task < executor
||
mapVar

但是假如有1000task ,变量大小10m ,就要复制出10G的文件,这样是很消耗资源的。所以spark提供了两种共享变量的方式,广播变量和计数器。

广播变量

他是只读的变量,cache在executors上而不是拷贝每个副本在task上。

没广播的使用

join: a.id = b.id  shuffle

a
<1,record1>
<2,record2>
<3,record3>

b
<1,xxx1>
<2,xxxx2>
<4,xxx4>

join,shuffle的
<1,(record1,xxx1)>
<2,(record2,xxxx2)>
...

使用广播变量

广播变量不能过大	,因为内存不一定够
 表a就可以直接匹配executor的广播变量了,注意这里就没有shuffle了。注意以前的shuffle是分区和分区之间,现在数据是在executor。
<1,record1>  匹配: executor==>Broadcast Variables
<2,record2>
<3,record3>

 表b变为广播变量,被放在executor的内存里
<1,xxx1>
<2,xxxx2>  ==> Broadcast Variables   ==> executor memory
<4,xxx4>

所以可以看出如果b表是小表,a表是大表,可以提升很多性能。

注意在IDEA写好代码,复制到spark-shell运行,每行前面不能有空格

def commonJoin(sc:SparkContext)={
    val g5 = sc.parallelize(Array(("1", "豆豆"), ("2", "牛哥"), ("34", "进取"))).map(x=>(x._1,x))
    val f11 = sc.parallelize(
      Array(("34", "深圳", 18), ("10", "北京", 2))
    ).map(x => (x._1,x))

    //注意要join需要数据是key-value的形式,所以上面的要做类型转换
    //join后的数据结构rdd.RDD[(String, ((String, String), (String, String, Int)))]
    g5.join(f11).map(x => {
      x._1+","+ x._2._1._2 + "," + x._2._2._2 + "," + x._2._2._3
    }).collect()
  }

在这里插入图片描述

可以看见广播变量的方式是没有shuffle的

def broadcastJoin(sc:SparkContext)={

    //假设g5是小表,collectAsMap返回的是key-value的键值对的map形式(方便得到key),返回到driver端,存于内存,不能过大
    val g5 = sc.parallelize(Array(("1", "豆豆"), ("2", "牛哥"), ("34", "进取"))).collectAsMap()
    //广播小表
    val g5Broadcast = sc.broadcast(g5)
    //注册计数器
    //sc.longAccumulator()
    //f11
    val f11 = sc.parallelize(Array(("34", "深圳", 18), ("10", "北京", 2))).map(x => (x._1, x))

    f11.mapPartitions(partition => {
      val g5student = g5Broadcast.value
//contains是判断map里的这个key是否有绑定,就是有值
//针对每一次 for 循环的迭代, yield 会产生一个值,被循环记录下来 (内部实现上,像是一个缓冲区).当循环结束后, 会返回所有 yield 的值组成的集合.返回集合的类型与被遍历的集合类型是一致的
      for ((key,value) <- partition if (g5student.contains(key)))
        yield(key,g5student.getOrElse(key,""),value._2)
    }
    )
  }

在这里插入图片描述

计数器

他是一个变量,只能做加法

//用longAccumulator注册一个计数器
val accum = sc.longAccumulator("My Accumulator")
sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
accum.value

在stage里可以看到计数器
在这里插入图片描述

spark on yarn

注意要查看yarn的健康情况,遇到过一个unhealthy的情况,是由于磁盘空间不够
yarnUI:8088
在这里插入图片描述
只要磁盘空间占用大于90%,就起不来
在这里插入图片描述

要指定在yarn里运行程序,只要指定下master。
可以通过设置–deploy-mode来指定连接yarn以client模式(默认)或者cluster mode模式。
需要在 HADOOP_CONF_DIR 或者 YARN_CONF_DIR 文件里设置client配置文件的路径。这些配置用来写在HDFS上和连接到yarn的ResourceManager。

我电脑是这个路径

/home/hadoop/app/hadoop-2.6.0-cdh5.7.0/etc/hadoop

shell里

export HADOOP_CONF_DIR=/home/hadoop/app/hadoop-2.6.0-cdh5.7.0/etc/hadoop
$SPARK_HOME/bin/spark-submit \
--master yarn \
##--deploy-mode cluster \
--class com.ruozedata.bigdata.core02.LogApp \
--name LogServerApp \
/home/hadoop/data/g5-spark-1.0.jar \
hdfs://hadoop000:9000/g5/generatefile hdfs://hadoop000:9000/g5/asd

在这里插入图片描述

spark on yarn的两种模式

client模式:Driver运行在本地,哪里提交就在哪里。执行过程中客户端不能中断,因为driver和executor会一直通信。能看到日志。因为日志在driver端
在这里插入图片描述

cluster模式:driver跑到AM进程里,提交后可以中断客户端,日志看不到。

在这里插入图片描述
可以通过:yarn logs -applicationId <app ID> 查看日志
后面的就是在这里插入图片描述
在控制台输入即可查询

猜你喜欢

转载自blog.csdn.net/qq_36459386/article/details/85215849