RDD详解
本篇文章是对Spark RDD论文的总结,中间会穿插一些Spark的内部实现总结,对应Spark版本为2.0。
Motivation
传统的分布式计算框架(如MapReduce)在执行计算任务时,中间结果通常会存于磁盘中,这样带来的IO消耗是非常大的,尤其是对于各种机器学习算法,它们需要复用上次计算的结果进行迭代,如果每次结果都存到磁盘上再从磁盘读取,耗时会很大。因此Spark这篇论文提出了一种新的分布式数据抽象 —— RDD。
设计思想及特点
Resilient Distributed Dataset(RDD)是Apache Spark中数据的核心抽象,它是一种只读的、分区的数据记录集合,RDD的特点:
- Lazy evaluation,只在需要的时候才进行计算
- RDD里面的数据是分区的,每一块数据都可能分布在集群内不同的节点上;支持并行计算
- Resilient: 借助RDD lineage graph,Spark可以重新执行之前失败的计算任务而不用整体上重新计算,保证了容错性而且非常灵活,实现了fault-tolerance
那么如何操作、处理数据呢?Spark提供了一组函数式编程风格的API,可以很方便地对RDD进行操作、变换,就像操作集合一样。比如:
val rdd = sc.parallelize(1 to 100)
val result = rdd.map(_ + 10)
.filter(_ > 15)
.map(x => (x, 1))
.reduceByKey(_+_)
.collect
并且开发者可以根据需要自己编写相应的RDD以及RDD之间的操作,非常方便。可以这么理解,RDD就相当于抽象的数据表示,而operation就相当于一套DSL用于对RDD进行变换或者求值。
RDD的表示
Spark中的RDD主要包含五部分信息:
- partitions(): partition集合
- dependencies(): 当前RDD的dependency集合
- iterator(split, context): 对每个partition进行计算或读取操作的函数
- partitioner(): 分区方式,如HashPartitioner和RangePartitioner
- preferredLocations(split): 访问某个partition最快的节点
所有的RDD都继承抽象类RDD。几种常见的操作:
- sc#textFile: 生成HadoopRDD,代表可以从HDFS中读取数据的RDD
- sc#parallelize: 生成ParallelCollectionRDD,代表从Scala集合中生成的RDD
- map, flatMap, filter: 生成MapPartitionsRDD,其partition与parent RDD一致,同时会对parent RDD中iterator函数返回的数据进行对应的操作(lazy)
- union: 生成UnionRDD或PartitionerAwareUnionRDD
- reduceByKey, groupByKey: 生成ShuffledRDD,需要进行shuffle操作
- cogroup, join: 生成CoGroupedRDD
Operations
Spark里面对RDD的操作分为两种:transformation 和 action。
- transformation是lazy的,仅仅会保存计算步骤并返回一个新的RDD,而不会立刻执行计算操作
- action会依次执行计算操作并且得到结果
这些tran