Spark的广播变量和累加器

本文详细介绍了Spark中广播变量和累加器的概念、作用及使用方法。广播变量用于在分布式环境中高效共享大对象,避免多次复制,节省资源;累加器提供了一种全局汇总的机制,适用于分布式计数等场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述
在spark程序中,当一个传递给Spark操作(例如map和reduce)的函数在远程节点上面运行时,Spark操作实际上操作的是这个函数所用变量的一个独立副本。这些变量会被复制到每台机器上,并且这些变量在远程机器上的所有更新都不会传递回驱动程序。通常跨任务的读写变量是低效的,但是,Spark还是为两种常见的使用模式提供了两种有限的共享变量:广播变(broadcast variable)和累加器(accumulator)

二、广播变量broadcast variable
2.1 为什么要将变量定义成广播变量?
如果我们要在分布式计算里面分发大对象,例如:字典,集合,黑白名单等,这个都会由Driver端进行分发,一般来讲,如果这个变量不是广播变量,那么每个task就会分发一份,这在task数目十分多的情况下Driver的带宽会成为系统的瓶颈,而且会大量消耗task服务器上的资源,如果将这个变量声明为广播变量,那么知识每个executor拥有一份,这个executor启动的task会共享这个变量,节省了通信的成本和服务器的资源。

2.2 广播变量图解
错误的,不使用广播变量
在这里插入图片描述

正确的,使用广播变量的情况

在这里插入图片描述

2.3 如何定义一个广播变量?
val a = 3
val broadcast = sc.broadcast(a)
2.4 如何还原一个广播变量?
val c = broadcast.value
2.5 定义广播变量需要的注意点?
变量一旦被定义为一个广播变量,那么这个变量只能读,不能修改

2.6 注意事项
1、能不能将一个RDD使用广播变量广播出去?

   不能,因为RDD是不存储数据的。可以将RDD的结果广播出去。

2、 广播变量只能在Driver端定义,不能在Executor端定义。

3、 在Driver端可以修改广播变量的值,在Executor端无法修改广播变量的值。

4、如果executor端用到了Driver的变量,如果不使用广播变量在Executor有多少task就有多少Driver端的变量副本。

5、如果Executor端用到了Driver的变量,如果使用广播变量在每个Executor中只有一份Driver端的变量副本。
从代码层面看使用广播变量和不使用广播变量的区别
不使用广播变量

package com.soul.bigdata.spark.core4

import org.apache.spark.{SparkConf, SparkContext}

object BroadCastApp {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("AccumulatorApp").setMaster("local[2]")

    val sc = new SparkContext(conf)

    commonJoin(sc)

    Thread.sleep(30000 * 10)

    sc.stop()
  }

  def commonJoin(sc: SparkContext): Unit = {
    val info1 = sc.parallelize(Array(("601", "张三"), ("602", "李四")))
    val info2 = sc.parallelize(Array(("601", "哈弗", "25"), ("603", "浙大", "22"), ("603", "深大", "26")))
      .map(x => (x._1, (x._2, x._3)))
    //TODO 需得到 601,张三,哈弗
    info1.join(info2).map(x => {
      //(601,(张三,(哈弗,25)))
      x._1 + "," + x._2._1 + "," + x._2._2._1
    }).foreach(println)
  }

}

image.png
使用广播变量后

package com.soul.bigdata.spark.core4

import org.apache.spark.{SparkConf, SparkContext}

object BroadCastApp {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("AccumulatorApp").setMaster("local[2]")

    val sc = new SparkContext(conf)

    broadcastJoin(sc)


    Thread.sleep(30000 * 10)

    sc.stop()
  }

  def broadcastJoin(sc: SparkContext): Unit = {
    //小数据 -> 广播
    val info1 = sc.parallelize(Array(("601", "张三"), ("602", "李四"))).collectAsMap() //转成Map 可以通过get得到key

    //Driver数据才需广播
    val broadcastinfo1 = sc.broadcast(info1)

    //大数据
    val info2 = sc.parallelize(Array(("601", "哈弗", "25"), ("603", "浙大", "22"), ("603", "深大", "26")))
      .map(x => (x._1, (x._2, x._3)))

    //broadcst以后就不会用Join实现。而是大表数据读取出来一条就和广播出去的小表记录做匹配

    info2.mapPartitions(x => {
      val broadcastMap = broadcastinfo1.value

      for ((key, value) <- x if broadcastMap.contains(key))
          //TODO 需得到 601,张三,哈弗
        yield (key, broadcastMap.get(key).getOrElse(), value._1)

    }).foreach(println)
  }

}

image.png
一个存在shuffle,一个不存在shuffle。性能对比一目了然,但是广播的前提是你的数据不能太大,否则也会发生OOM。
注意事项:

只能广播RDD的结果数据,不能直接广播RDD
广播变量只能在Driver端定义,不能在Executor端定义

三、累加器
3.1 为什么要将一个变量定义为一个累加器?
在spark应用程序中,我们经常会有这样的需求,如异常监控,调试,记录符合某特性的数据的数目,这种需求都需要用到计数器,如果一个变量不被声明为一个累加器,那么它将在被改变时不会在driver端进行全局汇总,即在分布式运行时每个task运行的只是原始变量的一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分布式计数的功能。

3.2 图解累加器
错误的图解
在这里插入图片描述

正确的图解

在这里插入图片描述

3.3 如何定义一个累加器?
val a = sc.accumulator(0)

3.4 如何还原一个累加器?
val b = a.value

3.5 注意事项
1、 累加器在Driver端定义赋初始值,累加器只能在Driver端读取最后的值,在Excutor端更新。

2、累加器不是一个调优的操作,因为如果不这样做,结果是错的

看一个列子

package com.soul.bigdata.spark.core4

import org.apache.spark.{SparkConf, SparkContext}

object AccumulatorApp {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("AccumulatorApp").setMaster("local[2]")
    val sc = new SparkContext(conf)
    val line = sc.textFile("file:///D:\\RZ-G6\\2019G6\\data\\wordcount.txt")

    var i = 0
    val result = line.map(x => {
      i = i + 1
      x
    })
    result.collect().foreach(println)
    //0  driver端数据与executor端数据不能共享导致 所以Spark就引出累加器
    println("不用累加器统计 word lines is " + i)
    sc.stop()

  }
}

运行结果为0

image.png
依然是因为driver端数据与executor端数据不能共享导致,所以Spark就引出累加器。

使用累加器之后

package com.soul.bigdata.spark.core4

import org.apache.spark.{SparkConf, SparkContext}

object AccumulatorApp {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("AccumulatorApp").setMaster("local[2]")
    val sc = new SparkContext(conf)
    var accu = sc.longAccumulator("MyAccumulator")
    println("累加器原始值: " + accu.value)

    val line = sc.textFile("file:///D:\\RZ-G6\\2019G6\\data\\wordcount.txt")

       val result2 = line.map(x => {
         accu.add(1)//有一行数据就增加1
         x
       })

       //.foreach(println) //必须触发一个action算子 将结果返回到Driver    累加器的值只有Driver可以读取   executor端只能累加计数器得到值,但不能获取
       result2.collect()
       println("使用累加器统计 word lines is " +accu.value)


    sc.stop()

  }
}

image.png

image.png
累加器的作用:提供了将工作节点中的值聚合到驱动器程序中的简单语法

注意事项:累加器在Driver端定义赋初始值,累加器只能在Driver端读取,在Excutor端更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值