本节主要针对Spark对数据的核心抽象——弹性分布式数据集(Resilient Distributed Dataset,简称RDD)。在Spark中对数据的所有操作不外乎创建RDD,转换已有RDD以及调用RDD操作进行求值。在这一切的背后Spark自动将RDD中的数据分发到集群上,并将操作并行化执行。
RDD基础
Spark中的RDD是一个不可变的分布式对象集合,每个RDD都被分成多个分区,这些分区运行在集群的不同节点上。RDD中可以包含Python,Java,Scala中任意类型的对象,甚至用户自定义的对象。_
创建RDD:两种方式——读取一个外部数据集或在驱动器程序里分发驱动器程序中的对象集合。
创建出来的RDD支持两种类型的操作——转化操作和行动操作。转化操作会由一个RDD生成一个新的RDD,行动操作会对RDD计算出一个结果。
注意虽然你可以在任何时候定义一个RDD,但是Spark只会惰性计算这些RDD,它们只会在对一次在一个行动操作中用到时,才会真正计算。
其次Spark的RDD会在每次对它们进行行动操作时重新计算,如果需要在多个行动操作中重用一个RDD,可以使用RDD.persist()让Spark吧这个RDD缓存起来。
一般来说每个Spark程序都按照如下方式进行工作:
(1)从外部数据中创建出输入RDD
(2)使用诸如fliter()这样的转化操作对RDD进行转化,以定义新的RDD
(3)告诉Spark对需要被重用的中间结果RDD执行persist()操作
(4)使用行动操作来触发一次并行计算,Spark会对计算进行优化后再执行
创建RDD
Spark提供两种创建RDD的方式:读取一个外部数据集或在驱动器程序里分发驱动器程序中的对象集合。
创建一个RDD最简单方式就是把程序中一个已有的集合传给SparkContext的parallelize()方法。
lines=sc.parallelize(["pandas","i like pandas"])
更加常用的方式是从外部存储中读取数据来创建RDD,例如之前例子中的读取文本文件README的例子
lines = sc.textFlie("README.md")
RDD操作
RDD支持两种类型的操作——转化操作和行动操作
1.转化操作
inputRDD = sc.textFlie("log.txt")
errorRDD = inputRDD.filter(lambda x : "error" in x)
warningRDD = inputRDD.filter(lambda x : "warning" in x)
badLineRDD = errorRDD.union(warningRDD)
在上述例子中首先读取文本文件log.txt文件得到inputRDD输入RDD,在使用filter()方法分别获得日志文件中错误数据和警告数据RDD,在使用union()方法将两个RDD合并求并集。
在Sprak中会使用谱系图来记录不同RDD之间的依赖关系。Spark使用这些信息按需计算每一个RDD,并在持久化数据丢失时进行数据恢复。
2.行动操作
print("Input had" + badLinesRDD.count() + "concerning lines")
print("Here are 10 example")
for line in badLinesRDD.take(10):
print(line)
上述例子中对badLinesRDD进行统计计数,并使用take()方法获取10个例子,最后遍历输出。
3.惰性求值
RDD的转化操作是惰性求值的,着意味着在调用RDD进行行动操作时不会真正开始计算RDD。同时从外部读取数据转化RDD时也不是立刻执行的,数据并没有读取出来。惰性求值的方式可以将一些操作合并起来减少计算步骤,使操作更加容易管理。
向Spark传递函数
在Python中拥有3种方式把函数传递给Spark——lambda表达式,顶层函数和定义的局部函数。
word = rdd.filter(lambda s : 'error' in s)
def containsError(s):
return 'error' in s
word = rdd.filter(containsError)
需要注意的是,Python会在你不经意间把函数所在的对象也序列话传出,当你传递的对象是某个对象的成员时,或者包含了对某个对象中的一个字段的引用时,Spark会把整个对象发送到工作节点上。
class SearchFunctions(object):
def __init__(self, query):
self.query = query
def isMatch(self, s):
return self.query in s
def getMatchesFunctionReference(self, rdd):
return rdd.filter(self.isMatch) # 问题在self.isMatch中引用了整个self对象
def getMatchesMemberReference(self, rdd):
return rdd.filter(lambda x: self.qurey in x) # 问题在self.qurey中引用了整个self对象
代替方案时,将需要的字段从对象中拿出来放到一个局部变量中,然后传递这个局部变量。
class SearchFunctions(object):
...
def getMatchesMemberNoReference(self, rdd):
query = self.query
return rdd.filter(lambda x: qurey in x)
常见的转化操作和执行操作
转化操作:
表一:对数据为{1,2,3,3}的RDD基本转化操作
函数名 | 目的 | 示例 | 结果 |
map() | 将函数应用与RDD的每一个元素,将返回值构成新的RDD | rdd.map(x => x+1) | {2,3,4,4} |
flatmap() | 将函数应用于RDD中的每一个元素,将返回的迭代器的每一个内容构成新的RDD。通常用来切片 | rdd.flatmap(x => x.to(3)) | {1,2,3,2,3,3,3} |
filter() | 返回一个有通过穿给filter()的函数的元素组成的RDD | rdd.filter(x => x!=1) | {2,3,3} |
distinct() | 去重 | rdd.distinct() | {1,2,3} |
sample(withReplacement, fraction, [seed]) | 对RDD采样,以及是否替换 | rdd.sample(false,0.5) | 非确定的 |
表二:对数据分别为{1,2,3}和{3,4,5}的RDD进行针对两个RDD的转化操作
函数名 | 目的 | 示例 | 结果 |
union() | 生成一个包含两个RDD中所有元素的RDD | rdd.union(other) | {1,2,3,3,4,5} |
intersection() | 求两个RDD共同元素的RDD | rdd.intersection(other) | {3} |
subtract() | 一处一个RDD中的内容 | rdd.subtract(other) | {1,2} |
cartesion() | 与另一个RDD的笛卡尔积 | rdd.cartesion(other) | {(1,3),(1,4),(1,5),…(3,5)} |
行动操作
表三:对数据为{1,2,3,3}的RDD进行基本的RDD操作
函数名 | 目的 | 示例 | 结果 |
collect() | 返回RDD所有的元素 | rdd.collect() | {1,2,3,3} |
count() | 返回RDD中元素的个数 | rdd.count() | 4 |
countByValue() | 各元素在RDD中出现的次数 | rdd.countByValue() | {(1,1),(2,1),(3,2)} |
take(num) | 从RDD中返回num个元素 | rdd.take(2) | {1,2} |
top(num) | 从RDD中返回最前面的num个元素 | rdd.top(2) | {3,3} |
takeOrdered(num)(ordering) | 从RDD中按照提供的顺序返回最前面的num个元素 | rdd.takeOrdered(2)(myOrdering) | {3,3} |
takeSample(withReplacement,num,[seed]) | 从RDD中返回任意一些元素 | rdd.takeSample(false,1) | 非确定性的 |
reduce(func) | 并行整合RDD中所有数据 | rdd,reduce((x,y) => x + y) | 9 |
fold(zero)(func) | 和reduce一样,不过需要提供初始值 | rdd.fold(0)((x,y) => x + y) | 9 |
aggregate(zeroValue)(seqOp,combOp) | 和reduce相似,但是通常返回不同类型的函数 | rdd.aggregate((0,0))(…) | (9,4) |
foreach(func) | 对RDD中的每一个元素使用给定的函数 | rdd.foreach(func) | 未知 |
持久化
Spark RDD是惰性求值的,有时对于一个RDD我们会重复使用,如果简单的调用,Spark每次都会重复计算RDD以及它的所有依赖。这时就需要对RDD进行持久化处理。
表四:持久化级别
级别 | 使用的空间 | cpu时间 | 是否在内存中 | 是否在磁盘上 | 备注 |
MEMORY_ONLY | 高 | 低 | 是 | 否 | |
MEMORY_ONLY_SER | 低 | 高 | 是 | 否 | |
MEMORY_AND_DISK | 高 | 中等 | 部分 | 部分 | 如果内存上放不下,溢写到磁盘 |
MEMORY_AND_DISK_SER | 低 | 高 | 部分 | 部分 | 如果内存上放不下,溢写到磁盘 |
DISK_ONLY | 低 | 高 | 否 | 是 |