Spark三大核心数据结构(二)——累加器 & 自定义累加器的使用原理

Spark的三大核心数据结构:RDD、累加器(只写不读)、广播变量(只读不写)

在spark应用程序中,我们经常会有这样的需求,如异常监控,调试,记录符合某特性的数据的数目,这种需求都需要用到计数器,如果一个变量不被声明为一个累加器,那么它将在被改变时不会再driver端进行全局汇总,即在分布式运行时每个task运行的只是原始变量的一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分布式计数的功能。

摘要:

(1)自定义变量在Spark中运算时,会从Driver中复制一份副本到Executor中运算,但变量的运算结果并不会返回给Driver,所以无法实现自定义变量的值改变,一直都是初始值,所以针对这个问题,引入了累加器的概念;

(2)系统累加器longAccumulator和自定义累加器(extends AccumulatorV2[类型,类型])实际都是两步,new累加器,然后sc.register注册累加器;

(3)先在Driver程序中创建一个值为0或者空的累加器对象,Task运算时,Executor中会copy一份累加器对象,在Executor中进行运算,累加器的运算结果返回给Driver程序并合并Merge,得出累加器最终结果;

(4)累加器.add(元素);具体对元素的操作包括数据sum、增加、删减、筛选等要求,都可以写在自定义累加器的.add()方法中。

目录

1. 累加器原理

2. 累加器原理图:

3. 自定义累加器

4. 自定义累加器——用法2:筛选数据

5. 累加器的Shell实现


1. 累加器原理

贴一段代码如下,来说明累加器的原理


 
 
  1. object Spark_Add {
  2. def main(args: Array[ String]): Unit = {
  3. val conf = new SparkConf().setMaster( "local[*]").setAppName( "Application")
  4. //构建Spark上下文对象
  5. val sc = new SparkContext(conf)
  6. var sum = 0
  7. val rdd = sc.makeRDD( Array( 1, 2, 3, 4, 5))
  8. rdd.map( item=>{
  9. sum = sum + item
  10. }).collect()
  11. println( "sum = "+sum)
  12. //释放资源
  13. sc.stop()
  14. }
  15. }
  16. -----------------------------------
  17. sum = 0

在Spark中声明SparkContext的类称为Driver,所以变量sum在Driver中;

而任务Task(即分区数据的运算)的执行是在Executor中进行,即sum = sum + item在Executor节点执行;

为什么Task运算完后,在Driver的IDEA客户端打印结果sum还是0呢?

问题的关键点在于:Executor只是做了运算,但并没有将sum运算后的值返回Driver中,也就是说Driver中的sum变量至始至终都保持初始值为0;

那么Spark中怎么解决将Executor中运算完毕的数据传回Driver中修改原始数据呢?这里就引入了“累加器”的概念


 
 
  1. object Spark_Add {
  2. def main(args: Array[ String]): Unit = {
  3. val conf = new SparkConf().setMaster( "local[*]").setAppName( "Application")
  4. //构建Spark上下文对象
  5. val sc = new SparkContext(conf)
  6. //使用系统默认累加器,默认初始值为0
  7. val sum = sc.longAccumulator( "sum")
  8. val rdd = sc.makeRDD( Array( 1, 2, 3, 4, 5))
  9. rdd.map( item=>{
  10. sum.add(item)
  11. }).collect()
  12. println( "sum = "+sum.value)
  13. //释放资源
  14. sc.stop()
  15. }
  16. }
  17. -------------------------------------
  18. sum = 15

因为累加器sum在Driver中,Executor中运算Task时,会把sum作为副本从Driver传递到Executor中,通过sum.add方法累加rdd元素;

并且会将各个Executor运算结果返回Driver,并作Merge合并操作,所以Driver中最终sum数据就是各个Executor运算合并后的结果。

 

2. 累加器原理图:

直接在Driver中声明共享变量,运算时会将其copy一份副本到各个Executor中,但是运算后不会将其返回;

如果共享数据以累加器的方式存在,那么copy运算后,还会从Executor中返回给Driver,实现Merge操作。

累加器的传递途径:序列化

 

3. 自定义累加器

上面的代码使用了系统默认累加器val sum = sc.longAccumulator("sum")

查看其源码:


 
 
  1. def longAccumulator(name: String): LongAccumulator = {
  2. val acc = new LongAccumulator
  3. register(acc, name)
  4. acc
  5. }

longAccumulator方法中,其实封装了两步,第一步new累加器对象,第二步完成累加器在当前SparkContext中注册


下面自定义累加器,也是用这两步


 
 
  1. //累加器
  2. object Spark_Add {
  3. def main(args: Array[String]): Unit = {
  4. val conf = new SparkConf().setMaster( "local[*]").setAppName( "Application")
  5. //构建Spark上下文对象
  6. val sc = new SparkContext(conf)
  7. //创建累加器
  8. val sum = new MyAccumulator()
  9. //注册累加器
  10. sc.register(sum, "accumulator")
  11. val rdd = sc.makeRDD(Array( 1, 2, 3, 4, 5))
  12. rdd.map(item=>{
  13. sum.add(item)
  14. }).collect()
  15. println( "sum = "+sum.value)
  16. //释放资源
  17. sc.stop()
  18. }
  19. }
  20. -----------------------------------------------
  21. sum = 15
  22. //自定义累加器
  23. class MyAccumulator extends AccumulatorV2[Int,Int]{
  24. var sum = 0
  25. //1. 是否初始状态(sum为0表示累加器为初始状态)
  26. override def isZero: Boolean = sum == 0
  27. //2. 执行器执行时需要拷贝累加器对象(把累加器对象序列化后,从Driver传到Executor)
  28. override def copy(): AccumulatorV2[ Int, Int] = {
  29. val mine = new MyAccumulator
  30. mine
  31. }
  32. //3. 重置数据(重置后看当前累加器是否为初始状态)
  33. override def reset(): Unit = sum = 0
  34. //累加数据
  35. override def add(v: Int): Unit = {
  36. sum = sum + v
  37. }
  38. //合并计算结果数据(把所有Executor中累加器value合并)
  39. override def merge(other: AccumulatorV2[ Int, Int]): Unit = {
  40. sum = sum + other.value
  41. }
  42. //累加器的结果
  43. override def value: Int = sum
  44. }

自定义累加器需要继承AccumulatorV2[类型,类型],重写所有方法即可;

可以看出创建自定义累加器,后需要sc注册该累加器,与系统默认累加器longAccumulator源码中封装的两步一致。

 

4. 自定义累加器——用法2:筛选数据

原理:累加器变量初始值为0或者空,通过add方法增加元素;

数据增加、删减、筛选的逻辑都可以写在自定义累加器的add方法中。

eg:写一个小demo,给定一个场景,从字符串中筛选出含b的元素


 
 
  1. object Spark_Add {
  2. def main(args: Array[String]): Unit = {
  3. val conf = new SparkConf().setMaster( "local[*]").setAppName( "Application")
  4. //构建Spark上下文对象
  5. val sc = new SparkContext(conf)
  6. //创建累加器
  7. val acc2 = new MyBlackAccumulator()
  8. //注册累加器
  9. sc.register(acc2, "accumulator")
  10. val rdd = sc.makeRDD(Array( "abc", "bcd", "efg"))
  11. rdd.map(s => {
  12. acc2.add(s)
  13. }).collect()
  14. println( "sum = " + acc2.value)
  15. //释放资源
  16. sc.stop()
  17. }
  18. }
  19. ---------------------------------------------------
  20. sum = [bcd, abc]
  21. //自定义累加器
  22. //传入元素String,返回的是String集合,要求无序不可重复,用java中hashSet
  23. class MyBlackAccumulator extends AccumulatorV2[String, java.util.HashSet[String]] {
  24. var blackList = new util.HashSet[String]()
  25. override def isZero: Boolean = {
  26. blackList.isEmpty
  27. }
  28. override def copy(): AccumulatorV2[String, util.HashSet[String]] = {
  29. val acc = new MyBlackAccumulator
  30. acc
  31. }
  32. override def reset(): Unit = {
  33. blackList.clear()
  34. }
  35. //包含b的加入黑名单,筛选逻辑写在add中
  36. override def add(v: String): Unit = {
  37. if (v.contains( "b")) {
  38. blackList.add(v)
  39. }
  40. }
  41. override def merge(other: AccumulatorV2[String, util.HashSet[String]]): Unit = {
  42. //把另外集合中数据合并,addAll方法
  43. blackList.addAll(other.value)
  44. }
  45. override def value: util.HashSet[String] = blackList
  46. }

 

5. 累加器的Shell实现

val a = sc.accumulator(0)
 
 

创建一个初始值为0的累加器

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值