摘要
spark的RDD具有延迟计算的特性,spark有两种操作算子,一种是transformation,一种是action。tranformation是来创建RDD的,它有可能从文件创建,也可能集合中创建,也可能依赖其它RDD创建。当算子为transformation的时候,spark并不执行计算操作,只有当遇到action算子的时候才开始计算。这就是transformation的lazy特性。
一、transformation函数概览
1、map (func)
--对调用map的RDD数据集中的每个element都使用func,然后返回一个新的分布式数据集RDD。
val linesRDD = sc.textfile("") //构建初始RDD
val mapRDD = linesRDD.map(x => (x,1)) // map操作
2、filter(func)
--对调用filter的RDD数据集中的每个element都使用func,然后返回一个包含使func为true的元素构成的RDD。
val numList = Array(1,2,3,4,5)
val numRDD = sc.parallelize(numList)
val filterRDD = numRDD.filter(x => x>3)
3、flatMap(func)
--和map算子差不多,但是flatMap生成的是多个结果。
val linesRDD = sc.textfile("") //构建初始RDD
linesRDD.flatMap(x => x.split(","))
4、mapPartitions(func)
--和map算子差不多,但是map针对的是每个element,而mapPartitions针对的是每个partition(block)。例如,有一个数据集,有100个partiton,每个partition有10000条数据。我们要将这100w的数据保存到数据库中。如果我们用map实现,则需要链接数据库100w次,而如果用mapPartitions,则只需要链接100次。大大减少了数据库的压力。但是使用mapPartitions要考虑到内存的问题,因为它要把一个partition的数据处理完后才会释放资源,如果内存不足可能会报OOM。
5、mapPartitionsWithSplit(func)
--和mapPartitions差不多,但是func是作用在其中的一个split上,所以func中应该有index。
6、sample(withReplacement, fraction, seed)
--抽样
7、union(otherDataset)
--返回一个新的dataset,包含源dataset和新给的dataset元素的集合。
8、distinct([numPartitions]))
--返回一个新的dataset。包含源dataset里面的element经过distinct的值。
9、groupByKey([numPartitions])
--当作用到一个(k,v)的dataset上时,返回的是一个(k,Iterable)的dataset。
10、reduceByKey(func, [numPartitions])
--就是用一个给定的reduce func作用在groupByKey产生的(k,Iterable)的dataset之上。一般用作聚合。
注意(9,10):groupByKey和reduceByKey的区别。
val conf = new SparkConf().setAppName("GroupAndReduce").setMaster("local")
val sc = new SparkContext(conf)
val words = Array("one", "two", "two", "three", "three", "three")
val wordsRDD = sc.parallelize(words).map(word => (word, 1))
val wordsCountWithReduce = wordsRDD.
reduceByKey(_ + _).
collect().
foreach(println)
val wordsCountWithGroup = wordsRDD.
groupByKey().
map(w => (w._1, w._2.sum)).
collect().
foreach(println)
两者都可以得到结果。但是reduceByKey更适合用在大数据集上,因为在将相同的key拉取到一个节点上进行聚合的时候,它会先做一个类似于mapreduce代码里面的combiner,先将同一个节点上的相同的key的数据进行聚合,然后在进行网络传输到最终节点。而groupByKey就没有这个动作,它会将所有相同key的数据走网络传输拉取到最终节点。这样就造成了网络与磁盘IO的压力增大。且可能造成节点OOM。
11、intersection(otherDataset)
--返回两个dataset的一个交集dataset。
12、sortByKey([ascending], [numPartitions])
--按照Key进行排序,ascending的值默认为True,True/False表示升序/降序 。
13、join(otherDataset, [numPartitions])
--类似于SQL中的连接操作,即作用于键值对(K, V)和(K, W)上,返回元组 (K, (V, W))。
14、cogroup(otherDataset, [numPartitions])
--作用于键值对(K, V)和(K, W)上,返回元组 (K, (Iterable, Iterable))。这一操作可叫做groupWith。
15、cartesian(otherDataset)
--笛卡尔乘积,作用于数据集T和U上,返回(T, U),即数据集中每个元素的两两组合
16、coalesce(numPartitions)
--将RDD的分区数减小到numPartitions个。当数据集通过过滤规模减小时,使用这个操作可以提升性能。一般作用在filter之后例如一个RDD有100个partition,意味着有100个task。但是经过一系列的filter过滤之后,可能每个partition里面包含的数据量已经很少了,这时再启动100个task会产生很多的小文件。这个时候就可以使用coalesce减少partition的数量。
17、repartition(numPartitions)
--重组数据,数据被重新随机分区为numPartitions个,numPartitions可以比原来大,也可以比原来小,平衡各个分区。这一操作会将整个数据集在网络中重新洗牌。
注意(16,17):repartition算子底层调用的是coalesce,coalesce有个默认参数shuffle: Boolean = false,默认是不走shuffle操作的,repartition算子调用coalesce的时候–coalesce(numPartitions, shuffle = true)传入的是true,走shuffle,可以增大partition的数量。
二、action函数概览
1、reduce(func)
--使用函数func(两个输入参数,返回一个值)对数据集中的元素做聚集操作
val numList = Array(1,2,3,4,5)
val numRDD = sc.parallelize(numList)
numRDD.reduce(_+_)
2、collect()
--在driver程序中以数组形式返回数据集中所有的元素。这以action通常在执行过filter或者其他操作后返回一个较小的子数据集时非常有用。
3、count()
--返回数据集中元素的个数。
4、first()
--返回数据集中的第一个元素,底层调用的是take(1)
def first(): T = withScope {
take(1) match {
case Array(t) => t
case _ => throw new UnsupportedOperationException("empty collection")
}
}
5、take(n)
--以数组形式返回数据集中前n个元素。需要注意的是,这一action并不是在多个node上并行执行,而是在driver程序所在的机器上单机执行,会增大内存的压力,使用需谨慎。
6、takeSample(withReplacement, num, [seed])
--以数组形式返回从数据集中抽取的样本数量为num的随机样本,有替换或者无替换的进行采样。可选参数[seed]可以允许用户自己预定义随机数生成器的种子。注意:该方法仅在预期结果数组很小的情况下使用,因为所有数据都被加载到driver的内存中。
(1) 当不可以多次抽样:withReplacement=false;样本个数num大于父本个数时,只能返回父本个数
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
List<Integer> res = rdd.takeSample(false, 8);
System.out.println(res);
结果:[6, 8, 2, 4]
(2)当不可以多次抽样:withReplacement=false;样本个数num小于父本个数时,返回样本个数
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
List<Integer> res = rdd.takeSample(false, 3);
System.out.println(res);
结果:[8, 4, 2]
(3)当可以多次抽样:withReplacement=true;样本个数num大于父本个数时,返回样本个数
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
List<Integer> res = rdd.takeSample(true, 8);
System.out.println(res);
结果:[4, 6, 8, 2, 6, 2, 4, 2]
(4)当可以多次抽样:withReplacement=true;样本个数num小于父本个数时,返回样本个数
JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
List<Integer> res = rdd.takeSample(true, 3);
System.out.println(res);
结果:[8, 8, 2]
7、takeOrdered(n, [ordering])
--返回RDD的前n个元素,可以利用自然顺序或者由用户执行排序的comparator。
8、saveAsTextFile(path)
--将数据集中的元素以文本文件(或者文本文件的一个集合)的形式写入本地文件系统,或者HDFS,或者其他Hadoop支持的文件系统的指定路径path下。Spark会调用每个元素的toString方法,将其转换为文本文件中的一行。
9、saveAsSequenceFile(path)
--将数据集中的元素以Hadoop SequenceFile的形式写入本地文件系统,或者HDFS,或者其他Hadoop支持的文件系统的指定路径path下。RDD的元素必须由实现了Hadoop的Writable接口的key-value键值对组成。在Scala中,也可以是隐式可以转换为Writable的键值对(Spark包括了基本类型的转换,例如Int,Double,String等等)
10、saveAsObjectFile(path)
--利用Java序列化,将数据集中的元素以一种简单的形式进行写操作,并能够利用SparkContext.objectFile()加载数据。(适用于Java和Scala)
11、countByKey()
--只能作用于键值对(K, V)形式的RDDs上。按照Key进行计数,返回键值对(K, int)的哈希表。
12、foreach(func)
--在数据集的每个元素上调用函数func。这一操作通常是为了实现一些副作用,比如更新累加器或者与外部存储系统进行交互。注意:在foreach()之外修改除了累加器以外的变量可能造成一些未定义的行为。更多内容请参阅闭包进行理解。