目录
AccumulatorV2 vs AccumulatorV1
package core.bc
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable
object AccWordCount {
def main(args: Array[String]): Unit = {
val sparkConf=new SparkConf().setMaster("local").setAppName("AccWordCount")
val sc = new SparkContext(sparkConf)
val value = sc.makeRDD(List("hello","spark","hello"))
//累加器:WordCount
//创建累加器对象
val wcAcc=new MyAccumulator()
//向Spark进行注册
sc.register(wcAcc, "wordCountAcc")
value.foreach(
word=>{
//数据的累加(使用累加器)
wcAcc.add(word)
}
)
//获取累加器结果
println(wcAcc.value)
sc.stop()
}
/**
* 自定义数据累加器
* 1、继承AccumulatorV2。定义泛型
* IN:累加器输入的数据类型
* OUT:返回的数据类型
* 2、重写方法
*/
class MyAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
val wcMap = mutable.Map[String, Long]()
override def isZero: Boolean = wcMap.isEmpty//判断知否为初始状态
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = new MyAccumulator()//复制一个新的累加器
override def reset(): Unit = wcMap.clear()//重置累加器
override def add(word: String): Unit ={ //获取累加器需要计算的值
val newcount=wcMap.getOrElse(word,0L)+1L
wcMap.update(word,newcount)
}
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {//Driver合并多个累加器
val map1=this.wcMap
val map2=other.value
map2.foreach {
case (word, count) => {
val newCount = map1.getOrElse(word, 0L) + count
wcMap.update(word, newCount)
}
}
}
override def value: mutable.Map[String, Long] = wcMap //获取累加器结果
}
}
1. 代码功能概述
该代码使用Apache Spark实现了一个基于自定义累加器的单词计数(WordCount)程序。通过自定义MyAccumulator
类(继承AccumulatorV2
),统计RDD中每个单词的出现次数,并利用累加器的分布式聚合特性将结果汇总到驱动程序。
2. 代码逐段解析
主程序逻辑
object AccWordCount {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("AccWordCount")
val sc = new SparkContext(sparkConf)
val value = sc.makeRDD(List("hello", "spark", "hello"))
// 创建并注册累加器
val wcAcc = new MyAccumulator()
sc.register(wcAcc, "wordCountAcc")
// 遍历RDD,累加单词
value.foreach(word => wcAcc.add(word))
// 输出结果
println(wcAcc.value) // 预期输出:Map(hello -> 2, spark -> 1)
sc.stop()
}
}
- RDD创建:
sc.makeRDD
生成包含3个单词的RDD。 - 累加器注册:
MyAccumulator
实例通过sc.register
注册到SparkContext,名称为wordCountAcc
。 - 累加操作:
foreach
遍历RDD中的每个单词,调用wcAcc.add(word)
累加计数。 - 结果获取:
wcAcc.value
返回最终的单词计数Map。
自定义累加器 MyAccumulator
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Long]] {
val wcMap = mutable.Map[String, Long]()
override def isZero: Boolean = wcMap.isEmpty
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = new MyAccumulator()
override def reset(): Unit = wcMap.clear()
override def add(word: String): Unit = {
val newCount = wcMap.getOrElse(word, 0L) + 1L
wcMap.update(word, newCount)
}
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
val map1 = this.wcMap
val map2 = other.value
map2.foreach { case (word, count) =>
val newCount = map1.getOrElse(word, 0L) + count
wcMap.update(word, newCount)
}
}
override def value: mutable.Map[String, Long] = wcMap
}
- 核心字段:
wcMap
用于存储单词及其计数。 - 关键方法:
isZero
:判断累加器是否为空(初始状态)。copy
:创建累加器的副本(用于任务节点本地计算)。reset
:清空累加器状态。add
:累加单个单词的计数。merge
:合并其他累加器的统计结果(分布式汇总)。value
:返回最终结果。
3. Spark累加器原理
累加器的作用
- 分布式聚合:在多个任务节点上独立计算局部结果,最后汇总到驱动程序。
- 高效通信:避免频繁的Shuffle操作,减少网络开销。
- 线程安全:Spark保证每个任务节点内的累加器操作是串行的。
AccumulatorV2 vs AccumulatorV1
- AccumulatorV1:仅支持简单数据类型(如
Long
、Double
),适用于计数、求和等场景。 - AccumulatorV2:支持复杂数据类型(如Map、List),需自定义
add
和merge
方法,适用于更灵活的聚合需求(如WordCount)。
累加器执行流程
- 任务节点本地计算:每个任务节点维护累加器的本地副本,通过
add
方法累加数据。 - 结果汇总:任务完成后,Spark将各节点的累加器副本发送到驱动程序,调用
merge
方法合并结果。 - 驱动程序获取结果:通过
value
方法获取全局聚合结果。
4. 代码扩展与优化建议
支持多词统计
当前代码统计单次出现的单词,若需统计多个单词(如键值对),可修改add
方法:
override def add(input: String): Unit = {
val words = input.split("\\s+") // 按空格分割多词
words.foreach(word => {
val newCount = wcMap.getOrElse(word, 0L) + 1L
wcMap.update(word, newCount)
})
}
线程安全优化
若add
方法可能被多线程并发调用(如在复杂算子中),需添加同步锁:
override def add(word: String): Unit = this.synchronized {
val newCount = wcMap.getOrElse(word, 0L) + 1L
wcMap.update(word, newCount)
}
使用内置累加器
对于简单场景(如全局计数),可直接使用Spark内置的LongAccumulator
:
val countAcc = sc.longAccumulator("countAcc")
value.foreach(_ => countAcc.add(1))
println(countAcc.value) // 输出总记录数
5. Spark累加器的适用场景
- 全局计数:统计任务处理的总记录数、错误数等。
- 分组统计:如WordCount、用户行为分类统计。
- 指标监控:实时计算平均值、最大值等(需结合自定义逻辑)。
- 调试与日志:在不中断作业的情况下收集分布式运行状态。
6. 总结
该代码通过自定义AccumulatorV2
实现了分布式单词计数,展示了累加器的核心原理:任务节点本地计算 + 驱动程序全局汇总。通过合理设计add
和merge
方法,累加器可支持复杂聚合逻辑,是Spark中高效的分布式统计工具。