Accumulator累加器
累加器(accumulator)是Spark中提供的一种分布式的变量机制,其原理类似于mapreduce,即分布式的改变,然后聚合这些改变。
累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。
Accumulable简单值 结果类型和要合并的元素类型一样,
例如变量仅仅能“添加”到关联和交换操作 所以能在并行程序上有效支持
被用来实现计数器或者求和
spark原生支持数值类型的累加器,程序员可以自己添加新类型的支持
计数器被创建通过一个初始的V值,通过调用SparkContext.accumlator
运行在集群上的任务可以添加它通过+=操作符
然而,他们不能读取它的值
仅仅Driver 程序能读取accumulator的值 用value 方法
例如
val accum = sc.accumulator(0)
sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x)
accum.value
spark2以后用AccumulatorV2替代
注意:使用Accumulator时,为了保证准确性,只使用一次action操作。如果需要使用多次则使用cache或persist操作切断依赖。具体可以参照
https://blog.csdn.net/lsshlsw/article/details/50979579
AccumulatorV2
accumulators的基础类
可以计算IN类型的输入值,产生Out类型的输出值
Out可以是一个自动读取的类型如Int Long
或者线程安全的类型 如synchronized collections
因为它可以被其他线程读取
实战
//创建并注册一个long accumulator, 从“0”开始,用“add”累加
def longAccumulator(name: String): LongAccumulator = {
val acc = new LongAccumulator
register(acc, name)
acc
}
//创建并注册一个double accumulator, 从“0”开始,用“add”累加
def doubleAccumulator(name: String): DoubleAccumulator = {
val acc = new DoubleAccumulator
register(acc, name)
acc
}
//创建并注册一个CollectionAccumulator, 从“empty list”开始,并加入集合
def collectionAccumulator[T](name: String): CollectionAccumulator[T] = {
val acc = new CollectionAccumulator[T]
register(acc, name)
acc
}
自定义累加器
自定义累加器类型的功能在1.X版本中就已经提供了,但是使用起来比较麻烦,在2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式。官方同时给出了一个实现的示例:CollectionAccumulator类,这个类允许以集合的形式收集spark应用执行过程中的一些信息。例如,我们可以用这个类收集Spark处理数据时的一些细节,当然,由于累加器的值最终要汇聚到driver端,为了避免 driver端的outofmemory问题,需要对收集的信息的规模要加以控制,不宜过大。
实现自定义类型累加器需要继承AccumulatorV2并至少覆写下例中出现的方法,下面这个累加器可以用于在程序运行过程中收集一些文本类信息,最终以Set[String]的形式返回。
1、类继承extends AccumulatorV2[String, String],第一个为输入类型,第二个为输出类型
2、覆写抽象方法:
isZero: 当AccumulatorV2中存在类似数据不存在这种问题时,是否结束程序。
copy: 拷贝一个新的AccumulatorV2
reset: 重置AccumulatorV2中的数据
add: 操作数据累加方法实现
merge: 合并数据
value: AccumulatorV2对外访问的数据结果
案例:实现字符串的拼接
class MyAccumulator extends AccumulatorV2[String,String]{
private var res = ""
override def isZero: Boolean = {res == ""}
override def merge(other: AccumulatorV2[String, String]): Unit = other match {
case o : MyAccumulator => res += o.res
case _ => throw new UnsupportedOperationException(
s"Cannot merge ${this.getClass.getName} with ${other.getClass.getName}")
}
override def copy(): MyAccumulator = {
val newMyAcc = new MyAccumulator
newMyAcc.res = this.res
newMyAcc
}
override def value: String = res
override def add(v: String): Unit = res += v +"-"
override def reset(): Unit = res = ""
}
//2、调用:
object Accumulator1 {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("Accumulator1").setMaster("local")
val sc = new SparkContext(conf)
val myAcc = new MyAccumulator
sc.register(myAcc,"myAcc")
//val acc = sc.longAccumulator("avg")
val nums = Array("1","2","3","4","5","6","7","8")
val numsRdd = sc.parallelize(nums)
numsRdd.foreach(num => myAcc.add(num))
println(myAcc)
sc.stop()
}
}
注意:像map()这样的惰性转换中,不保证会执行累加器更新。
// Here, accum is still 0 because no actions have caused the map operation to be computed.
val accum = sc.longAccumulator
data.map { x => accum.add(x); x }
案例:字符串中的数据累加统计
class MyAccumulatorV2 extends AccumulatorV2[String, String] {
private val log = LoggerFactory.getLogger("MyAccumulatorV2")
var result = "user0=0|user1=0|user2=0|user3=0" // 初始值
override def isZero: Boolean = {
true
}
override def copy(): AccumulatorV2[String, String] = {
val myAccumulator = new MyAccumulatorV2()
myAccumulator.result = this.result
myAccumulator
}
override def reset(): Unit = {
result = "user0=0|user1=0|user2=0|user3=0"
}
override def add(v: String): Unit = {
val v1 = result
val v2 = v
// log.warn("v1 : " + v1 + " v2 : " + v2)
if (StringUtils.isNotEmpty(v1) && StringUtils.isNotEmpty(v2)) {
var newResult = ""
// 从v1中,提取v2对应的值,并累加
val oldValue = StringUtils.getFieldFromConcatString(v1, "\\|", v2)
if (oldValue != null) {
val newValue = oldValue.toInt + 1
newResult = StringUtils.setFieldInConcatString(v1, "\\|", v2, String.valueOf(newValue))
}
result = newResult
}
}
override def merge(other: AccumulatorV2[String, String]) = other match {
case map: MyAccumulatorV2 =>
result = other.value
case _ =>
throw new UnsupportedOperationException(
s"Cannot merge ${this.getClass.getName} with ${other.getClass.getName}")
}
override def value: String = {
result
}
}
/**
* 从拼接的字符串中提取字段
*
* @param str 字符串
* @param delimiter 分隔符
* @param field 字段
* @return 字段值
*/
def getFieldFromConcatString(str: String, delimiter: String, field: String): String = {
val fields = str.split(delimiter)
var result = "0"
for (concatField <- fields) {
if (concatField.split("=").length == 2) {
val fieldName = concatField.split("=")(0)
val fieldValue = concatField.split("=")(1)
if (fieldName == field) {
result = fieldValue
}
}
}
result
}
/**
* 从拼接的字符串中给字段设置值
*
* @param str 字符串
* @param delimiter 分隔符
* @param field 字段名
* @param newFieldValue 新的field值
* @return 字段值
*/
def setFieldInConcatString(str: String, delimiter: String, field: String, newFieldValue: String): String = {
val fields = str.split(delimiter)
val buffer = new StringBuffer("")
for (item <- fields) {
val fieldName = item.split("=")(0)
if (fieldName == field) {
val concatField = fieldName + "=" + newFieldValue
buffer.append(concatField).append("|")
} else {
buffer.append(item).append("|")
}
}
buffer.substring(0, buffer.length() - 1)
}
}
//使用
val accumulator = new MyAccumulatorV2()
sc.register(accumulator)
//需要注册,不然在运行过程中,会抛出一个序列化异常。
案例:利用自定义的收集器收集过滤操作中被过滤掉的元素,当然这部分的元素的数据量不能太大
import java.util
import org.apache.spark.util.AccumulatorV2
class LogAccumulator extends AccumulatorV2[String, java.util.Set[String]] {
private val _logArray: java.util.Set[String] = new java.util.HashSet[String]()
override def isZero: Boolean = {
_logArray.isEmpty
}
override def reset(): Unit = {
_logArray.clear()
}
override def add(v: String): Unit = {
_logArray.add(v)
}
override def merge(other: AccumulatorV2[String, java.util.Set[String]]): Unit = {
other match {
case o: LogAccumulator => _logArray.addAll(o.value)
}
}
override def value: java.util.Set[String] = {
java.util.Collections.unmodifiableSet(_logArray)
}
override def copy(): AccumulatorV2[String, util.Set[String]] = {
val newAcc = new LogAccumulator()
_logArray.synchronized{
newAcc._logArray.addAll(_logArray)
}
newAcc
}
}
//测试类
import scala.collection.JavaConversions._
import org.apache.spark.{SparkConf, SparkContext}
object Main {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("Test").setMaster("local[2]")
val sc = new SparkContext(sparkConf)
val accum = new LogAccumulator
sc.register(accum, "logAccum")
val sum = sc.parallelize(Array("1", "2a", "3", "4b", "5", "6", "7cd", "8", "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 (v <- accum.value) print(v + " ")
println()
sc.stop()
}
}