Spark之Core高级知识分享四(Shuffle+Monitor+Share Variables)

1.shuffle

shuffle是将数据重新分配的过程,它是是跨分区的,涉及网络IO传输的,成本很高。它是整个大数据的性能杀手,瓶颈所在,故生产中尽量较少shuffle动作产生。下面是列举的常见的一些算子

1.1spark中会产生shuffl的操作

repartition:重分区,生产中用的最多是合并小文件,减小生成的文件数
repartition:底层调的是coalesce(num,shuffle=true)方法
coalesce:默认合并分区是不会产生shuffle的,若想使得分区数变大,必须设置能够shuffle
bykey:聚合
reducebykey:能够在map端进行combiner,底层调的是combineByKeyWithClassTag方法,默认传参 mapSideCombine: Boolean = true
groupBykey:能够在map端进行combiner进行聚合,生产慎用,底层调的是combineByKeyWithClassTag方法,传参 mapSideCombine: Boolean = false
join:关联,注意如果两个RDD分区一对一join是不会产生shuffle的
distinct:去重
sort:排序

扩展1:mapPartitions算子循环的元素是一个partition,在数据存储外部DB时有很大的性能提高
扩展2 mapPartitions效率比map高的多,开发应首先mapPartitions,但是当partition数据很大时有OOM的风险,要注意。
扩展3:mapPartitionsWithIndex算子循环的是一个键值对,key=分区下标,value=分区
扩展4 collect算在生产中慎用,所有的数据会加载到dirver,很大可能OOM,
扩展5 foreach 以及foreachPartition是action算子,可以直接操作数据,如将数据存储外部系统,而不是通过mapPartition().clollect()方式

2.Spark Monitor

spark的webUI界面给我们提供了非常好的作业监控界面,通过仔细观察那些界面我们可以做很多的事儿。

2.1 通过Spark UI进行监控

该方式可查看整整运行的spark程序的作业详细信息,Duration、gc,launch的时间,这都需要在生产上观看的 。但是但是当任务跑完或者挂了,是无法看到任何信息的
1

2.2 使用Spark HistoryServer UI进行监控

通过Spark HistoryServer我们可以观看已经结束的Spark Application

  • 启动HistoryServer:./sbin/start-history-server.sh
  • HistoryServer WebUI访问地址:http://server-ip:18080/
  • 相关配置
spark.eventLog.enabled true #开启事件日志
spark.eventLog.dir hdfs://namenode/shared/spark-logs #事件日志存放位置
  • 环境变量
SPARK_HISTORY_OPTS	spark.history.* 所有有关historyserver的配置都要配置在这个环变中 (default: none).
  • Spark History Server Configuration Options
Property Name	Default
spark.history.provider	org.apache.spark.deploy.history.FsHistoryProvider #日志的提供类
spark.history.fs.logDirectory	file:/tmp/spark-events #日志的存放位置
spark.history.fs.update.interval	10s #多久更新日志
spark.history.retainedApplications	50 #内存中最多持有程序数,多的则需要读取磁盘
spark.history.fs.cleaner.enabled	false #是否开启清理日志数据功能,生产上是必须清理的,需开启
  • 开启Spark HistoryServer流程
#修改默认的配置文件,cp defau-config文件去掉template,设置日志存放位置,日志目录要先创建
cd 
spark.eventLog.enabled true
spark.eventLog.dir hdfs://hadoop001:9000/shared/spark-logs
# 修改环境变量 ,cp env.sh文件去掉template,,告诉historyserver从哪儿读取日志
SPARK_HISTORY_OPTS=”-Dspark.history.fs.logDirectory=hdfs://hadoop001:9000/shared/spark-logs“
#启动
./sbin/start-history-server.sh
#停止
./sbin/stop-history-server.sh
jps
日志在log文件夹下
  • 测试,使用spark-shell运行一个作业,然后关闭,查询页面结果如下
2.3 使用REST API进行监控

这是通过rest ful请求的方式来获取spark的作业的信息,通过这种方式我们可以构建我们自己web界面。
如下是通过rest请求http://:18080/api/v1/applications来获取所有的application的信息。
更多的metrics请参考官网

2.4 Metrics

此方式生产用的少,通常对spark研究很深的人才才可能会使用

3.Share Variables(分享变量)

在算子中若直接操作外部的变量,spark会将普通的外部变量拷贝到每一个task上,这样不仅会吃很多内存,还会出现同时各自更改该变量如何保证都生效且不冲突的问题。spark引进了: broadcast variables(广播大变量) and accumulators(累加器)两个功能。

3.1 Accumulators(累加器)
  • accumulators是一个并行且高效只能进行add操作的计数器,spark本地只支持的是数值类型的计数器,其它类型需要开发人员自定义
  • 使用accumulators
object AccumulatorsApp {

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("AccumulatorsApp")
    val sc = new SparkContext(conf)

    val wskAccum = sc.longAccumulator("wsktets")
    val nums = sc.parallelize(List(1,2,3,4),2)
    println("driver端:计数器值:"+wskAccum.value)

    nums.map(x=>{
      println("计数前值:"+wskAccum.value)
      wskAccum.add(x)
      println("计数后值:"+wskAccum.value)
      x
    }).collect()
    println("driver端:最终计数器值:"+wskAccum.value)

    Thread.sleep(2000000)
    sc.stop()
  }
}

9

3.2 Broadcast Variables(广播大变量)
  • 广播变量是将变量复制到每一台机器上而不是普通变量那样每个task复制,它是高效的,广播变量只能读取,并不能修改。 经典应用是大表与小表的join中通过广播变量小表来实现以brodcast jion取代reduce join,。
  • 广播是dirver进行的操作,必须是有结果的变量,故不可能直接广播RDD,常用的是将通过collectAsMap算子将需要广播的RDD转换成Map集合然后广播出去
  • brodcast join代码实现
/**
  * share variables:BroadcastApp
  *  广播大变量实现以map join取代reduce join
  */
object BroadcastApp {

  def main(args: Array[String]): Unit = {
//    commomJoin()

    broadcastJoin()
  }
  def commomJoin(): Unit = {

    val conf = new SparkConf().setMaster("local[2]").setAppName("BroadcastApp")
    val sc = new SparkContext(conf)

    val wideInfo = sc.parallelize(List(("01","阿呆"),("02","sk"),("03","shjqi")))
    val baseInfo = sc.parallelize(List(("01",("北京","22")),("05",("上海","22"))))

    wideInfo.join(baseInfo).map(x =>(x._1,x._2._1,x._2._2._1,x._2._2._2))
      .collect().foreach(println)

    Thread.sleep(2000000)
    sc.stop()
  }

  def broadcastJoin(): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("BroadcastApp")
    val sc = new SparkContext(conf)

    val wideInfo = sc.parallelize(List(("01","阿呆"),("02","sk"),("03","shjqi")))
    val baseInfo = sc.parallelize(List(("01",("北京","22")),("05",("上海","22"))))
      .collectAsMap()
    val broadcastBaseInfo = sc.broadcast(baseInfo)

    wideInfo.mapPartitions(x =>{
      val value = broadcastBaseInfo.value
      for((k,v)<-x if(value.contains(k))) 
        yield (k,v,value.get(k).getOrElse().asInstanceOf[(String,String)]._1,value.get(k).getOrElse().asInstanceOf[(String,String)]._2)
    }).foreach(println)

    Thread.sleep(2000000)
    sc.stop()
  }
}

reduce join需要将job划分成三分stage
11
map join的job只有一个stage
12
扩展4: yeild是将循环中的元素添加到缓存,循环结束后返回集合,注意该集合和被循环的集合类型一致,如都是Map

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值