RDD创建
RDD可以通过两种方式创建:
第一种:读取一个外部数据集,从本地加载数据集或者从HDFS文件系统,HBASE,Cassandra,AmazonS3等外部数据源中加载数据集。
第二种:调用SparkContext的parmallelize方法,在Driver中一个已经存在的集合(数组)上创建。
从文件系统中加载数据创建RDD
Spark采用textfile()方法从文件系统中加载数据创建RDD,该方法把文件的URL作为参数,这个URL可以是本地文件系统的地址,或者是分布式文件系统HDFS的地址,或者是Amazon S3的地址等等。
scala> val lines = sc.textFile("file:///usr/local/spark/mycode/rdd/word.txt")
lines: org.apache.spark.rdd.RDD[String] = file:///usr/local/spark/mycode/rdd/word.txt MapPartitionsRDD[12] at textFile at <console>:27
lines是一个String类型的RDD,或者我们以后可以简单称为RDD[String],也就是说,这个RDD[String]里面的元素都是String类型。
在使用Spark读取文件时,需要说明以下几点:
(1)如果使用了本地文件系统的路径,那么,必须要保证在所有的worker节点上,也都能够采用相同的路径访问到该文件,比如,可以把该文件拷贝到每个worker节点上,或者也可以使用网络挂载共享文件系统。
(2)textFile()方法的输入参数,可以是文件名,也可以是目录,也可以是压缩文件等。比如,textFile(“/my/directory”), textFile(“/my/directory/.txt”), and textFile(“/my/directory/.gz”).
(3)textFile()方法也可以接受第2个输入参数(可选),用来指定分区的数目。默认情况下,Spark会为HDFS的每个block创建一个分区(HDFS中每个block默认是128MB)。你也可以提供一个比block数量更大的值作为分区数目,但是,你不能提供一个小于block数量的值作为分区数目。
通过并行集合(数组)创建RDD
可以调用SparkContext的parallelize方法,在Driver中一个已经存在的集合(数组)上创建。
scala>val array = Array(1,2,3,4,5)
array: Array[Int] = Array(1, 2, 3, 4, 5)
scala>val rdd = sc.parallelize(array)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[13] at parallelize at <console>:29
从执行结果信息可以看出,rdd是一个Int类型的RDD
RDD操作
RDD被创建好以后,在后续使用过程中一般会发生两种操作:
- 转换(Transformation): 基于现有的数据集创建一个新的数据集。
- 行动(Action):在数据集上进行运算,返回计算值。
转换操作
对于RDD而言,每一次转换操作都会产生不同的RDD,供给下一个“转换”使用。转换得到的RDD是惰性求值的,也就是说,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会发生真正的计算,开始从血缘关系源头开始,进行物理的转换操作。
- filter(func):筛选出满足函数func的元素,并返回一个新的数据集
- map(func):将每个元素传递到函数func中,并将结果返回为一个新的数据集
- flatMap(func):与map()相似,但是每个输入元素都可以映射到0或者多个输出结果。
- groupByKey():应用于(K,V)键值对的数据集时,返回一个新的(K,iterable)形式的数据集
- reduceByKey(func):应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中的每个值是将每个key传递到函数func中进行聚合
行动操作
行动操作是真正触发计算的地方,Spark程序执行到行动操作时,才会执行真正的计算,从文件加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果。
下面列出一些常见的行动操作(Action API):
- count() 返回数据集中的元素个数
- collect() 以数组的形式返回数据集中的所有元素
- first() 返回数据集中的第一个元素
- take(n) 以数组的形式返回数据集中的前n个元素
- reduce(func) 通过函数func(输入两个参数并返回一个值)聚合数据集中的元素
- foreach(func) 将数据集中的每个元素传递到函数func中运行*
惰性机制
scala> val lines = sc.textFile("file:///usr/local/spark/mycode/rdd/word.txt")
lines: org.apache.spark.rdd.RDD[String] = file:///usr/local/spark/mycode/rdd/word.txt MapPartitionsRDD[16] at textFile at <console>:27
scala> lines.filter(line => line.contains("Spark")).count()
res1: Long = 2 //这是执行返回的结果
lines就是一个RDD,lines filter()会遍历lines中的每行文本,并对每行文本执行括号中的匿名函数,也就是执行Lambda表达式:line=> line.contains(“spark”) 在执行Lambda表达式时,会把当前遍历到的这行文办内容复制给参数line,然后,执行处理逻辑line.contains(“spark”)。也就是只有当改行文本包含Spark才满足条件,才会被放入结果集中,最后,等到lines集合遍历结束后,就会得到一个结果集,这个结果集包含了所有包含“spark”的行,最后,对这个结果调用count(),这是个行动操作,会计算得出结果集中元素个数。
scala> val lines = sc.textFile("file:///usr/local/spark/mycode/rdd/word.txt")
scala> lines.map(line => line.split(" ").size).reduce((a,b) => if (a>b) a else b)
上面代码中,lines是一个RDD,是String类型的RDD,因为这个RDD里面包含了很多行文本。lines.map(),是一个转换操作,之前说过,map(func):将每个元素传递到函数func中,并将结果返回为一个新的数据集,所以,lines.map(line => line.split(” “).size)会把每行文本都传递给匿名函数,也就是传递给Lamda表达式line => line.split(” “).size中的line,然后执行处理逻辑line.split(” “).size。line.split(” “).size这个处理逻辑的功能是,对line文本内容进行单词切分,得到很多个单词构成的集合,然后,计算出这个集合中的单词的个数。因此,最终lines.map(line => line.split(” “).size)转换操作得到的RDD,是一个整型RDD,里面每个元素都是整数值(也就是单词的个数)。最后,针对这个RDD[Int],调用reduce()行动操作,完成计算。reduce()操作每次接收两个参数,取出较大者留下,然后再继续比较,例如,RDD[Int]中包含了1,2,3,4,5,那么,执行reduce操作时,首先取出1和2,把a赋值为1,把b赋值为2,然后,执行大小判断,保留2。下一次,让保留下来的2赋值给a,再从RDD[Int]中取出下一个元素3,把3赋值给b,然后,对a和b执行大小判断,保留较大者3.依此类推。最终,reduce()操作会得到最大值是5。