一,什么是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步:

  1. Create some input RDDs from external data.
  2. Transform them to define new RDDs using transformations like filter().
  3. Ask Spark to persist() any intermediate RDDs that will need to be reused.
  4. 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。