1、RDD概念
RDD的全称是弹性分布式数据集(Resilient Distributed Dataset),它是Spark对数据的核心抽象。在Spark中,对数据的操作可简单概括为创建RDD、转化RDD和调用RDD进行求值。此外,Spark会将RDD数据分发到集群的各个节点,并行执行。
2、创建RDD
Spark提供了两种创建RDD的方法,一个是通过程序中的集合,另一个是读取外部数据(包含读取本地文件和HDFS文件等)。
(1)通过集合创建RDD
最简单的创建RDD的方法是将代码中的集合传给SparkContext中的parallelize()方法。示例如下:JavaSparkContext sc = new JavaSparkContext();
JavaRDD city = sc.parallelize(Arrays.asList("beijing", "shanghai", "shenzhen"));
System.out.println(city.collect());
该方法可以方便我们在spark-shell中快速创建出需要的RDD,因此在我们学习Spark时经常用到,但在实际应用场景中,这种方法使用的极少。
(2)通过读取外部数据创建RDD
读取外部数据创建RDD使我们生产环境中经常会用到的方法,外部数据通常会包含本地文件、HDFS文件、MongoDB数据库等,下面苦李以读取本地日志文件为示例进行说明,示例如下:String logFile = "likuli_com_access.log";
SparkSession spark = SparkSession
.builder()
.appName("SparkTest")
.getOrCreate();
JavaRDD lines = spark.read().textFile(logFile).javaRDD();
System.out.println(lines.collect());
3、RDD操作
RDD支持两种操作,转化操作和行动操作。
转化操作返回一个全新的RDD,比如map()和filter(),行动操作返回的是其他数据类型,比如count()和first(),并且行动操作会出发实际的计算。
下面是一个转化操作filter()的典型案例:String logFile = "likuli_com_access.log";
SparkSession spark = SparkSession.builder().appName("SparkTest").getOrCreate();
JavaRDD lines = spark.read().textFile(logFile).javaRDD();
JavaRDD lines_error = lines.filter(s -> s.contains("error"));
值得注意的是,filter()不会改变原来的RDD lines,而是会生成新的RDD lines_error。lines在后续的操作中我们还可以继续使用,例如用来过滤warning日志等。
通过转化操作我们可以从已有的RDD中生成新的RDD,Spark内部会用谱系图的方式记录各个RDD之间的关系,以便在行动操作时对RDD进行计算,并且在数据丢失时依靠谱系图进行数据恢复。
下面是一个谱系图示例:
该谱系图表达的意思是,先将所有日志中的错误日志和告警日志分别过滤出来,再将两个RDD进行合并,最后得出所有的问题日志。
基于上面的代码,我们再来看一个行动操作的案例:System.out.println(":::: error lines count: " + lines_error.count());
System.out.println(":::: Here are 10 examples:");
for (String line: lines_error.take(10)) {
System.out.println(":::: " + line);
}
在上述代码中,我们得出所有error日志的总记录数,并且打印出其中10条的错误日志。
在执行行动操作时,我们应该注意,每调用一个新的行动操作,整个RDD都会重新进行计算,为提升我们的计算效率,最好将有需要的中间结果进行缓存。
4、RDD的惰性求值
RDD的转化操作都是惰性求值的,也就是说在调用行动操作之前Spark是不会开始进行计算的。
当我们调用转化操作时,Spark会在内部记录所要执行的信息,因此,严格意义上来说RDD不应该被看作是存放数据的数据集,而是通过转化操作构建出来的、记录如何计算数据的指令集。
此外,RDD的读取也是惰性的,这就是说,当我们调用textFile()函数时,数据并没有真正的读取,而是在有需要的时候才会读取。
为方便我们测试,我们可以通过运行一个行动操作强制Spark执行RDD的转化操作,比如使用count()方法。
惰性求值的优点是可以把一些操作进行合并以减少计算数据的步骤。在Hadoop的MapReduce中,我们通常需要花费大量的时间考虑如何把操作进行整合,以减少MapReduce的运行时间。但在Spark里,一个复杂的操作并不一定比使用多个简单的连续操作得到更好的运行性能。所以,我们在实际开发中可以用更小粒度的操作来组织Spark代码,以使得我们的程序更好维护。
5、常见的转化操作和行动操作
6、RDD缓存
因为RDD是惰性求值的,且在同一个程序里我们可以能要多次使用相同的RDD,如果只是简单对RDD调用行动操作,Spark每次都会重新计算RDD及其所有的依赖,这会造成极大的资源浪费。
为了避免资源浪费,提升代码运行效率,Spark可以通过persist()支持对RDD数据进行持久化操作。在Java中,persis()默认会把数据以序列化的方式缓存在JVM的堆空间里。