一、什么是RDD
什么是RDD?RDD是只读的,分区记录的集合。RDD只能基于稳定物理存储中的数据集合以及其他已有的RDD上执行确定性操作来创建。这些确定性操作被称为转换(transformation),例如map、filter、groupBy、join。因此RDD并不需要物化。并且也因为这个机制使得RDD含有如何从其他RDD衍生出本RDD的相关信息(即Lineage:血统),因此在RDD部分数据分区丢失的时候可以从物理存储的数据计算出相应的RDD分区--------摘自《spark技术内幕》
上面这段话摘自《spark技术内幕》,我认为描述的的非常简洁和准确。对此,我们有两个基本问题需要解决,RDD怎么创建?转换是什么意思?
二、RDD的创建
《spark技术内幕》中在说明创建时没有具体的样例代码,均是文字性描述,为了便于理解,这里选录了《hadoop权威指南》中有关创建RDD的部分,原文描述如下:
RDD的创建方式有三种
- 来自内存中的一个对象集合(也称并行化一个集合)
- 使用外部存储器(例如hdfs)中的数据集
- 对现有的RDD进行转换
第一种方法适用于对少量的输入数据进行并行的CPU密集型计算,例如下面这段代码对数字1到10运行独立运算
var params=sc.parallelize(1 to 10) val result=params.map(performExpensiveComputation)
performExpensiveComputation函数对输入的值并行运算,其并行度由spark.default.parallelism属性决定,默认值取决于spark作业的地点,本地运行时默认值就是内核数(core),而在集群上运行时,它是集群中executor节点的内核的总数。
可以为某个特定运算设置并行性级别,指定parallelize()方法的第二个参数即可:
sc.parallelize(1 to 10, 10)
创建RDD的第二种方式,是创建一个指向外部数据集的引用。例如以一个文本文件创建String对象的RDD:
val text:RDD[String] = sc.textFile(inputPath)
路径inputPath可以是任意的Hadoop文件系统路径,比如本地文件系统或HDFS上的一个文件。由于Spark使用了旧的MapReduce API中的TextInputFormat来读取这个文件。这就意味着文件切分行为与Hadoop是一样的,因此在HDFS的情况下,一个Spark分区对应一个HDFS块(block)。这个默认行为可以改变,传入第二个参数来请求一个特殊的切分数量:
sc.textFile(inputPath, 10)
另外一个方法允许把多个文本文件作为一个整体来处理,返回的RDD中,是成对的string,第一个string是文件的路径,第二个string是文件的内容。因为每个文件都会加载进内存,所以这种方式仅仅适合于小文件:
val files:RDD[(String, String)] = sc.wholeTextFiles(inputPath)
Spark能够处理文本文件以外的其他文件格式,比如,序列文件可以这样读入:
sc.sequenceFile[IntWritable, Text](inputPath)
注意这里指定序列文件的键和值的Writable类型的方式。对于常用的Writable类型,Spark能够映射到Java中对应的基本类型,因此我们可以使用等价的方式:
sc.sequenceFile[Int, String](inputPath)
从任意的Hadoop InputFormat来创建RDD,有两种方式:基于文件的格式,使用hadoopFile(),接收一个路径;其他格式,比如HBase的TableInputFormat,使用hadoopRDD()。这些方法使用的是旧的MapReduce API。如果要用新的MapReduce API,使用newAPIHadoopFile()和newAPIHadoopRDD()。下面是读取Avro数据文件的示例,使用特定的API和一个WeatherRecord类:
val job = new Job() AvroJob.setInputKeySchema(job, WeatherRecord.getClassSchema) val data = sc.newAPIHadoopFile(inputPath, classOf[AvroKeyInputFormat[WeatherRecord]], classOf[AvroKey[WeatherRecord]], classOf[NullWritable], job.getConfiguration)
除了路径之外,newAPIHadoopFile()方法还需要InputFormat的类型、键的类型、值的类型,再加上Hadoop配置,该配置中带有Avro的模式 schema,在第二行我们使用AvroJob帮助类做了设置。
创建RDD的第三种方式,是转换已存在的RDD。
三、RDD的转换
RDD中的所有转换都是惰性的,也就是说,他们并不会直接计算结果,相反的,他们只记住这些应用到基础数据集是哪个的转换动作。只有发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这个设计的巧妙之处在于我们不必要每个操作都在数据集上计算(往往数据集都很大),而是将某些操作延迟,在必要的时候(即发生动作时)再执行。这极大提高了spark的效率,也从侧面反映了RDD是一个逻辑集中的实体,也可以理解为是一种分布式的内存抽象。
RDD支持的转换如下所示
转换 | 含义 |
map(func) | 返回一个新的数据集,该数据集由每一个输入元素经过func函数转换而成 |
filter(func) | 返回一个新的数据集,该数据集由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但每一个输入元素可以被映射成为一个或多个输出元素 |
mapPartitions(func) | 类似于map,但独立的在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数必须是Iterator[T]=>Iterator[U] |
mapPartitionsWithSplit(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是( Int, Iterator[T] )=>Iterator[U] |
sample(withReplacement,seed) | 根据fraction指定的比例对数据进行采样,可以选择是否用随机种子进行替换,seed用于指定随机种子 |
union(otherDataset) | 返回一个数据集,新数据集由原数据集和参数数据集联合而成 |
distinct([num Tasks]) | 返回一个原数据集中所有不重复元素的新数据集 |
groupByKey([num Tasks]) | 在一个(K,V)对的数据集上调用,返回一个(K,Seq[V])对的数据集 注意默认情况下,只有8个并行任务来做操作,但是可以传入一个可选的numTasks参数来改变它 |
reduceByKey([num Tasks]) | 在一个(K,V)对的数据集上调用,返回一个(K,V)对的数据集。使用指定的reduce函数,将相同的key值聚合在一起,与groupByKey类似。reduce的任务可以传入一个可选的numTasks参数来改变它 |
sortByKey([ascending],[num Tasks]) | 在一个(K,V)对的数据集上调用,K必须实现Ordered接口,返回一个按照Key进行排序的(K,V)对数据集,升序或者降序由ascending决定 |
join(otherDataset,[num Tasks]) | 在类型为(K,V)和(K,W)类型的数据集上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))数据集 |
cogroup(otherDataset,[num Tasks]) | 在类型为(K,V)和(K,W)类型的数据集上调用,返回一个(K,Seq(V),Seq(W))元组的数据集,这个操作也被成为groupwith |
cartesian(otherDataset) | 笛卡尔积,在类型为T和U的数据集上调用,返回一个(T,U)对数据集(两两的元素对) |
四、RDD的动作
RDD支持的动作如下表所示
动作 | 含义 |
reduce(func) | 通过函数func(接受两个参数,返回一个参数)聚集数据集中所有元素,这个功能必须是可交换的且可关联的,从而可以正确并行执行 |
collect() | 在驱动程序中,以数组形式返回数据集所有元素,通常在使用filter或者其他操作返回一个足够小的数据子集时使用比较有用 |
count() | 返回数据集的元素个数 |
first() | 返回数据集的第一个元素(类似于take(1)) |
take() | 返回一个由数据集前n个元素组成的数组 注意,这个操作目前并非并行执行,而是由驱动程序计算所有的元素 |
takeSample(withReplacement, num,seed) | 返回一个数组,该数组由从数据集中随机采样的n个元素组成,可以选择是否用随机数替换不足的部分,seed指定随机种子 |
saveAsTextFile(path) | 将数据集的元素以TextFile的形式保存到本地文件系统----HDFS或者其他任何Hadoop支持的文件系统,对于每个元素,spark会调用toString方法,将它替换为文件中的文本行 |
saveAsSequenceFile(path) | 将数据集的元素以Hadoop sequencefile的格式保存到指定的目录下,可以是本地文件系统或者其他任何Hadoop支持的文件系统,这个仅限于由(K,V)对组成,并实现了Hadoop的Writable接口,或者可以隐式的转换为Writable的RDD(spark包含了基本类型的转换,例如Int,Double,String等) |
countByKey() | 对(K,V)类型的RDD有效,返回一个(K,Int)对的map,表示每一个key对应的元素个数 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新,通常用于边缘效果,例如更新一个累加器,或者和外部存储系统进行交互,例如Habse |