一,什么是RDD
RDD是Spark的核心概念之一。从名称上来看,弹性分布式数据集(Resilient Distributed Datasets )是一种数据集(但在下文中我们可以看到并非完全如此)。每个RDD会被自动分割成若干分片,并由Spark自动分配到集群中的各个节点上运行。RDD的特点是在内存中运行,因此速度很快。且RDD数据由Spark自动分散到集群中运行和管理,因此对于程序来说是透明的。
创建RDD
创建RDD有三种方式:
1, Load一个外部数据集。Spark支持多种外部数据源,包括本地文件系统,HDFS,HBase,Cassandra,Amazon S3等等。
lines = sc.textFile(“README.md”)
2, 使用数组产生RDD。
lines = sc.parallelize([“pandas”, “i like pandas”])
或
data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)
3, 基于已有的RDD,创建新的RDD。
lines = sc.textFile(“README.md”)
pythonLines = lines.filter(lambda line: “Python” in line)
注意RDD一旦产生就无法被修改,因此只能基于旧RDD创建新的RDD。Spark会建立所有RDD的血统图(lineage)。当执行计算任务时,Spark根据血统图来生成所有RDD。另外如果有RDD由于意外失败了或受损了,Spark使用血统图来重建RDD。
操作RDD
对RDD的操作有两种:数据转换类(Transformations)和行为类(action)。
Transformations基于前一个RDD构建一个新的RDD。另一方面,Actions基于一个RDD计算结果,然后将结果返回给驱动程序或将其存储到外部存储系统(例如HDFS)。
要区分一个操作,例如testFile()是转换类还是行为类很简单,就看返回的是什么。Transformations返回一个RDD,而actions返回其他数据类型。
Transformations和actions的区别在于:Although you can define new RDDs any time, Spark only computes them in a lazy fashion, the first time they are used in an action. --这一行为有时也称为lazy operation。
问题的本质在于:有时RDD是一次性的,用完即可以被丢弃。而有时一个RDD会被多次使用,此时将RDD缓存起来可以提高效率。如果按顺序一步步生成RDD,Spark并不知道一个RDD在之后的步骤中是否可以被复用,还是一次性的。只有当出现了action时,Spark才能看到一个全局的,整体的血统图,也只有此时才能判断RDD是否可以重用,且是否应该缓存在内存中。
因此在上例中,我们使用sc.textFile(“README.md”)读入一个文件时,Spark并不会立即读入数据,而且等到actions发生时,才会触发loading。
特别需要注意的是每当我们调用action的时候,整个血统图中的RDD必须被重新计算。例如,对于同一个RDD,执行了一次sc.count()和一次sc.take(),二者都是action。这时Spark从血统图的第一个RDD开始,计算两次来分别得到count和take的结果。为了避免这样的浪费,可以将中间结果固化,例如如果需要多次使用RDD数据集,则可以将RDD持久化到内存或硬盘里。
总的来说,从数据的角度来说,在Spark中执行计算任务就是以下4步:
- Create some input RDDs from external data.
- Transform them to define new RDDs using transformations like filter().
- Ask Spark to persist() any intermediate RDDs that will need to be reused.
- Launch actions such as count() and first() to kick off a parallel computation, which is then optimized and executed by Spark.
二,常见的RDD操作
一些操作对所有类型的RDD都可以使用,而另一些操作只能在特殊的RDD类型使用。例如只有对于元素都是数字的RDD才能计算平均值。在下面的操作都是在RDD上通用的操作。
Transformations
map()
Map函数和MapReduce中的map意义相同,即返回一个新RDD,其元素是输入RDD中元素,按照某个规则得到的新元素。输入输出的RDD中包含的元素是一一对应的。
例如下例中,将nums中的每个元素自乘。
nums = sc.parallelize([1, 2, 3, 4])
squared = nums.map(lambda x: x * x)flatmap()
有时需要让RDD中的一个元素产生多个对应元素。如下例:
lines = sc.parallelize([“hello world”, “hi”])
words = lines.flatMap(lambda line: line.split(" "))filter()
返回一个新RDD,其元素是输入RDD元素中符合过滤条件的那些元素。union()
返回一个包含两个输入RDD元素的新RDD。intersection()
返回一个新RDD,其元素是两个输入RDD元素的交集。subtract()
返回一个新RDD,其元素是第一个输入RDD的元素且不是第二个输入RDD的元素。sample()
返回一个新RDD,其元素是输入RDD元素中符合按规则sample出来的那些元素。
Actions
reduce()
Reduce函数和MapReduce中的Reduce意义相同,用于对RDD中的数据做聚合操作。例如:
rdd = sc.parallelize([1,2,3,4,5])
rdd.reduce(lambda a,b:a+b)
其中输入参数为两个,输出1个。a代表聚合结果,b代表RDD中的元素。a+b则表示a=a+b,即将RDD中的所有元素累加到a上,且a为聚合的结果,15.count()
返回RDD元素的个数。collect()
有多种方式可以将RDD中的所有元素返回driver program,collect()是常用的一个。大家知道一个RDD的多个分区通常分布在集群中的各个节点内存里,而collect()将这些分散的RDD分区数据全部传回driver program所在的机器,合并后返回给driver program。
因此如果数据集很大,可能超过driver program所在机器的内存时,不建议使用collect()。take(n)
返回指定个数的RDD元素,返回的元素是随机的。top()
默认安装RDD中元素的顺序返回一个元素,也可以使用比较函数来返回最大或最小的元素。aggregate()
和reduce类似,但可以返回不同类型的RDD。