Spark RDD 总结
2019年11月21日
16:58
RDD两种操作
RDD 支持两种类型的操作:转化操作(transformation)和行动操作(action)
转化操作会由一个RDD生产一个新的RDD。 如fliter,map
行动操作会对RDD计算出一个结果。如first,count
两种操作区别在于Spark计算RDD的方式不同。Spark惰性计算,只有第一次在一个行动操作中用到时,才会真正计算。
RDD.persist()可以将RDD缓存下来,在多个行动操作中重用同一个RDD。
创建RDD,2种方式
- 读取外部数据集 (最常用的方式)
val lines = sc.textFile("/path/to/README.md")
- 在驱动器程序中对一个集合进行并行化。 将一个已有的集合传给SparkContext的parallelize()方法(除了开发原型和测试,这种方式用的不多)
val lines = sc.parallelize(List("pandas", "i like pandas"))
RDD操作
转化操作返回的是RDD,而行动操作返回的是其他的数据类型
errorsRDD = inputRDD.filter(lambda x: "error" in x)
warningsRDD = inputRDD.filter(lambda x: "warning" in x)
badLinesRDD = errorsRDD.union(warningsRDD)
以union()为例,转换操作可以操作任意数量的输入RDD
Spark会用谱系图(lineage graph)来记录这些不同RDD之间的依赖关系。
3-16:在Scala 中使用行动操作进行计数
println("Input had " + badLinesRDD.count() + " concerning lines")
println("Here are 10 examples:")
badLinesRDD.take(10).foreach(println)
collect函数不能用在大规模数据集上。单台机器内存能放下数据时使用。
最好把RDD看做我们通过转化操作构建出来的、记录如何计算数据的指令列表。读取数据操作也是惰性的。
向Spark传递函数
Python
注意:Python会在你不经意间吧函数所在的对象也序列化传出去。
正确做法:
class WordFunctions(object):
def getMatchesNoReference(self, rdd):
#安全:只把需要的字段提取到局部变量中
query = self.query
return rdd.filter(lambda x: query in x)
Scala
所传递的函数及其引用的数据需要是可序列化的(实现了JAVA的Serializable接口)
Scala示例
class SearchFunctions(val query: String) {
def isMatch(s: String): Boolean = {
s.contains(query)
}
def getMatchesFunctionReference(rdd: RDD[String]): RDD[String] = {
// 问题:"isMatch"表示"this.isMatch",因此我们需要传递整个"this"
rdd.map(isMatch)
}
def getMatchesFieldReference(rdd: RDD[String]): RDD[String] = {
// 问题:"query"表示"this.query",因此我们需要传递整个"this"
rdd.map(x => x.split(query))
}
def getMatchesNoReference(rdd: RDD[String]): RDD[String] = {
// 安全:只把我们需要的字字段拿出来放入局部变量中
val query_ = this.query
rdd.map(x => x.split(query_))
}
}
常见的转化操作和行动操作
- 针对各个元素的转化操作
map()和filter()
map()的输入RDD和输出RDD可以不是同一类型
flatMap()将行数据切分为单词
val lines = sc.parallelize(List("hello world", "hi"))
val words = lines.flatMap(line => line.split(" "))
words.first() // "hello"
2. 伪集合操作
RDD支持许多数学上的集合操作,要求操作的RDD数据类型相同。
RDD.distinct() 返回只包含不同元素的新RDD,开销比较大
union中可包含重复数据。
intersection不包含重复数据,性能较差,因为需要通过网络混洗数据。
subtract和intersection一样,也需要数据混洗。
cartesian(other)求两个RDD的笛卡尔积,使用场景:在求用户相似度的应用,考虑所有可能的组合的相似度等应用。
注意:求大规模RDD的笛卡尔积开销巨大。
- 行动操作
reduce()
val sum = rdd.reduce((x, y) => x + y)
aggregate()
#计算RDD的平均值
val result = input.aggregate((0, 0))(
(acc, value) => (acc._1 + value, acc._2 + 1),
(acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
val avg = result._1 / result._2.toDouble
RDD的一些行动操作会以普通集合或值的形式将RDD的全部或部分数据返回驱动器程序中。
如collect(),通常在单元测试中使用
take(n)返回RDD中的n个元素,而且尝试只访问尽量少的分区,返回顺序不确定
top()会使用数据的默认顺序
takeSample(withReplacement,num,seed)函数,从数据中获取一个采样,并指定是否替换。
foreach()对RDD的所有元素应用一个行动操作,但不把任何结果返回到驱动器程序中。
- 在不同RDD类型间转换
有些函数只能用于特定类型的RDD,如mean()和variance()只能用在数值RDD上。Join()只能用在键值对RDD上。
在Scala中,将RDD转为有特定函数的RDD(如在RDD[Double]上进行数值操作)是由隐式转换来自动处理的。
需要加上import org.apache.spark.SparkContext._来使用这些隐式转换。例如隐式转换可以把RDD[Double]转为DoublleRDDFunctions.查文档时不要忘记这些封装的专用类中的函数。
持久化(缓存)
例子:Scala中的两次执行
val result = input.map(x => x*x)
println(result.count())
println(result.collect().mkString(","))
为了避免多次计算同一个RDD,可以让Spark对数据进行持久化。
默认情况下persist()会把数据以序列化的形式缓存在JVM的堆空间中。
在Scala中使用persist()
val result = input.map(x => x * x)
result.persist(StorageLevel.DISK_ONLY)
println(result.count())
println(result.collect().mkString(","))
RDD 还有一个方法叫做unpersist(),调用该方法可以手动把持久化的RDD从缓存中移除。