Spark RDD初探(一)
本文概要
本文主要从以下几点阐述RDD,了解RDD
- 什么是RDD?
- 两种RDD创建方式
- 向给spark传递函数Passing Functions to Spark
- 两种操作之转换Transformations
- 两种操作之行动Actions
- 惰性求值
- RDD持久化Persistence
- 理解闭包Understanding closures
- 共享变量Shared Variables
- 总结
Working with Key-Value Pairs、Shuffle operations、patitioning与并行度、DoubleRDDFunctions、RDD如何保障数据处理效率、RDD对容错的支持等知识点将做下一篇文章中阐述。
与许多专有的大数据处理平台不同,Spark建立在统一抽象的RDD之上,使得它可以以基本一致的方式应对不同的大数据处理场景,包括MapReduce,Streaming,SQL,Machine Learning以及Graph等。
要使用Spark,首先需要理解RDD。
1. 什么是RDD?
官网解释:
the concept of a resilient distributed dataset (RDD), which is a fault-tolerant collection of elements that can be operated on in parallel.
RDD全称为Resilient Distributed Datasets,是一个容错的集合,它可以并行操作。
RDD作为数据结构,本质上是一个只读的分区记录集合。一个RDD可以包含多个分区,每个分区就是一个dataset片段。多个分区可以由多个任务并行计算。
RDD有容错机制,这点将做下篇文章中探讨。
2. 两种RDD创建方式
- Parallelized Collections集合并行化:
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
- External Datasets从外部数据集创建:
- any storage source supported by Hadoop, including your local file system, HDFS, Cassandra, HBase, Amazon S3, etc. Spark supports text files, SequenceFiles, and any other Hadoop InputFormat.它支持text、SequenceFiles、任意的hadoop输入格式。
JavaRDD<String> distFile = sc.textFile("data.txt");
3. 向spark传递函数Passing Functions to Spark
Spark’s API relies heavily on passing functions in the driver program to run on the cluster. Spark’s API严重依赖传递给他的函数,驱动程序在集群上运行函数。
在java中需要实现接口 org.apache.spark.api.java.function,有如下两种方式:
- 实现接口:Implement the Function interfaces in your own class, either as an anonymous inner class or a named one, and pass an instance of it to Spark.
JavaRDD<String> lines = sc.textFile("data.txt");
JavaRDD<Integer> lineLengths = lines.map(new Function<String, Integer>() {
public Integer call(String s) { return s.length(); }
});
int totalLength = lineLengths.reduce(new Function2<Integer, Integer, Integer>() {
public Integer call(Integer a, Integer b) { return a + b; }
});
- lambda表达式:Use lambda expressions to concisely define an implementation.
JavaRDD<String> lines = sc.textFile("data.txt");
JavaRDD<Integer> lineLengths = lines.map(s -> s.length());
int totalLength = lineLengths.reduce((a, b) -> a + b);
4. 两种操作之转换Transformations
转换操作返回的结果是RDD. RDD-->RDD。
转换操作举例:
- map:
原始数据 | 函数 | 结果 |
---|---|---|
{1,2,3,4} | rdd.map(x => x+1) | {2,3,4,5} |
- filter
原始数据 | 函数 | 结果 |
---|---|---|
{1,2,3,4} | rdd.filter(x => x!=1) | {2,3,4} |
- flatmap: 1-->N
原始数据 | 函数 | 结果 |
---|---|---|
{1,2,3,4} | rdd.filter(x => x!=1) | {2,3,4} |
伪集合操作
- distinct(): 需要网络混洗数据shuffle
- intersection(): shuffle
- subtract(): shuffle
- union():
- cartesian: 笛卡尔积。应用于求用户相似度时。
注意:transformations只有在执行action时才会执行转换
5. 两种操作之行动Actions
Action: return a value to the driver program after running a computation on the dataset.在数据集计算完成后返回一个结果给驱动程序。
最常见的行动操作:reduce(func):通过函数func先聚集各分区的数据集,再聚集分区之间的数据,func接收两个参数,返回一个新值,新值再做为参数继续传递给函数func,直到最后一个元素
原始数据 | 函数 | 结果 |
---|---|---|
{1,2,3,4} | rdd.reduce((x, y) => x + y) | 10 |
6. 惰性求值
只有发生行动操作时,转换才会真正执行。
好处:This design enables Spark to run more efficiently. For example, we can realize that a dataset created through map will be used in a reduce and return only the result of the reduce to the driver, rather than the larger mapped dataset. spark运行性能更好。比如一个数据集结果map/reduce 处理后得到一个结果,而不是返回一个很大的map数据集。
- Spark可以把一些操作合并到一起来减少计算数据的步骤,比如可以对RDD进行多次转换。而在类似 Hadoop MapReduce 的系统中,开发者常常花费大量时间考虑如何把操作组合到一起,以减少MapReduce 的周期数。
- 写一些很复杂的映射,性能并不一定比简单操作好。所以可以写一些简单的连续的操作,这些操作更容易管理。
7. RDD持久化Persistence
RDD支持两种持久化方式:
- persist()
- cache(): Spark’s cache is fault-tolerant – if any partition of an RDD is lost, it will automatically be recomputed using the transformations that originally created it. cache是容错的,如果RDD的任一分区丢失了,它将自动重新计算。
在运行action时,RDD会被反复计算。为了避免多次计算同一RDD,可以对其持久化。
lineLengths.persist(StorageLevel.MEMORY_ONLY());
spark存储级别:
加上后缀‘_2’,持久化两份数据
存储级别的选择Which Storage Level to Choose?
- MEMORY_ONLY:the most CPU-efficient option。缺省的、最快的方式
- MEMORY_ONLY_SER : selecting a fast serialization library to make the objects much more space-efficient,but still reasonably fast to access. 通过快速序列化库节省空间,但是访问快递。
- Don’t spill to disk unless the functions that computed your datasets are expensive, or they filter a large amount of the data. 不要溢出到磁盘,除非计算数据集的成本太高,或者需要过滤大量数据
- Use the replicated storage levels if you want fast fault recovery.
Removing Data
内存管理算法:least-recently-used (LRU) fashion
手动清内存:RDD.unpersist() method.
8. 理解闭包Understanding closures
One of the harder things about Spark is understanding the scope and life cycle of variables and methods when executing code across a cluster.
理解变量和方法的范围和生命周期
var counter = 0
var rdd = sc.parallelize(data)
// Wrong: Don't do this!!
rdd.foreach(x => counter += x)
println("Counter value: " + counter)
本地模式 Vs. 集群模式
预备知识:Spark应用程序的组成
Driver: 驱动器,一个 job 只有一个,主要负责 job 的解析,与 task 的调度等。
Executor:执行器,实际运行 task 的地方,一个 job 有多个。
再回过头看上面代码的行为是有歧义的。
本地模式: 以本地模式运行在单个JVM上,运行程序的 JVM 和运行驱动器的 JVM 是同一个。所以操作就会引用到原始的 counter,对RDD中的值进行累加,并且将它存储到counter中。
集群模式: 以集群模式运行时,上面的代码的结果也许不会如我们预期的那样。当执行一个作业(job)时,Spark会将RDD 操作拆分成多个任务(task),多个task并行处理--每一个任务都会由一个executor来执行。
在执行之前,Spark会计算闭包(closure)。闭包是对executors可见的变量和方法,executors会用闭包来执行RDD上的计算(在这个例子中,闭包是foreach())。这个闭包是被序列化的,并且发送给每个executor。在本地模式中,只有一个executor,所以共享相同的闭包。然而,在集群模式中,就不是这样了。executors会运行在各自的worker节点中,每个executor都有闭包的一个副本。
发送给每个executor的闭包中的变量其实也是副本。每个foreach函数中引用的counter不再是driver节点上的counter。当然,在driver节点的内存中仍然存在这一个counter,但是这个counter对于executors来说是不可见的。executors只能看到自己的闭包中副本。这样,counter最后的值仍旧是0,因为所有在counter的操作只引用了序列化闭包中的值。
简单说,spark为了避免闭包引入的问题,仅处理闭包内的局部变量。
如何做到这一点?
- 将传入的变量和方法,拷贝到闭包中。
那么如何确保上面的行动Action正确执行,达到预期的效果?Shared Variables
9. 共享变量Shared Variables
两种使用模式:broadcast variables and accumulators.
广播变量Broadcast Variables
creating broadcast variables is only useful when tasks across multiple stages need the same data or when caching the data in deserialized form is important.什么时候创建广播变量?任务的多个步骤需要同一份数据 或者缓存数据的反序列化 很重要时。
Broadcast variables allow the programmer to keep a read-only variable cached on each machine rather than shipping a copy of it with tasks. 广播变量缓存在每台机器上。
The data broadcasted this way is cached in serialized form and deserialized before running each task.广播数据序列化后缓存在缓存中,在运行每个任务时进行反序列化操作。
Broadcast<int[]> broadcastVar = sc.broadcast(new int[] {1, 2, 3});
广播变量只能被传播一次,传播之后不能修改。
Accumulators
一般用来做集算器counter或者求和sum.
For accumulator updates performed inside actions only, Spark guarantees that each task’s update to the accumulator will only be applied once, i.e. restarted tasks will not update the value.累加器只在action中执行,spark保证每个task只更新一次累加值,重新执行任务不会更新其值。
LongAccumulator accum = jsc.sc().longAccumulator();
sc.parallelize(Arrays.asList(1, 2, 3, 4)).foreach(x -> accum.add(x));
accum.value();
// returns 10
累加器执行过程:
10. 总结
RDD的操作只有三种:
- 创建RDD
- 转化RDD。transformation
- 调用RDD操作进行求值。action
RDD是惰性求值,只有执行Action时,转化操作才会执行。
如果多次使用转化操作中的数据,可以将数据缓存,避免多次重复计算。
spark的难点之一,就是理解闭包。因为数据是在task中并行处理,所以不能在task中处理普通的全局变量,只能使用共享变量Shared Variables。
参考文献
- RDD Programming Guide
- RDD:基于内存的集群计算容错抽象
- 深入理解Spark RDD抽象模型和编写RDD函数
- 理解Spark的核心RDD
- Spark 3. RDD 操作一 基础 ,放入方法,闭包,输出元素, 使用 K-V 工作
- 理解Spark中的闭包(closure)
Spark RDD初探(一)的更多相关文章
- Spark RDD概念学习系列之rdd的依赖关系彻底解密(十九)
本期内容: 1.RDD依赖关系的本质内幕 2.依赖关系下的数据流视图 3.经典的RDD依赖关系解析 4.RDD依赖关系源码内幕 1.RDD依赖关系的本质内幕 由于RDD是粗粒度的操作数据集,每个Tra ...
- Spark Rdd coalesce()方法和repartition()方法
在Spark的Rdd中,Rdd是分区的. 有时候需要重新设置Rdd的分区数量,比如Rdd的分区中,Rdd分区比较多,但是每个Rdd的数据量比较小,需要设置一个比较合理的分区.或者需要把Rdd的分区数量 ...
- Spark RDD API详解(一) Map和Reduce
RDD是什么? RDD是Spark中的抽象数据结构类型,任何数据在Spark中都被表示为RDD.从编程的角度来看,RDD可以简单看成是一个数组.和普通数组的区别是,RDD中的数据是分区存储的,这样不同 ...
- Spark RDD aggregateByKey
aggregateByKey 这个RDD有点繁琐,整理一下使用示例,供参考 直接上代码 import org.apache.spark.rdd.RDD import org.apache.spark. ...
- Spark RDD解密
1. 基于数据集的处理: 从物理存储上加载数据,然后操作数据,然后写入数据到物理设备; 基于数据集的操作不适应的场景: 不适合于大量的迭代: 不适合交互式查询:每次查询都需要对磁盘进行交互. 基于数 ...
- Spark - RDD(弹性分布式数据集)
org.apache.spark.rddRDDabstract class RDD[T] extends Serializable with Logging A Resilient Distribut ...
- Spark RDD Operations(1)
以上是对应的RDD的各中操作,相对于MaoReduce只有map.reduce两种操作,Spark针对RDD的操作则比较多 ************************************** ...
- Spark RDD的依赖解读
在Spark中, RDD是有依赖关系的,这种依赖关系有两种类型 窄依赖(Narrow Dependency) 宽依赖(Wide Dependency) 以下图说明RDD的窄依赖和宽依赖 窄依赖 窄依赖 ...
- Spark RDD操作(1)
https://www.zybuluo.com/jewes/note/35032 RDD是什么? RDD是Spark中的抽象数据结构类型,任何数据在Spark中都被表示为RDD.从编程的角度来看,RD ...
随机推荐
- jmeter-抓取数据库数据,循环请求1000次(变量嵌套变量)
场景:数据库返回1000个数据,用这1000个数据去做请求 数据库请求结果为: 添加while控制器 添加BeanShell 预处理程序,${__counter(FALSE,Vcount)} 为自增函 ...
- python多进程单线程+协程实现高并发
并发:看起来像同时运行就是并发 并行:同一时间同时被执行叫做并行,最大并行数就是CPU核数 协程不是实实在在存在的物理基础和操作系统运行逻辑,只是程序员从代码层面避开了系统对遇到IO的程序会切走CPU ...
- [bzoj3357][Usaco2004]等差数列_动态规划_贪心
[Usaco2004]等差数列 题目大意:约翰发现奶牛经常排成等差数列的号码.他看到五头牛排成这样的序号:“1,4,3,5,7”很容易看出“1,3,5,7”是等差数列.给出N(1≤N≤2000)数字A ...
- 运用加密技术保护Java源代码(转)
出处:运用加密技术保护Java源代码 为什么要加密? 对于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以.遗憾的是,Java程序的源代码很容易被别人偷看.只要有一 ...
- 使用python的ctypes库实现内存的动态申请和释放
1.申请前内存占用情况 2.申请内存 from ctypes import * import time #在这里申请1G的内存,单位k mem = create_string_buffer(1024* ...
- CNN网络结点计算总结(1998)
图 来源:Gradient-Based Learning Applied to Document Recognition 参阅CSDN:https://blog.csdn.net/dcxhun3/ar ...
- hadoop面试题(自己整理版)
1. hadoop 运行原理2. mapreduce 原理3. mapreduce 的优化4.举一个简单的例子说下 mapreduce 是怎么运行的5. hadoop 中 combiner 的作用6. ...
- vue-cli实现原理
分析:https://kuangpf.com/vue-cli-analysis/create/basic-verification.html vue-cli-service :https://blog ...
- 封装一些简单的 dom 操作
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- vue项目中实现图片懒加载的方法
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载.这样子对于页面加载性能上会有很大的提升,也提高了用户体验. 实 ...