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种方式

  1. 读取外部数据集 (最常用的方式)
val lines = sc.textFile("/path/to/README.md")
  1. 在驱动器程序中对一个集合进行并行化。 将一个已有的集合传给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之间的依赖关系。

SPARK RDD 生成矩阵 spark rdd的操作有几种_Scala


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_))
}
}

常见的转化操作和行动操作

  1. 针对各个元素的转化操作
    map()和filter()

map()的输入RDD和输出RDD可以不是同一类型
flatMap()将行数据切分为单词

val lines = sc.parallelize(List("hello world", "hi"))
val words = lines.flatMap(line => line.split(" "))
words.first() // 􀝓􀣮"hello"

SPARK RDD 生成矩阵 spark rdd的操作有几种_Spark_02


2. 伪集合操作

RDD支持许多数学上的集合操作,要求操作的RDD数据类型相同。RDD.distinct() 返回只包含不同元素的新RDD,开销比较大

SPARK RDD 生成矩阵 spark rdd的操作有几种_Scala_03


union中可包含重复数据。

intersection不包含重复数据,性能较差,因为需要通过网络混洗数据。

subtract和intersection一样,也需要数据混洗。

SPARK RDD 生成矩阵 spark rdd的操作有几种_大数据_04


cartesian(other)求两个RDD的笛卡尔积,使用场景:在求用户相似度的应用,考虑所有可能的组合的相似度等应用。

注意:求大规模RDD的笛卡尔积开销巨大。

SPARK RDD 生成矩阵 spark rdd的操作有几种_数据_05


SPARK RDD 生成矩阵 spark rdd的操作有几种_Spark_06

  1. 行动操作
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的所有元素应用一个行动操作,但不把任何结果返回到驱动器程序中。

SPARK RDD 生成矩阵 spark rdd的操作有几种_数据_07

  1. 在不同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的堆空间中。

SPARK RDD 生成矩阵 spark rdd的操作有几种_SPARK RDD 生成矩阵_08

在Scala中使用persist()

val result = input.map(x => x * x)
result.persist(StorageLevel.DISK_ONLY)
println(result.count())
println(result.collect().mkString(","))

RDD 还有一个方法叫做unpersist(),调用该方法可以手动把持久化的RDD从缓存中移除。