Spark一路火花带闪电——RDD编程

RDD基础

Spark 中的RDD 就是一个不可变的分布式对象集合。每个RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含Python、Java、Scala 中任意类型的对象,甚至可以包含用户自定义的对象。

用户可以使用两种方法创建RDD:读取一个外部数据集,或在驱动器程序里分发驱动器程序中的对象集合(比如list 和set)。

创建出来后,RDD 支持两种类型的操作: 转化操作(transformation) 和行动操作(action)。转化操作会由一个RDD 生成一个新的RDD。
转化操作和行动操作的区别在于Spark 计算RDD 的方式不同。一般来说,转换操作是对一个数据集里的所有记录执行某种函数,从而使记录发生改变;而执行通常是运行某些计算或聚合操作,并将结果返回运行 SparkContext 的驱动程序。
虽然你可以在任何时候定义新的RDD,但Spark 只会惰性计算这些RDD。它们只有第一次在一个行动操作中用到时,才会真正计算

区分转化算子和行动算子的文章:https://blog.csdn.net/caiandyong/article/details/53612561

  • 为什么会有惰性计算呢?
    如果Spark 在我们运行lines = sc.textFile(…) 时就把文件中所有的行都读取并存储起来,就会消耗很多存储空间,而我们马上就要筛选掉其中的很多数据。
    相反, 一旦Spark 了解了完整的转化操作链之后,它就可以只计算求结果时真正需要的数据。事实上,在行动操作first() 中,Spark 只需要扫描文件直到找到第一个匹配的行为止,而不需要读取整个文件

默认情况下,Spark 的RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个RDD,可以使用RDD.persist() 让Spark 把这个RDD 缓存下来。

总的来说,每个Spark 程序或shell 会话都按如下方式工作。

(1) 从外部数据创建出输入RDD。
(2) 使用诸如filter() 这样的转化操作对RDD 进行转化,以定义新的RDD。
(3) 告诉Spark 对需要被重用的中间结果RDD 执行persist() 操作。
(4) 使用行动操作(例如count() 和first() 等)来触发一次并行计算,Spark 会对计算进行优化后再执行。

创建RDD

前文说到,Spark 提供了两种创建RDD 的方式:读取外部数据集,以及在驱动器程序中对一个集合进行并行化。


  • 对一个集合进行并行化:

创建RDD 最简单的方式就是把程序中一个已有的集合传给SparkContext 的parallelize()方法。
Scala:
val lines =sc.parallelize(List("pandas", "i like pandas"))
Java:
JavaRDD<String> lines = sc.parallelize(Arrays.asList("pandas", "i like pandas"));


  • 从外部存储中读取数据来创建RDD:

用来将文本文件读入为一个存储字符串的RDD 的方法SparkContext.textFile()。
Scala:
val lines =sc.textFile("/path/to/README.md")
Java:
JavaRDD<String> lines = sc.textFile("/path/to/README.md");

RDD操作

3.1 转化操作

举个例子,假定我们有一个日志文件log.txt,内含有若干消息,希望选出其中的错误消息。
我们可以使用转化操作filter()。
Scala 实现:

val inputRDD = sc.textFile("log.txt")
val errorsRDD = inputRDD.filter(line => line.contains("error"))

Java 实现:

JavaRDD<String> inputRDD = sc.textFile("log.txt");
JavaRDD<String> errorsRDD = inputRDD.filter(
	new Function<String, Boolean>() {
	public Boolean call(String x) { return x.contains("error"); }
}
});

3.2 行动操作

行动操作会把最终求得的结果返回到驱动器程序,或者写入外部存储系统中。
在下面的例子中,我们在驱动器程序中使用take() 获取了RDD 中的少量元素。然后在本地遍历这些元素,并在驱动器端打印出来。
Scala实现:

println("Input had " + badLinesRDD.count() + " concerning lines")
println("Here are 10 examples:")
badLinesRDD.take(10).foreach(println)

Java实现:

System.out.println("Input had " + badLinesRDD.count() + " concerning lines")
System.out.println("Here are 10 examples:")
for (String line: badLinesRDD.take(10)) {
System.out.println(line);
}

RDD 还有一个collect() 函数,可以用来获取整个RDD 中的数据。。如果你的程序把RDD 筛选到一个很小的规模,并且你想在本地处理这些数据时,就可以使用它。记住,只有当你的整个数据集能在单台机器的内存中放得下时,才能使用collect(),因此,collect() 不能用在大规模数据集上

在大多数情况下,RDD 不能通过collect() 收集到驱动器进程中,因为它们一般都很大。此时,我们通常要把数据写到诸如HDFS 或Amazon S3 这样的分布式的存储系统中。你可以使用saveAsTextFile()saveAsSequenceFile(),或者任意的其他行动操作来把RDD 的数据内容以各种自带的格式保存起来。

==需要注意的是,每当我们调用一个新的行动操作时,整个RDD 都会从头开始计算。要避免这种低效的行为,用户可以将中间结果持久化。==这点,以后我们会补充。

向Spark传递函数

4.1 Scala实现

在Scala 中,我们可以把定义的内联函数、方法的引用或静态方法传递给Spark,就像Scala 的其他函数式API 一样。

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

4.2 Java实现

在Java 中,函数需要作为实现了Spark 的org.apache.spark.api.java.function 包中的任一函数接口的对象来传递。
在这里插入图片描述
在Java 中使用匿名内部类进行函数传递:

RDD<String> errors = lines.filter(new Function<String, Boolean>() {
	public Boolean call(String x) { return x.contains("error"); }
});

在Java 中使用具名类进行函数传递:

class ContainsError implements Function<String, Boolean>() {
	public Boolean call(String x) { return x.contains("error"); }
}
RDD<String> errors = lines.filter(new ContainsError());

具体风格的选择取决于个人偏好。不过我们发现顶级具名类通常在组织大型程序时显得比较清晰。使用顶级函数的另一个好处在于你可以给它们的构造函数添加参数。
带参数的Java 函数类:

class Contains implements Function<String, Boolean>() {
	private String query;
	public Contains(String query) { this.query = query; }
	public Boolean call(String x) { return x.contains(query); }
}
RDD<String> errors = lines.filter(new Contains("error"));

在Java 8 中,你也可以使用lambda 表达式来简洁地实现函数接口。

RDD<String> errors = lines.filter(s -> s.contains("error"));

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


  • 转化操作基本函数
    在这里插入图片描述
    在这里插入图片描述
    图中阐释了flatMap() 和map() 的区别。
    Scala 中的flatMap() 将行数据切分为单词:
val lines = sc.parallelize(List("hello world", "hi"))
val words = lines.flatMap(line => line.split(" "))
words.first() // 返回"hello"

在这里插入图片描述


  • 伪集合操作

尽管RDD 本身不是严格意义上的集合,但它也支持许多数学上的集合操作。
在这里插入图片描述
也可以计算两个RDD 的笛卡儿积:
在这里插入图片描述

在这里插入图片描述


  • 行动操作基本函数

对一个数据为{1, 2, 3, 3}的RDD进行基本的RDD行动操作
在这里插入图片描述


  • 在不同RDD类型间转换

有些函数只能用于特定类型的RDD,比如mean() 和variance() 只能用在数值RDD 上,而join() 只能用在键值对RDD 上。
在Scala 和Java 中,这些函数都没有定义在标准的RDD类中,所以要访问这些附加功能,必须要确保获得了正确的专用RDD 类。

在Scala 中,将RDD 转为有特定函数的RDD(比如在RDD[Double] 上进行数值操作)是由隐式转换来自动处理的。(需要加上import org.apache.spark.SparkContext._ 来使用这些隐式转换。)
隐式转换虽然强大,但是会让阅读代码的人感到困惑。如果你对RDD 调用了像mean() 这样的函数, 可能会发现RDD 类的Scala 文档中根本没有mean() 函数。调用之所以能够成功,是因为隐式转换可以把RDD[Double] 转为DoubleRDDFunctions。

在Java 中,各种RDD 的特殊类型间的转换更为明确。Java 中有两个专门的类JavaDoubleRDD和JavaPairRDD,来处理特殊类型的RDD,这两个类还针对这些类型提供了额外的函数。
Java中针对专门类型的函数接口:
在这里插入图片描述
用Java 创建DoubleRDD:

JavaDoubleRDD result = rdd.mapToDouble(
	new DoubleFunction<Integer>() {
		public double call(Integer x) {
			return (double) x * x;
		}
	});
System.out.println(result.mean());

持久化(缓存)

为了避免多次计算同一个RDD,可以让Spark 对数据进行持久化。当我们让Spark 持久化存储一个RDD 时,计算出RDD 的节点会分别保存它们所求出的分区数据。

如果一个有持久化数据的节点发生故障,Spark 会在需要用到缓存的数据时重算丢失的数据分区。如果希望节点故障的情况不会拖累我们的执行速度,也可以把数据备份到多个节点上。

我们可以为RDD 选择不同的持久化级别:
在这里插入图片描述
在Scala)和Java 中,默认情况下persist() 会把数据以序列化的形式缓存在JVM 的堆空间中。如有必要,可以通过在存储级别的末尾加上“_2”来把持久化数据存为两份。

在Scala 中使用persist():

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

注意,我们在第一次对这个RDD 调用行动操作前就调用了persist() 方法。persist() 调用本身不会触发强制求值。

如果要缓存的数据太多,内存中放不下,Spark 会自动利用最近最少使用(LRU)的缓存策略把最老的分区从内存中移除。下一次要用到已经被移除的分区时,这些分区就需要重新计算。

猜你喜欢

转载自blog.csdn.net/No_Game_No_Life_/article/details/88863513