Spark上开发的应用程序都是由一个driver programe构成,这个所谓的驱动程序在Spark集群通过跑main函数来执行各种并行操作。集群上的所有节点进行并行计算需要共同访问一个分区元素的集合,这就是RDD(RDD resilient distributed dataset)弹性分布式数据集。RDD可以存储在内存或磁盘中,具有一定的容错性,可以在节点宕机重启后恢复。
在Spark 中,对数据的所有操作不外乎创建RDD、转化已有RDD 以及调RDD 操作进行求值。而在这一切背后,Spark 会自动将RDD 中的数据分发到集群上,并将操作并行化执行。
创建RDD有两种方式:一种是通过并行化驱动程序中的已有集合创建,另外一种方法是读取外部数据集。
一种非常简单的创建RDD的方式,将程序中的一个集合传给SparkContext
的parallelize()
方法。
python中的操作(pyspark打开shell):
>>> data = [1, 2, 3, 4, 5]
>>> distData = sc.parallelize(data)
# 对RDD进行测试操作
# 对集合中的所有元素进行相加,返回结果为15
>>> distData.reduce(lambda a, b: a + b)
15
scala中的操作(spark-shell打开shell):
scala> val data = Array(1,2,3,4,5)
data: Array[Int] = Array(1, 2, 3, 4, 5)
scala> val distData = sc.parallelize(data)
distData: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :23
scala> distData.reduce((a,b)=>a+b)
res0: Int = 15
Spark可以从任何Hadoop支持的存储上创建RDD,比如本地的文件系统,HDFS,Cassandra等。Spark可以支持文本文件,SequenceFiles等。
这种方法更为常用。
python:
# 从protocols文件创建RDD
distFile = sc.textFile("/etc/protocols")
scala:
// 从protocols文件创建RDD
val distFile = sc.textFile("/etc/protocols")
RDD支持两种操作:
转换操作是懒惰的,举个例子:
>>> lines = sc.textFile("README.md")
>>> pythonLines = lines.filter(lambda line: "Python" in line)
>>>> pythonLines.first()
如果Spark 在我们运行lines = sc.textFile(…) 时就把文件中所有的行都读取并存储起来,就会消耗很多存储空间,而我们马上就要筛选掉其中的很多数据。相反, 一旦Spark 了解了完整的转化操作链之后,它就可以只计算求结果时真正需要的数据。事实上,在行动操作first() 中,Spark 只需要扫描文件直到找到第一个匹配的行为止,而不需要读取整个文件。
下面引用《Spark快速大数据分析》的一段话:
我们不应该把RDD 看作存 放着特定数据的数据集,而最好把每个RDD 当作我们通过转化操作构建出来的、记录如 何计算数据的指令列表。把数据读取到RDD 的操作也同样是惰性的。因此,当我们调用 sc.textFile() 时,数据并没有读取进来,而是在必要时才会读取。和转化操作一样的是, 读取数据的操作也有可能会多次执行。
简单的例子理解转换和行动操作:
python:
# 从protocols文件创建RDD
distFile = sc.textFile("/etc/protocols")
# Map操作,每行的长度,转换操作
lineLengths = distFile.map(lambda s: len(s))
# Reduce操作,获得所有行长度的和,即文件总长度,这里才会执行map运算
totalLength = lineLengths.reduce(lambda a, b: a + b)
# 可以将转换后的RDD保存到集群内存中
lineLengths.persist()
scala:
// 从protocols文件创建RDD
val distFile = sc.textFile("/etc/protocols")
// Map操作,每行的长度
val lineLengths = distFile.map(s => s.length)
// Reduce操作,获得所有行长度的和,即文件总长度,这里才会执行map运算
val totalLength = lineLengths.reduce((a, b) => a + b)
// 可以将转换后的RDD保存到集群内存中
lineLengths.persist()
如下图所示:
# 定义需要传递给Spark的函数
def myFunc(s):
words = s.split(" ")
return len(words)
# 将词数统计函数传递给Spark
sc.textFile("/etc/protocols").map(myFunc).reduce(lambda a,b:a+b)
上述中定义的函数为计算一行字符串中单词的个数,
sc.textFile("/etc/protocols").map(myFunc).reduce(lambda a,b:a+b)
表明先读取内容转出RDD,然后转换map()是对每一行都进行统计操作,然后进行行动操作,用lambda匿名函数计算所有行的单词和。
//创建一个单例对象MyFunctions
object MyFunctions {
def func1(s: String): Int = {s.split(" ").length}
}
val lineLengths = sc.textFile("/etc/protocols").map(MyFunctions.func1)
val count = lineLengths.reduce((a,b)=>a+b)
与python几乎一样,注意其匿名函数的写法。
参考资料:
1. 《spark快速大数据分析》
2. https://www.shiyanlou.com/courses/456/labs/1462/document