第二篇介绍一下Spark的基本数据抽象——RDD,RDD来自伯克利的一篇论文,也就是http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf
下面就选取一些主要的特性进行介绍:
一、什么是RDD
RDD(Resilient Distributed Datasets)即弹性分布式数据集,RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,只能通过在其他RDD执行确定的转换操作(如map、join和group by)而创建。它具备像MapReduce等数据流模型的容错特性,并且允许开发人员在大型集群上执行基于内存的计算。尽管如此,RDD仍然足以表示很多类型的计算,包括MapReduce和专用的迭代编程模型(如Pregel)等。
二、RDD的高效之处
在论文中,RDD取得了优异的性能指标。实验表明,在处理迭代式应用上Spark比Hadoop快高达20多倍,计算数据分析类报表的性能提高了40多倍,同时能够在5-7秒的延时内交互式扫描1TB数据集。那么为什么RDD可以如此高效呢?主要得益于以下几点:
1、RDD是只读的,而且每个RDD都保留了父子血缘关系,并且在容错时根据关系进行重新计算,避免了数据冗余。
2、RDD的操作全部在内存中进行,避免了效率较低的磁盘I/O操作。
3、RDD中存放的数据可以是java对象,所以避免的不必要的对象序列化和反序列化。
三、RDD的基本操作
RDD的基本操作如下表所示:
RDD提供了两种类型的操作:transformation是得到一个新的RDD,方式很多,比如从数据源生成一个新的RDD,从RDD生成一个新的RDD。action是得到一个值,或者一个结果(直接将RDD cache到内存中)
所有的transformation都是采用的懒策略,就是如果只是将transformation提交是不会执行计算的,计算只有在action被提交的时候才被触发。下面介绍一下RDD的常见操作:(注意是dataset还是RDD)
transformation操作:
1. map(func):对调用map的RDD数据集中的每个element都使用func,然后返回一个新的RDD,这个返回的数据集是分布式的数据集
2. filter(func) :
对调用filter的RDD数据集中的每个元素都使用func,然后返回一个包含使func为true的元素构成的RDD
3. flatMap(func):和map差不多,但是flatMap生成的是多个结果
4. mapPartitions(func):和map很像,但是map是每个element,而
mapPartitions是每个partition
5. mapPartitionsWithSplit(func):和mapPartitions很像,但是func作用的是其中一个split上,所以func中应该有index
6. sample(withReplacement,faction,seed):抽样
7. union(otherDataset):返回一个新的dataset,包含源dataset和给定dataset的元素的集合
8. distinct([numTasks]):返回一个新的dataset,这个dataset含有的是源dataset中的distinct的element
9. groupByKey(numTasks):返回(K,Seq[V]),也就是hadoop中reduce函数接受的key-valuelist
10. reduceByKey(func,[numTasks]):就是用一个给定的reduce
func再作用在groupByKey产生的(K,Seq[V]),比如求和,求平均数
11. sortByKey([ascending],[numTasks]):按照key来进行排序,是升序还是降序,ascending是boolean类型
12. join(otherDataset,[numTasks]):当有两个KV的dataset(K,V)和(K,W),返回的是(K,(V,W))的dataset,numTasks为并发的任务数
13. cogroup(otherDataset,[numTasks]):当有两个KV的dataset(K,V)和(K,W),返回的是(K,Seq[V],Seq[W])的dataset,numTasks为并发的任务数
14. cartesian(otherDataset):笛卡尔积就是m*n,大家懂的
action操作:
1. reduce(func):说白了就是聚集,但是传入的函数是两个参数输入返回一个值,这个函数必须是满足交换律和结合律的
2. collect():一般在filter或者足够小的结果的时候,再用collect封装返回一个数组
3. count():返回的是dataset中的element的个数
4. first():返回的是dataset中的第一个元素
5. take(n):返回前n个elements,这个是driver program返回的
6. takeSample(withReplacement,num,seed):抽样返回一个dataset中的num个元素,随机种子seed
7. saveAsTextFile(path):把dataset写到一个text
file中,或者hdfs,或者hdfs支持的文件系统中,spark把每条记录都转换为一行记录,然后写到file中
8. saveAsSequenceFile(path):只能用在key-value对上,然后生成SequenceFile写到本地或者hadoop文件系统
9. countByKey():返回的是key对应的个数的一个map,作用于一个RDD
10. foreach(func):对dataset中的每个元素都使用func
4、RDD样例
论文中给了一个控制台错误日志查找的例子
假定有一个大型网站出错,管理员想检查Hadoop文件系统(HDFS)中的日志文件(TB级大小)来找出原因。通过使用Spark,操作员只需将日志中的错误信息装载到一组节点的内存中,然后执行交互式查询。首先,需要在Spark解释器中输入如下Scala命令:
lines = spark.textFile("hdfs://...")
errors = lines.filter(_.startsWith("ERROR"))
errors.cache()
第1行从HDFS文件定义了一个RDD(即一个文本行集合),第2行获得一个过滤后的RDD,包含的关键字是error,第3行请求将errors缓存。注意在Scala语法中filter的参数是一个闭包。
集群还没有开始执行任何任务。但用户已经可以在RDD上执行相应的动作,例如统计错误消息的数目:
errors.count()
用户还可以在RDD上执行更多的转换操作,并使用转换结果,如:
// 寻找MySQL相关错误:
errors.filter(_.contains("MySQL")).count()
//返回出错时间
//3是制表符分隔
errors.filter(_.contains("HDFS"))
.map(_.split('\t')(3))
.collect()