RDD(resilient distributed dataset,弹性分布式数据集),是一个抽象概念,是可分布式存储和处理的数据的集合。spark中可进行RDD的创建;转化已存在的RDD为一个新的RDD;在RDD上进行分布式处理,并行计算得到结果(value)。
RDD基础:
RDD可分成多个分区,每个分区分布在集群节点上。RDDs可包含python,java,scala对象类型(包含自定义类对象)。有两种创建方式:从外部数据集装载;已有driver program种的一个数据list或者set对象集合。例如之前一篇中lines = sc.textFile("README.md"),用textFile创建(每行作为一个item的RDD)
RDD创建之后有两种操作可进行:transformations(转换,从一个RDD经过特定处理到另一个RDD); actions(在RDD上处理得到一个确定的结果)。
transformations:例如 val pythonLines = lines.filter(line => line.contains("Python"))//从每行作为一个item的RDD中过滤出包含单词Python的行构成的RDD。
actions:在RDD上处理得到一个确定的结果,结果可返回给driver program或者存到外部存储系统(例如HDFS)。例如:pythonLines.first()就是在RDD pythonLines上进行一个action得到结果(RDD的第一个item)。
RDD架构之所以好在:惰性计算。在action阶段才对数据集进行计算。转换阶段并不对数据进行加载存储(相反MapReduce会对每次结果进行存储到disk中),因此较高效。例如lines = sc.textFile(...)后并不进行对每一行进行加载存储,若进行加载存储之后filter阶段还会再一次加载存储。而action first()只需对最终的转换RDD处理得到第一行就可以,无需读取整个文件。
在第一次对RDD进行action之后,spark会存储RDD的内容到内存中(也是分布式存储到集群上)而不是disk中。RDD.persist()操作会把数据持久化到disk中,便于多次对RDD进行action(我理解为:RDD1->RDD2->RDD3,在RDD3上进行action时才进行RDD1到RDD3的转换并做相应action的计算,此时如果再次action,那么会重新从RDD1来一次。如果RDD2进行持久化后,从RDD2开始就可以了),例如: pythonLines.persist pythonLines.count() pythonLines.first()。若不会重复使用RDD则无需进行持久化而浪费存储空间。
因此,每个spark程序或shell会话如下流程:
1.从外部数据中创建RDDs
2.对RDDs进行转换成新的RDDs,例如filter()操作
3.若要对RDDs进行重复使用,则需persist()持久化
4.actions例如count(),first(),启动并行的计算,spark会对计算任务进行优化和执行。
一:把一个已存在的集合传给SparkContext的parallelize()方法得到。例如 val lines = sc.parallelize(List("pandas", "i like pandas"))
二:从外部数据集装载。详细会在第五章讲,但是之前我们已经见识过一种文件中得到RDD的方式(每行作为一个item,数据的inputformat决定):val lines = sc.textFile("/path/to/README.md")
RDD支持transformations(RDDs转为新的RDDs,例如map(),filter()操作)和actions(在集群上启动一个分布式计算得到一个结果返回给driber program或存储起来,例如count(),first()操作)。区分操作是transformations还是actions:返回类型为RDDs为转换,为其他数据类型是actions。
transformations:例如一个logfile inputRDD,我们通过filter进行过滤转换出含有error的errorsRDD,之后我们还可以filter出含有warning的warningsRDD,新的RDD都会存于内存中,同样我们可以依赖于errorsRDD和warningsRDD进行转换操作(badLinesRDD = errorsRDD.union(warningsRDD)),如下图所示,spark会记录此过程:
因此若某个环节的RDD持久化的数据损坏了,还可以进行修复,这也是spark的容错机制。
actions:例如如下
println("Input had " + badLinesRDD.count() + " concerning lines")
println("Here are 10 examples:")
badLinesRDD.take(10).foreach(println)//take收集十个item到driver program(返回结果到driver program)
driver program通常部署在name node,collect() action可以收集整个RDD到driver program,若你的RDD小数据量,且你希望在本地进行处理可进行collect,若大数据不可,你可以通过saveAsTextFile()或saveAsSequenceFile()或者其他formats的存储到系统(HDFS或者S3中)。需要注意的是,每次action都会使RDD重头开始计算,因此可以通过持久化中间结果来避免效率低下。
惰性计算:每次RDD转换并不会立即计算,在遇到action时才计算。RDD可想象成记录怎么转换数据的操作,而不是数据集合,这样会把转换操作集中,减少数据传递。
最常用的transformations为map和filter:
map可理解为MapReduce中的map阶段,map的输入和输出类型可不同,inputRDD为string,map出的RDD可为double等。filter就是过滤出一个子集RDD。flatMap()返回values的迭代器,功能可用来把string分为words:
val lines = sc.parallelize(List("hello world", "hi"))
val words = lines.flatMap(line => line.split(" "))
words.first() // returns "hello"
map和flatmap区别如下图所示:
RDD1中“”中为一个item,操作tokenize是把每个item list化,map的结果是RDD1的一个item编程mappedRDD中一个item(是一个list[“”,“”]),flagmap则把RDD1的每个item得到的list再进行flattening,每个单词为一个item。
RDD还支持一些数学集合操作:
distinct进行去重,union是并集,intersection是交集,subtract差集。
cartesian(other)是求两个RDD item的所有组合。
reduce():val sum = rdd.reduce((x, y) => x + y),类似于MapReduce的reduce概念。
若RDD {1,2,3,3},如下action:
图中很容易理解,不一一解释了。
spark有如下持久化选择:
使用如下(在action之前进行持久化,unpersist()从cache移除):val result = input.map(x => x * x)
result.persist(StorageLevel.DISK_ONLY)
println(result.count())
println(result.collect().mkString(","))
若你试图持久化很多数据,使用LRU策略进行内存管理。