Spark 共享变量——累加器(accumulator)与广播变量(broadcast variable)

累加器(accumulator)

我们传递给Spark的函数,如map(),或者filter()的判断条件函数,能够利用定义在函数之外的变量,但是集群中的每一个task都会得到变量的一个副本,并且task在对变量进行的更新不会被返回给driver。而Spark的两种共享变量:累加器(accumulator)和广播变量(broadcast variable),在广播和结果聚合这两种常见类型的通信模式上放宽了这种限制。
使用累加器可以很简便地对各个worker返回给driver的值进行聚合。累加器最常见的用途之一就是对一个job执行期间发生的事件进行计数。例如,当我们统计输入文件信息时,有时需要统计空白行的数量。下面的程序描述了这个过程。

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

object AccumulatorTest{
  def main(args: Array[String]) {
    val conf = new SparkConf()
      //      .setMaster("spark://node01:7077")
      .setMaster("local")
      .setAppName("accumulatrorTest")
      .setJars(List("E:\\IdeaProjects\\SparkExercise\\out\\artifacts\\SparkExercise_jar\\SparkExercise.jar"))
    val sc = new SparkContext(conf)
    val file = sc.textFile("E:\\file.txt")
    val blankLines = sc.accumulator(0) // Create an Accumulator[Int] initialized to 0,结果返回4
//    var blankLines =0     //结果返回0,因为每个task会得到blankLines的一个副本,且每个task对它的更新不会返回给driver
    val callSigns = file.flatMap(line => {
        if (line == "") {
          blankLines += 1 // Add to the accumulator
        }
        line.split(" ")
      })
    callSigns.saveAsTextFile("E:\\output.txt")
    println("Blank lines: " + blankLines)
    sc.stop()
  }
}

在上面的例子中,我们创建了一个名为blankLines的整型累加器(Accumulator[Int]),初始化为0,然后再每次读到一个空白行的时候blankLines加一。因此,累加器使我们可以用一种更简便的方式,在一个RDD的转换过程中对值进行聚合,而不用额外使用一个filter()或reduce()操作。
需要注意的是,由于Spark的lazy机制,只有在saveAsTestFile这个action算子执行后我们才能得到blankLines的正确结果。

由于对于worker节点来说,累加器的值是不可访问的,所有对于worker上的task,累加器是write-only的。这使得累加器可以被更高效的实现,而不需要在每次更新时都进行通信。

当有多个值需要被跟踪记录,或者一个值需要在并行程序的多处进行更新时,使用累加器的计数功能变得尤其方便。例如,原始数据中经常有一部分的无效数据。当无效数据的比例很高时,为了防止产生垃圾输出,我们需要使用累加器对数据中的有效数据和无效数据分别进行计数。接着上面的程序,下面的程序描述了有效数据和无效数据的统计过程,并且为了简便,我们假设以字母 “t”开头的单词是无效数据。

    def validateSign(word:String):Boolean={
      if (word.startsWith("t")){
        invalidLines += 1
        false
      }
      else{
        validLines += 1
        true
      }
    }
    val validSigns = callSigns.filter(validateSign)
    val contactCount = validSigns.map(word => (word,1)).reduceByKey((a,b)=>a+b)

    contactCount.count()
    if(invalidLines.value<0.1*validLines.value){
      contactCount.saveAsTextFile("E:\\contactCount")
    }else{
      println(f"Too many errors: $invalidLines in $validLines ")
    }

累加器与容错

对于失效节点和慢节点,Spark会自动通过重新执行(re-executing)失效任务或慢任务。而有时,即使没有节点失效,Spark可能会需要重新执行一遍tasks来重建一个被移出内存的缓存值,这就导致同一数据上的同一函数可能会因此执行多次。
对于在RDD转换(Transformation)操作中的累加器,一个累加器的更新可能会出现多次。出现这种现象的一种可能情况是,一个被缓存,但是不经常使用的RDD被第一次弹出LRU cache队列,但是之后又需要使用了。这时RDD会根据其血统(lineage)被重新计算,而累加器上的更新也因此多执行了一遍,并返回给driver。因此,建议在转换操作中使用的累加器仅用于调试目的。

自定义累加器

上面的例子中使用的是Spark内建的Integer类型累加器。同时,Spark还支持Double,Long,和Float类型的累加器。除此之外,Spark还提供了自定义累加器类型和聚合操作(如查找最大值等加操作以外的操作)的API,但要保证定义的操作满足交换律和结合律。

广播变量

Spark的另一种共享变量是广播变量。通常情况下,当一个RDD的很多操作都需要使用driver中定义的变量时,每次操作,driver都要把变量发送给worker节点一次,如果这个变量中的数据很大的话,会产生很高的传输负载,导致执行效率降低。使用广播变量可以使程序高效地将一个很大的只读数据发送给多个worker节点,而且对每个worker节点只需要传输一次,每次操作时executor可以直接获取本地保存的数据副本,不需要多次传输。

val signPrefixes = sc.broadcast(loadCallSignTable())
val countryContactCounts = contactCounts.map{case (sign, count) =>
val country = lookupInArray(sign, signPrefixes.value)
(country, count)
}.reduceByKey((x, y) => x + y)
countryContactCounts.saveAsTextFile(outputDir + "/countries.txt")

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

  • 在一个类型T的对象obj上使用SparkContext.brodcast(obj)方法,创建一个Broadcast[T]类型的广播变量,obj必须满足Serializable。
  • 通过广播变量的.value()方法访问其值。

另外,广播过程可能由于变量的序列化时间过程或者序列化变量的传输过程过程而成为瓶颈,而Spark Scala中使用的默认的Java序列化方法通常是低效的,因此可以通过spark.serializer属性为不同的数据类型实现特定的序列化方法(如Kryo)来优化这一过程。

参考文献

Learning Spark - O’Reilly Media

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
广播变量累加器是 PySpark 中常用的两种分布式计算工具。 广播变量Broadcast Variables)是用于在集群中的所有节点之间共享大型只读数据集的机制。它们可以提高任务执行的效率,因为它们只需要在网络上传输一次,而不是每个任务都传输一次。广播变量通常用于将大型查找表或机器学习模型有效地广播到集群的每个节点上。 下面是一个使用广播变量的示例: ```python from pyspark import SparkContext # 创建 SparkContext 对象 sc = SparkContext("local", "Broadcast Example") # 创建要广播的数据集 data = [1, 2, 3, 4, 5] broadcast_data = sc.broadcast(data) # 在集群中的每个节点上使用广播变量 result = sc.parallelize([1, 2, 3, 4, 5]).map(lambda x: x * broadcast_data.value[0]).collect() print(result) # 输出 [1, 2, 3, 4, 5] ``` 累加器Accumulators)是用于在分布式计算中进行累加操作的变量。它们通常用于在集群的所有节点上对计数器或求和器进行更新。累加器只支持“加”操作,并且在单个任务中对累加器进行更新不会影响其他任务。 下面是一个使用累加器的示例: ```python from pyspark import SparkContext # 创建 SparkContext 对象 sc = SparkContext("local", "Accumulator Example") # 创建累加器 accumulator = sc.accumulator(0) # 在集群中的每个节点上更新累加器 sc.parallelize([1, 2, 3, 4, 5]).foreach(lambda x: accumulator.add(x)) print(accumulator.value) # 输出 15 ``` 在这个示例中,我们创建了一个累加器,并在集群中的每个节点上通过 `foreach` 操作对累加器进行更新。最后,我们打印出累加器的值,得到结果为 15。 这就是广播变量累加器在 PySpark 中的应用方式。希望对你有所帮助!如果还有其他问题,请继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值