Spark基础:(五)Spark编程进阶
共享变量
(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编程进阶的更多相关文章
- spark实验(五)--Spark SQL 编程初级实践(1)
一.实验目的 (1)通过实验掌握 Spark SQL 的基本编程方法: (2)熟悉 RDD 到 DataFrame 的转化方法: (3)熟悉利用 Spark SQL 管理来自不同数据源的数据. 二.实 ...
- Day7 - Python基础7 面向对象编程进阶
Python之路,Day7 - 面向对象编程进阶 本节内容: 面向对象高级语法部分 经典类vs新式类 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 作业:开发一个 ...
- Python基础7 面向对象编程进阶
本节内容: 面向对象高级语法部分 经典类vs新式类 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 经典 ...
- Python基础-week06 面向对象编程进阶
一.反射 1.定义:指的是通过字符串来操作类或者对象的属性 2.为什么用反射? 减少冗余代码,提升代码质量. 3.如何用反射? class People: country='China' def __ ...
- Spark 基础操作
1. Spark 基础 2. Spark Core 3. Spark SQL 4. Spark Streaming 5. Spark 内核机制 6. Spark 性能调优 1. Spark 基础 1. ...
- Spark菜鸟学习营Day3 RDD编程进阶
Spark菜鸟学习营Day3 RDD编程进阶 RDD代码简化 对于昨天练习的代码,我们可以从几个方面来简化: 使用fluent风格写法,可以减少对于中间变量的定义. 使用lambda表示式来替换对象写 ...
- Spark函数式编程进阶
函数式编程进阶 1.函数和变量一样作为Scala语言的一等公民,函数可以直接复制给变量: 2.函数更长用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称,但是匿名函数赋值给 ...
- Scala实战高手****第12课:Scala函数式编程进阶(匿名函数、高阶函数、函数类型推断、Currying)与Spark源码鉴赏
/** * 函数式编程进阶: * 1.函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量 * 2.函数更常用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称 ...
- Spark学习之编程进阶——累加器与广播(5)
Spark学习之编程进阶--累加器与广播(5) 1. Spark中两种类型的共享变量:累加器(accumulator)与广播变量(broadcast variable).累加器对信息进行聚合,而广播变 ...
- Spark编程基础_RDD初级编程
摘要:Spark编程基础_RDD初级编程 RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素 ...
随机推荐
- linux下文件特殊权限设置位S和沾附位T(转载)
今天在创建文件的时候,发现了文件权限末尾有个T,之前没留意过,后来又用c创建(open)了两个文件来查看,在我没有指定权限(省略open的第三个参数)的时候,有时还会出现S,虽然还没弄懂什么时候会出现 ...
- SpringCloud微服务实战——搭建企业级开发框架(十三):OpenFeign+Ribbon实现高可用重试机制
Spring Cloud OpenFeign 默认是使用Ribbon实现负载均衡和重试机制的,虽然Feign有自己的重试机制,但该功能在Spring Cloud OpenFeign基本用不上,除非 ...
- js 组合继承详解
目录 前言 原型链继承 构造函数继承 组合继承 前言 首先学习继承之前,要对原型链有一定程度的了解. 不了解可以去先阅读我另一篇文章,里面对原型链有一个较为详细的说明:js 原型链详解. 如果已经了解 ...
- 13.G1垃圾收集器
G1收集器是一款面向服务器的垃圾收集器,也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命.为什么对G1收集器给予如此高的期望呢?既然对G1收集器寄予了如此高的期望,那么他一定是有其 ...
- 折腾systemd-nspawn运行centos7
Archlinux创建Debian/Ubuntu的systemd-nspawn容器是很简单的,因为有debootstrap软件.某天我突然想装个centos7玩玩,搜了半天没发现有什么类似于deboo ...
- linux 安装docker(一)
1.安装环境 此处在Centos7进行安装,可以使用以下命令查看CentOS版本 lsb_release -a 在 CentOS 7安装docker要求系统为64位.系统内核版本为 3.10 以上,可 ...
- Linux环境下安装、配置Redis
linux下安装redis 官网下载链接:https://redis.io/download 安装 下载redis压缩包 1.选择Stable(5.0)下的Download 5.0.0 链接进行下载 ...
- Android ANR从原理到日志分析,记下来就够了
站在巨人的肩膀上可以看的更远 做一个优秀的搬运工 Android 彻底理解安卓应用无响应机制 Android ANR日志分析全面解析 优秀的文章不可独享,要扩散,要做好笔记,哈 <沁园春长沙&g ...
- Python基础(作用域)
def _private_1(name): return 'Hello, %s' % name def _private_2(name): return 'Hi, %s' % name def gre ...
- 限制q-error,防止产生次优计划
原文:<Preventing bad plans by bounding the impact of cardinality estimation errors> 摘要 文章定义了一个衡量 ...