弹性分布式数据集(简称RDD)是Spark对数据的核心抽象。RDD其实就是分布式的元素集合。在Spark中,对数据的操作不外乎创建RDD、转化已有RDD以及调用RDD操作进行求值。而在这一切背后,Spark会自动将RDD中的数据分发到集群上,并将操作并行化执行。
3.1 RDD基础
Spark中的RDD就是一个不可变的分布式对象集合。每个RDD都被分为多个分区,这些分区运行在集群中的不同节点上。
例3-1:在Python中使用textFile()创建一个字符串的RDD
lines = sc.textFile("README.md")
创建出来后,RDD支持两种类型的操作:转化操作和行动操作。转化操作会由一个RDD生成一个新的RDD。行动操作会对RDD计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统(如HDFS)中。
例3-2:调用转化操作filter()
pythonLines = lines.filter(lambda line:"python" in line)
例3-3:调用first()行动操作
pythonLines.first()
Spark只会惰性计算这些RDD。它们只有第一次在一个行动操作中用到时,才会真正计算。Spark了解了完整的转化操作链之后,它就可以只计算求结果时真正需要的数据。
默认情况下,Spark的RDD会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个RDD,可以使用RDD.persist()让Spark把这个RDD缓存下来。在第一次对持久化的RDD计算之后,Spark会把RDD的内容保存在内存中(以分区方式存储到集群中的各个机器上)。
例3-4:把RDD持久化到内存中
pythonLines.persist()
pythonLines.count()
pythonLines.first()
3.2 创建RDD
Spark提供了两种创建RDD的方式:1读取外部数据集,2在驱动器程序中对一个集合进行并行化。
创建RDD最简单的方式就是把程序中一个已有的集合传给SparkContext的parallelize()方法,这种方式用的并不多,毕竟需要把整个数据集先放在一台机器的内存中。
例3-5:Python中的parallelize()方法
lines = sc.parallelize(["pandas", "i like pandas"])
例3-6:Scala中的parallelize()方法
val lines = sc.parallelize(List("pandas", "i like pandas"))
更常用的方式是从外部存储中读取数据来创建RDD。
例3-8 : Python中的textFile()方法
lines = sc.textFile("/path/to/README.md")
例3-9:Scala中的textFile()方法
val lines = sc.textFile("/path/to/README.md")
3.3 RDD操作
3.3.1 转化操作
RDD的转化操作是返回新RDD的操作。
例 3-11:用Python实现filter()转化操作
inputRDD = sc.textFile("log.txt") errorsRDD = inputRDD.filter(lambda x: "error" in x)
例 3-12:用Scala实现filter()转化操作
val inputRDD = sc.textFile("log.txt") val errorsRDD = inputRDD.filter(line => line.contains("error"))
filter()操作不会改变已有的inputRDD中的数据
例 3-14:用Python进行union()转化操作
errorsRDD = inputRDD.filter(lambda x:"error" in x) warningsRDD = inputRDD.filter(lambda x:"warning" in x) badlLinesRDD = errorsRDD.union(warningsRDD)
通过转化操作,我们从已有的RDD中派生出新的RDD,Spark会使用谱系图来记录这些不同RDD之间的依赖关系。Spark需要用这些信息来按需计算每个RDD,也可以依靠谱系图在持久化的RDD丢失部分数据时恢复所丢失的数据。
3.3.2 行动操作
行动操作会对数据集进行实际的计算,把最终求得的结果返回到驱动器程序,或者写入外部存储系统中。行动操作会强制执行那些求值必须用到的RDD的转化操作。
例3-15:在Python中使用行动操作对错误进行计数
print "Input had" + badLinesRDD.count() + "concerting lines" for line in badLinesRDD.take(10): print line
例3-16:在Scala中使用行动操作对错误进行计数
println("Input had " + badLinesRDD.count() + " concerning lines") badLinesRDD.take(10).foreach(println)
每当我们调用一的新的行动操作时,整个RDD都会从头开始计算。要避免这种低效的行为,我们可以将中间结果持久化。
3.3.3 惰性求值
我们不应该把RDD看作存放着特定数据的数据集,而最好把每个RDD当作我们通过转化操作构建出来的、记录如何生成新数据集的指令列表。
在Spark中,一个非常复杂的映射不会比使用很多简单的连续操作获得更好的性能。
3.4 向Spark传递函数
Spark的大部分转化操作和一部分行动操作,都需要依赖用户传递的函数来计算
3.4.1 Python
例 3-18:在Python中传递函数
word = rdd.filter(lambda s : "error" in s) def containsError(s): return "error" in s word = rdd.filter(containsError)
传递函数时需要小心的一点是,Python会在你不经意间把函数所在的对象也序列化传出去。
替代方案是,只把我们所需要的字段从对象中拿出来放到一个局部变量中,然后传递这个局部变量。
例3-20:传递不带字段引用的Python函数
class WordFunstions(object): def __init__(self, query): self.query = query def func(self, rdd): query = self.query return rdd.filter(lambda x: query in x)
3.4.2 Scala
与Python类似,传递一个对象的方法或者字段时,会包含对整个对象的引用。
我们可以把需要的字段放到一个局部变量中,来避免传递包含该字段的整个对象。
class SearchFunctions(val query: String){ def getMatchesNoReference(rdd: RDD[String]):RDD[String] = { val query_ = this.query rdd.map(x => x.split(query_)) } }