Spark(七)——累加器和广播变量

本文详细介绍了如何在Spark中使用累加器(Accumulator)进行分布式聚合计数,并展示了自定义累加器MyAccumulator的实现。同时,讲解了广播变量(Broadcast Variable)的创建、使用和只读特性。
摘要由CSDN通过智能技术生成

5、累加器

通过在驱动器中调用SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。返回值为org.apache.spark.Accumulator[T] 对象,其中 T 是初始值 initialValue 的类型。 Spark闭包里的执行器代码可以使用累加器的 += 方法(在Java中是 add)增加累加器的值。 
 驱动器程序可以调用累加器的value属性(在Java中使用value()或setValue())来访问累加器的值。 注意:工作节点上的任务不能访问累加器的值。从这些任务的角度来看,累加器是一个只写变量。 对于要在行动操作中使用的累加器,Spark只会把每个任务对各累加器的修改应用一次。因此,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在 foreach() 这样的行动操作中。转化操作中累加器可能会发生不止一次更新。

object SparkCoreDemo13_Accumulator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("Demo13")
    val sc = new SparkContext(conf)
​
    var count = 0
    // 1. 创建累加器对象
    val acc = new LongAccumulator
    val myAcc = new MyAccumulator
    // 2. 使用sc注册累加器
    sc.register(acc)
    sc.register(myAcc)
​
    val rdd = sc.makeRDD(1 to 10)
​
    //    val sum = rdd.reduce(_ + _)
    //    val count1 = rdd.count()
    //    sum / count1.toDouble
​
    val rdd1 = rdd.map(x => {
      count += 1
      // 3. 使用累加器对象进行数据的添加
      acc.add(1)
      myAcc.add(x)
      println(s"acc value in map: ${acc.value} --" + Thread.currentThread().getId)
      println(s"count in map: $count---" + Thread.currentThread().getId)
      x + 1
    })
​
    println(rdd1.collect().toList)
​
    // 4. 使用value()获取累加器的值
    println(s"acc value in main: ${acc.value} --" + Thread.currentThread().getId)
    println(s"count in main: $count--" + Thread.currentThread().getId)
    // 上面的代码中
    // 如果想通过创建在Driver中的局部变量统计RDD 算子的执行次数
    // 最终无法获取到执行次数
    //   因为RDD的算子操作是在Driver中进行编译
    //    并真正提交到执行器(Executor)中的任务线程(Task)中执行
    //     每个线程(Task)都会保有一份属于自己线程的局部变量
    //     最终Driver程序中的局部变量没有参与任何运算
​
    // Spark提供了Accumulator 累加器对象  用于方便的进行分布式聚合(计数)
​
    // AccumulatorV2
    //    add(对象)  将对象添加到累加器中
    //   对象 =  value()  获取累加器中的值
​
    println(myAcc.value)
​
  }
}

自定义累加器

//                               [IN,OUT] 累加器的输入对象
//                                        累加器的输出对象
class MyAccumulator extends AccumulatorV2[Int, Double] {
  // 创建成员属性用于记录当前累加器的值
  var count: Long = 0L
  var sum: Long = 0L
​
  /**
   * 用于判断当前累加器是否为初始状态
   *
   * @return
   */
  override def isZero: Boolean = this.count == 0 && this.sum == 0
​
  /**
   * 复制当前累加器的状态
   *
   * @return
   */
  override def copy(): AccumulatorV2[Int, Double] = {
    val accumulator = new MyAccumulator
    accumulator.count = this.count
    accumulator.sum = this.sum
    accumulator
  }
​
  /**
   * 重置当前累加器的值
   */
  override def reset(): Unit = {
    this.count = 0
    this.sum = 0
  }
​
  /**
   * 将传入的对象添加到当前的累加器值中
   *
   * @param v
   */
  override def add(v: Int): Unit = {
    this.count += 1
    this.sum += v
  }
​
  /**
   * 将其他分区的累加器传入merge 并将所有累加器的值进行合并
   *
   * @param other 其他分区的累加器
   */
  override def merge(other: AccumulatorV2[Int, Double]): Unit = {
    val o = other.asInstanceOf[MyAccumulator]
    this.count += o.count
    this.sum += o.sum
  }
​
  /**
   * 返回当前累加器的值
   *
   * @return
   */
  override def value: Double = this.sum.toDouble / this.count
}

6、广播变量

使用广播变量的过程如下:

(1) 通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。 任何可序列化的类型都可以这么实现。

(2) 通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。

(3) 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。

package com.zch.spark.core
​
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.{SparkConf, SparkContext}
​
import scala.io.Source
​
/**
 * Created with IntelliJ IDEA.
 * Author: Amos
 * E-mail: amos@amoscloud.com
 * Date: 2021/12/14
 * Time: 9:22
 * Description: 
 */
object SparkCoreDemo14_BroadcastVariable {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("Demo14")
    val sc = new SparkContext(conf)
​
​
    // 加载黑名单文件放入集合
    val source = Source.fromFile("C:\\Users\\Amos\\Desktop\\blackList.txt")
    //   文件大小1GB
    val blkList: List[String] = source.getLines().toList
    source.close()
​
    // 1. 创建广播对象
    val bc_blkList: Broadcast[List[String]] = sc.broadcast(blkList)
​
    // 加载日志数据创建RDD
    val rdd = sc.textFile("C:\\Users\\Amos\\Desktop\\weblog\\access.log-20211107")
    // 将日志数据通过处理得到  (ip,是否为黑名单用户)
    rdd
      .repartition(10)
      .map(line => {
        val ip = line.split(" ").head
        //2. 需要使用时  从公共缓存中读取对象
        val list = bc_blkList.value
        (ip, if (list.contains(ip)) 1 else 0)
      })
      .foreach(println)
​
  }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值