在Spark中的RDD就是一个不可变的分布式对象集合。每个RDD都被分为多个分区,这些分区运行在集群中的不同节点上。RDD可以包含Java、Scala、Python中任意类型的对象,甚至可以包含用户自定义的对象。
RDD的两种创建方法:
JavaRDD lines = sc.textFile("README.md");
创建出来后,RDD支持两种类型的操作:转化操作(transformation)和行动操作(action)。转化操作会由一个RDD生成一个新的RDD。例如,根据谓词匹配情况筛选数据就是一个常见的转化操作。在我们的文本示例中,我们可以用筛选来生成一个只存储包含单词Python的字符串的新的RDD
JavaRDD pythonLines = lines2.filter(
(Function) s -> s.contains("Python")
);
咳咳 我也不知道对不对,我也不敢问。。。
另一方面,行动操作会对RDD计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统如HDFS中。first()就是我们之前调用的一个行动操作,它会返回RDD的第一个元素
String firstPythonLines = pythonLines.first();
转化操作和行动操作的区别在于Spark计算RDD的方式不同。虽然你可以在任何时候定义新的RDD,但Spark只会惰性计算这些RDD。它们只有第一次在一个行动操作中用到时,才会真正计算。这种策略刚开始看起来可能会显得有些奇怪,不过在大数据领域是很有道理的。比如,我们以一个文本文件定义了数据,然后把其中包含Python的行筛选出来。如果Spark在我们运行lines = sc.textFile()时就把文件中所有的行都读取并存储起来,就会消耗很多存储空间,而我们马上就要筛选掉其中的很多数据。相反,一旦Spark了解了完整的转化操作链之后,它就可以只计算求结果时真正需要的数据。事实上,在行动操作first()中,Spark只需要扫描文件知道找到第一个匹配的行为止,而不需要读取整个文件。
最后,默认情况下,Spark的RDD会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个RDD,可以使用RDD.persist()让Spark把这个RDD缓存下来。我们可以让Spark把数据持久化到许多不同的地方,可用的选项会在下面列出。
pythonLines.persist(StorageLevels.MEMORY_AND_DISK);
Spark提供了两种创建RDD的方式:
JavaRDD lines1 = sc.parallelize(Arrays.asList("pandas", "i like pandas"));
一般我们最常用的是另一种方式
JavaRDD lines2 = sc.textFile("README.md");
JavaRDD log = sc.textFile("log.txt");
JavaRDD errorLog = log.filter(
(Function) s -> s.contains("error")
);
该转化操作不会改变log的RDD数据,会生成一个新的RDD,加下来看一下union()转化操作
JavaRDD errorLog = log.filter(
(Function) s -> s.contains("error")
);
JavaRDD warningLog = log.filter(
(Function) s -> s.contains("warning")
);
JavaRDD badLog = errorLog.union(warningLog);
行动操作是第二种类型的RDD操作,他们会把最终求得的姐u哦返回到驱动器程序,或者写入外部存储系统中。由于行动操作需要生成实际的输出,他们会强制执行哪些求值必须用到的RDD的转化操作。
继续使用上面的例子,我们可能想输出关于badLog的一些信息。为此,需要使用两个行动操作来实现:用count()来返回计数结果,用take()来说及RDD中的一些元素
System.out.println("Input had " + badLog.count() + " concerning lines");
System.out.println("Here are 10 examples:");
for (String line: badLog.take(10)) {
System.out.println(line);
}
在这个例子中,我们在驱动器程序中使用take()获取了RDD中的少量元素。然后在本地遍历这些元素,并在驱动器端打印出来。RDD还有一个collect()函数,可以用来获取整个RDD中的数据。如果你的程序把RDD筛选到一个很小的规模,并且你想咋i本地处理这些数据时,就可以使用它。记住,只有当你的整个数据集能在单台机器的内存中放得下时,才能使用collect(),因此,并不能用在大规模数据集上。
RDD转化操作都是惰性求值的。这意味着在被调用行动操作之前Spark不会开始计算。这对新用户来说可能与直觉有些相违背。但对于哪些使用过诸如Haskell等函数式语言或者类似LINQ这样的数据处理框架的人来说,会似曾相识。
惰性求值意味着当我们对RDD调用转化操作时,操作不会立即执行。Spark会在内部记录下所要求执行的操作的相关信息。我们不应该把RDD看作存放着特定数据的数据集。把数据读取到RDD的操作也同样时惰性的。因此,当我们调用sc.textFile()时,数据并没有读取进来,而是在必要时才会读取。和转化操作一样的是,读取数据的操作也有可能会多次执行。
Spark使用惰性求值,这样就可以把一些操作合并到一起来减少计算数据的步骤。在类型Hadoop MapReduce 的系统中,开发者常常花费大量时间考虑如何把操作组合到一起,以减少mr的周期数。而在Spark中,写出一个非常复杂的映射并不见得能比使用很多简单的连续操作获得好很多的性能。因此,用户可以用更小的操作来组织他们的程序,这样也使这些操作更容易管理。