首先需要明确的是,spark的作者Matei Zaharia提出的应该是一个名为弹性分布式数据集的概念,即为RDD。而所谓的spark,是一个基于scala的框架,是RDD抽象概念的实现。有了这个前提,我们可以开始正式地学习RDD和spark。


一、 RDD概述:



从形式上看,RDD是一个可分区的只读记录集合,创建RDD的方法只有两种:1、通过从稳定的存储器,比如硬盘上读取。2、从其他的RDD上进行相应操作得到。这两种方法统称为transformation,包括map、filter这些都是transformation,以区别于马上要提到的action操作。



Action操作,不同于trans,它的目的是返回一个值或者把数据导入存储系统,如count、save这些操作。



而在spark中,对于RDD的操作是惰性的,即对于一个给定的RDD,之前进行多个trans操作都不会有动静,只有当RDD上执行了一个action时,spark才开始从第一个trans起逐个执行RDD的计算工作,注意,每当执行一个trans操作,相当于得到一个新的RDD。



这样做的好处在于,如下面的程序,目的是读取日志中的错误信息:



Lines=spark.textFile(“hdfs://…”) 
   Errors=lines.filter(_.startsWith(“ERROR”))



这样一来,日志中所有以ERROR开头的行信息,都被存入error变量中,若之后再在error上进行一个action,spark就会在内存中存储error对应的分区,与此同时,第一行的lines并不会被载入内存,因为它只有一个trans操作,没有对应的action。这样的好处在于节省开销,错误信息可能只是日志中的一小部分,而日志本身太大无法加载入内存。


二、 Spark编程接口:



正如上文提到的,spark是RDD思想结合scala语言的一个实现。下面以pagerank算法为例,介绍如何在spark上控制RDD。该算法主要流程是,在每一次迭代中,每个文档发送一个贡献值r/n到其邻近结点,其中r表示该文档的rank,n为其邻居节点数。然后文档更新其rank值为:α/N + (1 —α) Σci,这里的求和是对所有接收到的贡献值求和,N 表示总的文档数,α是一个平滑参数,ci是累加所有邻点发送过来的贡献值。下面逐行分析:

// 以(URL, outlinks) 的形式把网页文件加载为RDD,这里使用了persist函数,表示该RDD会在后续操作中用到,spark会把它保存在内存中。
val links = spark.textFile(...).map(...).persist()

// 类似上一行,这里以(URL, rank) 的形式加载网页文件
var ranks = 。。。

// 令i从1到自己设定的迭代次数,即循环。
for (i <-1 to ITERATIONS) {

//首先links和ranks两个RDD执行JOIN操作,得到一个(URL,(outlinks, ranks))形式的RDD。然后对该RDD执行flatMap的操作,该操作还内嵌了links的map操作,最终得到的是(目标URL,贡献值(r/n))这种形式的RDD。
val contribs = links.join(ranks).flatMap {
case (url, (links, rank)) =>
links.map(dest => (dest, rank/links.size))
}

// 首先用reduceByKey把相同目标URL的rank值合并,该操作的功能是,如(a,x),(a,y)这样的记录,使用(x,y)=>x+y这样的reduceByKey操作就把它俩合并成(a,x+y)。而mapValues则是针对key-value类型的value进行操作,不会对key进行操作。在本例中,即是把合并后的目标URL的rank值进行如公式中的操作变化。
ranks = contribs.reduceByKey((x,y) => x+y).mapValues(sum => a/N + (1-a)*sum)
}