spark 累加器

spark 3大数据结构

  1. RDD:弹性分布式数据集
  2. 累加器:分布式共享只写变量
  3. 广播变量:分布式共享只读变量

为什么要使用累加器

首先来看一段代码(统计单词出现的次数)

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

    // 1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[1]")

    // 2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    // 3.创建RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)),numSlices = 2)

    // 3.1 打印单词出现的次数(a,10)
    rdd.reduceByKey(_ + _).collect().foreach(println) // 其中有shuffle过程

    // 有shuffle过程,那么意味着执行效率低下
   // 3.2 如果不用shuffle,怎么处理呢?
    var sum = 0
    rdd.foreach {
      case (word, count) => {
        sum = sum + count
        println("sum=" + sum) // 打印是在Executor端
      }
    }
    println(("a", sum)) // 打印是在Driver端
	sc.stop()
}

输出结果:

(a,10)
sum=1
sum=3
sum=3
sum=7
(a,0)

问题1:使用reduceByKey会有shuffle,有shuffle则意味着执行效率低下
问题2:如果不用reduceByKey,怎么处理呢?

通常的思路是:自己去定义一个全局sum变量,每遍历到相同key的单词后,就去做累加(sum+count),但为什么最终结果会是(a,0)?

在这里插入图片描述

因为numSlices = 2,所以会由2个Executor来处理数据,然后每个Executor会去复制一份Driver端的sum变量,每个Executor内的sum变量互不干扰,所以就有:第一个分区中的数据为(“a”, 1), (“a”, 2),输出sum=1,sum=3;第二个分区中的数据为(“a”, 3), (“a”, 4),输出sum=3,sum=7;因为Driver端的sum变量是被复制Executor端过去的,Executor端并没有直接操作Driver端的sum变量,所以Driver端的sum变量仍然为0,如果想要Executor端的sum变量影响到Driver端的sum变量,则需要使用累加器

    // 3.3 使用累加器实现数据的聚合功能
    // Spark自带常用的累加器
    // 3.3.1 声明累加器
    val sum1: LongAccumulator = sc.longAccumulator("myAcc")

    rdd.foreach {
      case (word, count) => {
        // 3.3.2 使用累加器
        sum1.add(count)
      }
    }

    //3.3.3 获取累加器的值
    println(sum1.value)

在这里插入图片描述

spark 累加器功能

累加器用来对信息进行聚合。比如上述使用 foreach 算子时,该算子内部使用到了 Dirver 端定义的变量,并且集群中运行的每个任务的 Executor 端都会得到变量的一个副本,在 Executor 端更新副本的值不会影响 Dirver 端对应的变量,如果想要影响,则需要使用累加器

自定义累加器案例演示

自定义累加器在1.x版本中就已经提供了,但是使用起来比较麻烦,在2.0版本后累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式

需求:统计集合List(“Hello”, “World”, “Hi”, “Spark”)中首字母为“H”单词出现的次数

输出:Map(Hello -> 1, Hi -> 1)

package com.xcu.bigdata.spark.core.pg02_accumulator

import org.apache.spark.rdd.RDD
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable

/**
 * @Desc : 累加器的使用 统计特定形式的wordCount
 */
object Spark01_Accumulator {
  def main(args: Array[String]): Unit = {
    //创建配置文件
    val conf: SparkConf = new SparkConf().setAppName("Spark01_Accumulator").setMaster("local[*]")
    //创建SparkContext,该对象是提交的入口
    val sc = new SparkContext(conf)
    //创建RDD
    val rdd: RDD[String] = sc.makeRDD(List("Hello", "World", "Hi", "Spark"))
    //创建累加器
    val myAcc = new MyAccumulator()
    //注册累加器
    sc.register(myAcc, name = "myAcc")
    //使用累加器
    rdd.foreach(
      word => {
        myAcc.add(word)
      }
    )
    //获取累计器的累加结果
    println(myAcc.value)
    //释放资源
    sc.stop()
  }
}


//AccumulatorV2[输入数据的类型, 输出数据的类型]
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] {
  //定义一个集合,记录单词以及出现次数
  var map: mutable.Map[String, Int] = mutable.Map[String, Int]()

  //初始化状态
  override def isZero: Boolean = map.isEmpty

  //拷贝(不同的excutor需要去拷贝一份driver上的累加器,作为副本)
  override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
    val newAcc = new MyAccumulator
    newAcc.map = this.map
    newAcc
  }

  //重置集合
  override def reset(): Unit = map.clear()


  //分区内计算逻辑
  override def add(e: String): Unit = {
    if (e.startsWith("H")) {
      //向可变集合中添加或者更新元素
      map(e) = map.getOrElse(e, 0) + 1
    }
  }


  //分区间计算逻辑
  override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
    //当前excutor的map
    var map1: mutable.Map[String, Int] = map
    //另一个excutor的map
    var map2: mutable.Map[String, Int] = other.value
    map = map1.foldLeft(map2) {
      (map2, kv) => {
        map2(kv._1) = map2.getOrElse(kv._1, 0) + kv._2
        map2
      }
    }
  }

  //获取累加器的值
  override def value: mutable.Map[String, Int] = map
}

spark 累加器执行原理

首先序列化 driver 端 accumulator 到 executor ,序列化前调用 reset 重置 value 并使用 isZero 检测是否重置成功。单个 executor 内使用 add 进行累加,最终 driver 端对多个 executor 间的 accumulaotr 使用merge 进行合并得到结果

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据分析职业是一个多金的职业,数据分析职位是一个金饭碗的职位,前景美好,但是要全面掌握大数据分析技术,非常困难,大部分学员的痛点是不能快速找到入门要点,精准快速上手。本课程采用项目驱动的方式,以Spark3和Clickhouse技术为突破口,带领学员快速入门Spark3+Clickhouse数据分析,促使学员成为一名高效且优秀的大数据分析人才。学员通过本课程的学习,不仅可以掌握使用Python3进行Spark3数据分析,还会掌握利用Scala/java进行Spark数据分析,多语言并进,力求全面掌握;另外通过项目驱动,掌握Spark框架的精髓,教导Spark源码查看的技巧;会学到Spark性能优化的核心要点,成为企业急缺的数据分析人才;更会通过Clickhouse和Spark搭建OLAP引擎,使学员对大数据生态圈有一个更加全面的认识和能力的综合提升。真实的数据分析项目,学完即可拿来作为自己的项目经验,增加面试谈薪筹码。课程涉及内容:Ø  Spark内核原理(RDD、DataFrame、Dataset、Structed Stream、SparkML、SparkSQL)Ø  Spark离线数据分析(千万简历数据分析、雪花模型离线数仓构建)Ø  Spark特征处理及模型预测Ø  Spark实时数据分析(Structed Stream)原理及实战Ø  Spark+Hive构建离线数据仓库(数仓概念ODS/DWD/DWS/ADS)Ø  Clickhouse核心原理及实战Ø  Clickhouse engine详解Ø  Spark向Clickhouse导入简历数据,进行数据聚合分析Ø  catboost训练房价预测机器学习模型Ø  基于Clickhouse构建机器学习模型利用SQL进行房价预测Ø  Clickhouse集群监控,Nginx反向代理Grafana+Prometheus+Clickhouse+node_exporterØ  Spark性能优化Ø  Spark工程师面试宝典       课程组件:集群监控:福利:本课程凡是消费满359的学员,一律送出价值109元的实体书籍.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值