累加器

目录
1.累加器简单使用
2.累加器异常
3.自定义累加器

1.累加器简单使用

Spark内置的提供了Long和Double类型的累加器。下面是一个简单的使用示例,在这个例子中我们在过滤掉RDD中奇数的同时进行计数,最后计算剩下整数的和。

/**
  * @Auther: chenyj
  * @Date: 2018/9/7 14:23
  * @Description:
  */
object spark {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("spark").setMaster("local[2]")
    val sc = new SparkContext(sparkConf)
    val accum = sc.longAccumulator("longAccum") //统计偶数的个数
    val rddSum = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
      .filter(n=>{
        if(n%2==0)
          accum.add(1L)
          n%2==0
    }).reduce(_+_)
    println("accum: "+accum.value)
    println("rddSum: "+rddSum)
    sc.stop()
  }
}

这是结果正常的情况,但是在使用累加器的过程中如果对于spark的执行过程理解的不够深入就会遇到两类典型的错误:少加(或者没加)、多加.
结果

accum: 4
rddSum: 20

2.累加器异常

2.1 少加
/**
  * @Auther: chenyj
  * @Date: 2018/9/7 14:23
  * @Description:
  */
object spark {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("spark").setMaster("local[2]")
    val sc = new SparkContext(sparkConf)
    val accum = sc.longAccumulator("longAccum") //统计偶数的个数
    val rddSum = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
      .map(n=>{
          accum.add(1L)
    })
    println("accum: "+accum.value)
    sc.stop()
  }
}

结果:

accum: 0

原因:
执行完毕,打印的值是多少呢?答案是0,因为累加器不会改变spark的lazy的计算模型,即在打印的时候像map这样的transformation还没有真正的执行,从而累加器的值也就不会更新。

2.2 多加
/**
  * @Auther: chenyj
  * @Date: 2018/9/7 14:23
  * @Description:
  */
object spark {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("spark").setMaster("local[2]")
    val sc = new SparkContext(sparkConf)
    val accum = sc.longAccumulator("longAccum") //统计偶数的个数
    val rddSum = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
      .map(n=>{
          accum.add(1L)
        n
    })
    rddSum.count
    println("accum1:"+accum.value)
    rddSum.reduce(_+_)
    println("accum2: "+accum.value)
    sc.stop()
  }
}

结果:

accum1:9
accum2:18

原因:
我们虽然只在map里进行了累加器加1的操作,但是两次得到的累加器的值却不一样这是由于count和reduce都是action类型的操作,触发了两次作业的提交,所以map算子实际上被执行了了两次,在reduce操作提交作业后累加器又完成了一轮计数,所以最终累加器的值为18。究其原因是因为count虽然促使numberRDD被计出来,但是由于没有对其进行缓存,所以下次再次需要使用numberRDD这个数据集是,还需要从并行化数据集的部分开始执行计算。解释到这里,这个问题的解决方法也就很清楚了,就是在count之前调用numberRDD的cache方法(或persist),这样在count后数据集就会被缓存下来,reduce操作就会读取缓存的数据集而无需从头开始计算了。改成如下代码即可:

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

/**
  * @Auther: chenyj
  * @Date: 2018/9/7 14:23
  * @Description:
  */
object spark {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("spark").setMaster("local[2]")
    val sc = new SparkContext(sparkConf)
    val accum = sc.longAccumulator("longAccum") //统计偶数的个数
    val rddSum = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
      .map(n=>{
          accum.add(1L)
        n
    })
    //cache()
    rddSum.cache().count
    println("accum1:"+accum.value)
    rddSum.reduce(_+_)
    println("accum2: "+accum.value)
    sc.stop()
  }
}

3.自定义累加器

在2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式。官方同时给出了一个实现的示例:CollectionAccumulator类,这个类允许以集合的形式收集spark应用执行过程中的一些信息。例如,我们可以用这个类收集Spark处理数据时的一些细节,当然,由于累加器的值最终要汇聚到driver端,为了避免 driver端的outofmemory问题,需要对收集的信息的规模要加以控制,不宜过大。
实现自定义类型累加器需要继承AccumulatorV2并至少覆写下例中出现的方法,下面这个累加器可以用于在程序运行过程中收集一些文本类信息,最终以Set[String]的形式返回。

自定义累加器

import java.util

import org.apache.spark.util.AccumulatorV2

/**
  * @Auther: chenyj
  * @Date: 2018/9/7 14:55
  * @Description:
  */
class teAccumulator extends AccumulatorV2[String, java.util.Set[String]]{
  private val _teArray: java.util.Set[String] = new java.util.HashSet[String]()

  override def isZero: Boolean = {
    _teArray.isEmpty
  }

  override def copy(): AccumulatorV2[String, util.Set[String]] = {
    val newTe = new teAccumulator();
    _teArray.synchronized{
      newTe._teArray.addAll(_teArray)
    }
    newTe
  }

  override def reset(): Unit = {
    _teArray.clear()
  }

  override def add(v: String): Unit = {
    _teArray.add(v)
  }

  override def merge(other: AccumulatorV2[String, util.Set[String]]): Unit = {
    other match {
      case o: teAccumulator => _teArray.addAll(o.value)
    }
  }

  override def value: util.Set[String] = {
    java.util.Collections.unmodifiableSet(_teArray)
  }
}

测试类

import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.JavaConversions._

/**
  * @Auther: chenyj
  * @Date: 2018/9/7 14:23
  * @Description:
  */
object spark {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("spark").setMaster("local[2]")
    val sc = new SparkContext(sparkConf)
    val accum = new teAccumulator()
    sc.register(accum,"teAccSum")
    val sum = sc.parallelize(Array("1", "2a", "3", "4b", "5", "6", "7c", "8d", "9"), 2).filter(line => {
      val pattern = """^-?(\d+)"""
      val flag = line.matches(pattern)
      if (!flag) {
        accum.add(line)
      }
      flag
    }).map(_.toInt).reduce(_ + _)

    println("sum: " + sum)

    for(i <- accum.value)
      println(i)
    sc.stop()
    }
}

ps: 注意导入 import scala.collection.JavaConversions._
否则报错 :value foreach is not a member of java.util.Set[String]
结果:

sum: 24
7c
8d
4b
2a
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值