共享变量

(1)累加器:是用来对信息进行聚合的,同时也是Spark中提供的一种分布式的变量机制,其原理类似于mapreduce,即分布式的改变,然后聚合这些改变。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。 Spark内置的提供了Long和Double类型的累加器

object AccumulatorDemo {
def main(args: Array[String]): Unit = {
//通过conf创建sc 创建Spark配置对象
val sc = new SparkContext(new SparkConf().setMaster("local").setAppName("AccumulatorDemo"))
val file = sc.textFile("file:///F:/spark/c.txt")
val blankLines = sc.longAccumulator("blankLines");
val info = file.flatMap(line => {
if (line == "") {
blankLines.add(1L)
}
line.split(" ")
})
info.saveAsTextFile("file:///F:/spark/out1")
println("BlankLines= " + blankLines)
}
}

此代码用于实现累加文档中的空行数目。输出

BlankLines= LongAccumulator(id: 0, name: Some(blankLines), value: 0)

**需要注意的是:**XXXXAccumulator也是lazy的,在行动操作前的转化操作是不会进行求值的。

这里用一个更好的例子来解释这个lazy的效果。

    val accum = sc.longAccumulator("AccumulatorDemo")
val numberRDD = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2).map(n=>{
accum.add(1L)
n+1
})
numberRDD.count
println("accum1:"+accum.value)
numberRDD.reduce(_+_)
println("accum2: "+accum.value)

结果如下

accum1:9
accum2: 18

可见,虽然在map里面进行了累加器加1的操作,两个累加值却不一样,这是因为count和reduce都是action,而且第一次count的action并没有持久化,此时的accum的value已经是9了,在下一次的reduce的action中依然要重新计算一次,相当于提交了两次作业,那reduce执行之后accume的value将会从9变为18。如果我们在count的action之前调用persist()(cache())进行持久化,这样的话reduce的action就不需要从头计算了,两次的accum的就会一样了。

自定义累加器:

isZero: 判断是否为初始值

copy:拷贝累加器

add: 操作数据累加方法实现

merge: 合并数据

value: AccumulatorV2对外访问的数据结果 “`

import org.apache.spark.util.AccumulatorV2

/**
* 实现字符串拼接
* */
class MyAccumulator extends AccumulatorV2[String, String] { private var res=""
override def isZero: Boolean = {
res==""
} override def copy(): AccumulatorV2[String, String] ={
val newAcc=new MyAccumulator()
newAcc.res=this.res
newAcc
} override def reset(): Unit = {
res=""
} override def add(v: String): Unit = {
res+=v+'-'
} 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 value: String = res
}

调用实现

def main(args: Array[String]): Unit = {
val sc = new SparkContext(new SparkConf()
.setAppName("Accumulator1")
.setMaster("local")) val myAcc = new MyAccumulator
sc.register(myAcc,"myAcc")
//val acc = sc.longAccumulator("avg")
val nums = Array("a","b","c","d","e","f","h","i")
val numsRdd = sc.parallelize(nums)
numsRdd.foreach(num => myAcc.add(num))
println(myAcc)
sc.stop()
}

执行结果

MyAccumulator(id: 0, name: Some(myAcc), value: a-b-c-d-e-f-h-i-)

(2)广播变量

首先要简单来了解一下闭包的概念:函数可以访问函数外面的变量,但是函数内对变量的修改,在函数外是不可见的。(闭包是一个函数,它返回值取决于在此函数之外声明的一个或多个变量的值。)

Spark的第二种共享变量类型是广播变量,它可以让程序高效的向所有的工作节点发送一个较大的只读值,以供一个或者多个spark操作来使用,广播变量可以解决闭包函数引用外部大变量引起的性能问题,广播变量将只读变量缓存在每个worker节点中,Spark使用了高效广播算法分发变量从而提高通信性能。

广播变量的优势:是因为不是每个task一份变量副本,而是变成每个节点的executor才一份副本。这样的话,就可以让变量产生的副本大大减少。

广播变量,初始的时候,就在Drvier上有一份副本。

task在运行的时候,想要使用广播变量中的数据,此时首先会在自己本地的Executor对应的BlockManager中,尝试获取变量副本;如果本地没有BlockManager,也许会从远程的Driver上面去获取变量副本;也有可能从距离比较近的其他节点的Executor的BlockManager上去获取,并保存在本地的BlockManager中;BlockManager负责管理某Executor对应的内存和磁盘上的数据,此后这个executor上的task,都会直接使用本地的BlockManager中的副本。

例如,50个executor,1000个task。一个map,10M:

默认情况下,1000个task,1000份副本。10G的数据,网络传输,在集群中,耗费10G的内存资源。

如果使用了广播变量。50个execurtor,50个副本。500M的数据,网络传输,而且不一定都是从Driver传输到每个节点,还可能是就近从最近的

节点的executor的bockmanager上拉取变量副本,网络传输速度大大增加;500M,大大降低了内存消耗。

import java.util
import org.apache.spark.{SparkConf, SparkContext}
object testBroadcast {
def main(args: Array[String]): Unit = {
//声明一个SparkContext对象
val sc=new SparkContext(new SparkConf().setMaster("local[*]").setAppName("BroadcastDemo"))
val rdd = sc.parallelize(List("1,张三","0,李四","3,王五"))
val map = new util.HashMap[String,String]()
map.put("1","男人")
map.put("0","女人")
//声明一个广播变量
val bd = sc.broadcast(map)
val rdd1=rdd.map(e=>{
val splits = e.split(",")
val sid = splits(0)
//获取广播变量中的值
val name = bd.value.getOrDefault(sid,"未知")
splits(1)+" is "+name+""
}).cache()
rdd1.foreach(println(_))
}
}

注意: 不能将RDD使用一个广播变量广播出去,因为RDD是不存储数据的。可以将RDD的结果广播出去。通过广播变量的value获取广播变量的值。

参考文章:

https://blog.csdn.net/u013468917/article/details/70617085

https://blog.csdn.net/leen0304/article/details/78866353

http://www.ccblog.cn/103.htm

https://www.cnblogs.com/newdingwei/p/6802972.html

Spark基础:(五)Spark编程进阶的更多相关文章

  1. spark实验(五)--Spark SQL 编程初级实践(1)

    一.实验目的 (1)通过实验掌握 Spark SQL 的基本编程方法: (2)熟悉 RDD 到 DataFrame 的转化方法: (3)熟悉利用 Spark SQL 管理来自不同数据源的数据. 二.实 ...

  2. Day7 - Python基础7 面向对象编程进阶

    Python之路,Day7 - 面向对象编程进阶   本节内容: 面向对象高级语法部分 经典类vs新式类 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 作业:开发一个 ...

  3. Python基础7 面向对象编程进阶

    本节内容: 面向对象高级语法部分 经典类vs新式类 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 经典 ...

  4. Python基础-week06 面向对象编程进阶

    一.反射 1.定义:指的是通过字符串来操作类或者对象的属性 2.为什么用反射? 减少冗余代码,提升代码质量. 3.如何用反射? class People: country='China' def __ ...

  5. Spark 基础操作

    1. Spark 基础 2. Spark Core 3. Spark SQL 4. Spark Streaming 5. Spark 内核机制 6. Spark 性能调优 1. Spark 基础 1. ...

  6. Spark菜鸟学习营Day3 RDD编程进阶

    Spark菜鸟学习营Day3 RDD编程进阶 RDD代码简化 对于昨天练习的代码,我们可以从几个方面来简化: 使用fluent风格写法,可以减少对于中间变量的定义. 使用lambda表示式来替换对象写 ...

  7. Spark函数式编程进阶

    函数式编程进阶 1.函数和变量一样作为Scala语言的一等公民,函数可以直接复制给变量: 2.函数更长用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称,但是匿名函数赋值给 ...

  8. Scala实战高手****第12课:Scala函数式编程进阶(匿名函数、高阶函数、函数类型推断、Currying)与Spark源码鉴赏

    /** * 函数式编程进阶: * 1.函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量 * 2.函数更常用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称 ...

  9. Spark学习之编程进阶——累加器与广播(5)

    Spark学习之编程进阶--累加器与广播(5) 1. Spark中两种类型的共享变量:累加器(accumulator)与广播变量(broadcast variable).累加器对信息进行聚合,而广播变 ...

  10. Spark编程基础_RDD初级编程

    摘要:Spark编程基础_RDD初级编程 RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素 ...

随机推荐

  1. path-sum leetcode C++

    Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all ...

  2. JAVA笔记2__类/封闭性/构造方法/方法的重载/匿名对象

    public class Main { public static void main(String[] args) { Chicken c1 = new Chicken(); Chicken c2 ...

  3. 『学了就忘』Linux基础命令 — 20、文件操作的相关命令

    目录 1.touch 命令 2.stat命令 3.cat命令 4.more命令 5.less命令 6.head命令 7.tail命令 1.touch 命令 touch命令用于创建空文件或修改文件时间, ...

  4. idea连接数据库时区错误

    错误界面 IDEA连接mysql,地址,用户名,密码,数据库名,全都配置好了,点测试连接,咔!不成功! 界面是这样的, 翻译过来就是:服务器返回无效时区.进入"高级"选项卡,手动设 ...

  5. Go语言核心36讲(Go语言进阶技术十六)--学习笔记

    22 | panic函数.recover函数以及defer语句(下) 我在前一篇文章提到过这样一个说法,panic 之中可以包含一个值,用于简要解释引发此 panic 的原因. 如果一个 panic ...

  6. SpringCloud 2020.0.4 系列之 Stream 消息广播 与 消息分组 的实现

    1. 概述 老话说的好:事情太多,做不过来,就先把事情记在本子上,然后理清思路.排好优先级,一件一件的去完成. 言归正传,今天我们来聊一下 SpringCloud 的 Stream 组件,Spring ...

  7. 大一C语言学习笔记(4)---自省篇

    博主"曾经"做过的傻事: #你有的*没打全 #你用/的时候没考虑()是一对的 #printf随后加\n #所有变量只要用,就一定要定义数据类型 #sqrt()代表根号 #inclu ...

  8. vuex基础(vuex基本结构与调用)

    import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const modulesA = { state:{//状态 count: ...

  9. selet 语句详解

    SELECT 语句的基本格式为:        SELECT 要查询的列名 FROM 表名字 WHERE 限制条件;        2.0 数学符号条件            SELECT 语句常常会 ...

  10. 你真的懂Redis的5种基本数据结构吗?

    摘要: 你真的懂Redis的5种基本数据结构吗?这些知识点或许你还需要看看. 本文分享自华为云社区<你真的懂Redis的5种基本数据结构吗?这些知识点或许你还需要看看>,作者:李子捌. 一 ...