前言
spark系列教程
spark-core—RDD进阶知识(图文详解,基于IDEA开发)
本篇文章操作基于IDEA的本地测试,如果你还不会如何在IDEA中运行spark,请参考这篇文章
先说说这篇文章说了什么,
如果你对spark有所了解,应该知道spark的几大内置模块:
这篇文章就是基于IDEA,详解spark-Core(围绕RDD展开)
即:
- RDD创造操作
- RDD转化操作
- RDD行为操作
目录
RDD入门程序
0. 入门经典程序,统计字符
在项目的父目录下创建in目录,创建word.txt
看不懂?没关系,先把后面看懂了,再回来看这个
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//1.读取文件,将文件内容一行一行读取
var lines:RDD[String] = sc.textFile("in/word.txt");
//2将一行一行数据分解成一个个单词
var words:RDD[String]=lines.flatMap(_.split(" "));
//3.将单词数据进行结构转化,便于统计
var wordToOne=words.map((_,1));
//4.两两聚合
var sum=wordToOne.reduceByKey(_+_)
//5打印
var result=sum.collect();
result.foreach(println);
}
图解:
1. RDD
rdd简介
通俗点来讲,可以将 RDD 理解为一个分布式对象集合,本质上是一个只读的分区记录集合。每个 RDD 可以分成多个分区,每个分区就是一个数据集片段。一个 RDD 的不同分区可以保存到集群中的不同结点上,从而可以在集群中的不同结点上进行并行计算。
RDD 实质上是一种更为通用的迭代并行计算框架,用户可以显示控制计算的中间结果,然后将其自由运用于之后的计算。
在大数据实际应用开发中存在许多迭代算法,如机器学习、图算法等,和交互式数据挖掘工具。这些应用场景的共同之处是在不同计算阶段之间会重用中间结果,即一个阶段的输出结果会作为下一个阶段的输入。
RDD 正是为了满足这种需求而设计的。虽然 MapReduce 具有自动容错、负载平衡和可拓展性的优点,但是其最大的缺点是采用非循环式的数据流模型,使得在迭代计算时要进行大量的磁盘 I/O 操作。
通过使用 RDD,用户不必担心底层数据的分布式特性,只需要将具体的应用逻辑表达为一系列转换处理,就可以实现管道化,从而避免了中间结果的存储,大大降低了数据复制、磁盘 I/O 和数据序列化的开销。
2.RDD- 创建操作
从集合中创建
1)使用parallelize()从集合创建
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8))
2)使用makeRDD()从集合创建
val rdd1 = sc.makeRDD(Array(1,2,3,4,5,6,7,8))
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//1.使用makeRDD创建
var r1=sc.makeRDD(Array(1,2,3,4,5,6));
r1.collect().foreach(println);
//2.使用parallelize创建
var r2=sc.parallelize(Array(1,2,3,4,5,6));
r2.collect().foreach(println);
}
从外部创建
包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等,
如前面写过的----var lines:RDD[String] = sc.textFile(“in/word.txt”);
3. 转换操作–value类型
注:下划线表示自身
若出现x._1,x._2,则x表示key-value键值对
1 map算子
- 作用:返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
- 需求:创建一个1-10数组的RDD,将所有元素*2形成新的RDD
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//创建
var arr=sc.makeRDD(1 to 10);
var map=arr.map(_*2);
//打印
map.collect().foreach(println);
}
2 mapPartitions(func) 算子
- 作用:类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。
- 需求:创建一个RDD,使每个元素*2组成新的RDD
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//创建
var arr=sc.makeRDD(1 to 10);
//
var map=arr.mapPartitions(x=>x.map(_*2));
map.collect().foreach(println);
}
3 mapPartitionsWithIndex(func)
- 作用:类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];
- 需求:创建一个RDD,使每个元素跟所在分区形成一个元组组成一个新的RDD
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//创建
var arr=sc.makeRDD(1 to 5);
//
var map=arr.mapPartitionsWithIndex((index,item)=>(item.map((index,_))));
map.collect().foreach(println);
}
**4. flatMap(func) **
- 作用:类似于map,但是每一个输入元素可以被映射为0或多个输出元素
(所以func应该返回一个序列,而不是单一元素)
- 需求,创建一个二维数组,合并成一维数组
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//创建
var arr=sc.makeRDD(Array(List(1 , 3),List(2 , 4)));
var map=arr.flatMap(x=>x);
map.collect().foreach(println);
}
5 glom案例
- 作用:将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]
- 需求:创建一个4个分区的RDD,并将每个分区的数据放到一个数组
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//创建4个分区
var arr=sc.makeRDD(1 to 16,4);
var map=arr.glom();
map.collect().foreach(arr=>{
println(arr.mkString(","));
});
}
6 groupBy(func)&&filter
字面意思
//groupby
var arr=sc.makeRDD(1 to 10);
var map=arr.groupBy(_%2);
map.collect().foreach(println);
//filter
var arr1=sc.makeRDD(Array("xiaoming","xiaohong","xiaohuang"));
var map1=arr1.filter(x=>(x.contains("ming")));
map1.collect().foreach(println);
7. sample(withReplacement, fraction, seed)
-
作用:随机抽样
-
参数说明
- withReplacement 抽样后是否放回
- fraction 打分标准,大于fraction就抽出,小于就不抽出
- seed 用于指定随机数生成器种子。
-
需求:创建一个RDD(1-10),从中选择放回和不放回抽样
var arr=sc.makeRDD(1 to 10);
var map=arr.sample(false,0.4,1);
map.collect().foreach(println);
8. distinct([numTasks])) 案例
- 作用:对源RDD进行去重后返回一个新的RDD。默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它。
- 需求:创建一个RDD,使用distinct()对其去重。
var arr=sc.makeRDD(Array(1,2,2,3,4,4,5,6,6));
var map=arr.distinct();
map.collect().mkString(",");
9. coalesce(numPartitions) &&repartision
- 作用:缩减/重分配分区数,用于大数据集过滤后,提高小数据集的执行效率。
- 需求:创建一个4个分区的RDD,对其缩减/增分区
var arr=sc.makeRDD(1 to 16,4);
var map=arr.coalesce(3);
var m=map.repartition(4);
println(map.partitions.size);
println(m.partitions.size);
10.sortBy
var arr=sc.makeRDD(1 to 16);
//按照自身大小从小到大
var map=arr.sortBy(x=>x).collect();
//降序
var map1=arr.sortBy(x=>(x,false)).collect();
//取余3后降序
var map2=arr.sortBy(x=>(x%3,false)).collect();
4转化操作–双value类型
1. 集合
union(otherDataset)–计算并集
subtract (otherDataset) --计算差集
intersection(otherDataset)–计算交集
var arr1=sc.makeRDD(1 to 3);
var arr2=sc.makeRDD(2 to 4);
var map=arr1.union(arr2);
map.collect().foreach(println);
println("-----")
var map1=arr1.intersection(arr2);
map1.collect().foreach(println);
2 cartesian(otherDataset)
- 作用:笛卡尔积,即两个集合元素间一 一成对
var arr1=sc.makeRDD(1 to 3);
var arr2=sc.makeRDD(2 to 4);
var map=arr1.cartesian(arr2);
map.collect().foreach(println);
结果:
(1,2)
(1,3)
(1,4)
(2,2)
(2,3)
(2,4)
(3,2)
(3,3)
(3,4)
3 . zip(otherDataset)案例
- 作用:将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。
- 需求:创建两个RDD,并将两个RDD组合到一起形成一个(k,v)RDD
var arr1=sc.makeRDD(Array("a","b","c"));
var arr2=sc.makeRDD(Array(1,2,3));
var map=arr1.zip(arr2);
map.collect().foreach(println);
结果:
(a,1)
(b,2)
(c,3)
5 RDD转化操作–key-value类型
1 partitionBy 案例
- 作用:对pairRDD进行分区操作,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区, 否则会生成ShuffleRDD,即会产生shuffle过程。
- 分区依据:根据传入的分区值,获取原有的hashcode,进行取模运算:
- 需求:创建一个4个分区的RDD,对其重新分区
var arr1=sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c"),(4,"d")),4);
var map=arr1.partitionBy(new org.apache.spark.HashPartitioner(2));
map.collect().foreach(println);
---------------------
结果:
(2,b)
(4,d)
(1,a)
(3,c)
2 groupByKey案例
- 作用:根据相同的值进行分组
- 需求:创建一个pairRDD,将相同key对应值聚合到一个sequence中,并计算相同key对应值的相加结果。
//1.获取k-v型rdd
var words = Array("one", "two", "two", "three", "three", "three");
var rdd=sc.makeRDD(words).map(x=>(x,1));
//2.根据k进行分组
var group=rdd.groupByKey();
//3.将分组的值进行求和
var result=group.map(x=>(x._1,x._2.sum));
result.collect().foreach(println);
----result
(two,2)
(one,1)
(three,3)
3 reduceByKey(func, [numTasks]) 案例
- 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。
- 需求:创建一个pairRDD,计算相同key对应值的相加结果
//1.获取k-v型rdd
var rdd=sc.parallelize(List(("female",1),("male",5),("female",5),("male",2)));
var result=rdd.reduceByKey((x,y)=>(x+y));
result.collect().foreach(println);
------
(male,7)
(female,6)
现在反过头来看经典的统计单词案列,你是否看懂了?
```java
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//1.读取文件,将文件内容一行一行读取
var lines:RDD[String] = sc.textFile("in/word.txt");
//2将一行一行数据分解成一个个单词
var words:RDD[String]=lines.flatMap(_.split(" "));
//3.将单词数据进行结构转化,便于统计
var wordToOne=words.map((_,1));
//4.两两聚合
var sum=wordToOne.reduceByKey(_+_)
//5打印
var result=sum.collect();
result.foreach(println);
}
4 reduceByKey和groupByKey的区别
- reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v].
- groupByKey:按照key进行分组,直接进行shuffle。
- 开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。
5 aggregateByKey案例
参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
- 作用:在kv对的RDD中,,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出。
- 参数描述:
(1)zeroValue:给每一个分区中的每一个key一个初始值;
(2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;
(3)combOp:函数用于合并每个分区中的结果。 - 需求:创建一个pairRDD,取出每个分区相同key对应值的最大值,然后相加
- 需求分析
//1.获取k-v型rdd
val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
//取出每个分区相同key对应值的最大值,然后相加
val result=rdd.aggregateByKey(0)(math.max(_,_),_+_);
result.collect().foreach(println);
------
(b,3)
(a,3)
(c,12)
6. foldByKey案例
参数:(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
1.作用:和reduceByKey类似
7 sortByKey([ascending], [numTasks])
- 作用:在一个(K,V)的RDD上调用,返回一个按照key进行排序的(K,V)的RDD
- ascending为bool,决定升降序
8 mapValues案例
- 针对于(K,V)形式的类型只对V进行操作
- 需求:创建一个pairRDD,并将value添加字符串"|||"
val rdd3 = sc.parallelize(Array((1,"a"),(1,"d"),(2,"b"),(3,"c")));
rdd3.mapValues((value)=>(value+"|||||")).collect().foreach(println);
9 join(otherDataset, [numTasks]) 案例
- 作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
- 需求:创建两个pairRDD,并将key相同的数据聚合到一个元组。
val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")));
val rdd1=sc.parallelize(Array((1,4),(2,5),(3,6)));
rdd.join(rdd1).collect().foreach(println);
----------------------
(1,(a,4))
(3,(c,6))
(2,(b,5))
10 cogroup(otherDataset, [numTasks]) 案例
- 作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
- 需求:创建两个pairRDD,并将key相同的数据聚合到一个迭代器。
val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")));
val rdd1=sc.parallelize(Array((1,4),(2,5),(3,6)));
rdd.cogroup(rdd1).collect().foreach(println);
-------
(1,(CompactBuffer(a),CompactBuffer(4)))
(3,(CompactBuffer(c),CompactBuffer(6)))
(2,(CompactBuffer(b),CompactBuffer(5)))
6. RDD转化操作综合案列–统计广告点击数量
- 数据结构:省份,城市,广告, 中间字段使用空格分割。
样本如下:
asdf aaa 4
asdf aaa 3
asdf aaa 4
asdf aaa 2
asdf aaa 1
qwer asdf 1
qwer asdf 2
qwer asdf 3
qwer asdf 1
qwer asdf 2
- 需求:统计出每一个省份广告被点击次数的TOP3
- 思路:
- 读取文件
- 按照最小粒度聚合:((Province,AD),1)
- 计算每个省中每个广告被点击的总数:((Province,AD),sum)
- 将省份作为key,广告加点击数为value:(Province,(AD,sum))
- 将同一个省份的所有广告进行聚合(Province,List((AD1,sum1),(AD2,sum2)…))
- 对同一个省份所有广告的集合进行排序并取前3条,排序规则为广告点击总数
def main(args: Array[String]): Unit = {
//上下文
var conf=new SparkConf().setAppName("WordCount").setMaster("local");
var sc=new SparkContext(conf);
sc.setLogLevel("ERROR")
//读文件
val rdd=sc.textFile("in/word.txt");
//转化成((province,AD),1)
val r1=rdd.map(x=>{
val line=x.split(" ");
((line(0),line(2)),1);
})
//计算每个省每个广告被点击总数
var r2=r1.reduceByKey(_+_);
//转化成 (province,(AD,times))
val r3=r2.map(x=>(x._1._1,(x._1._2,x._2)));
//分组
var r4=r3.groupByKey();
//计算前三名的广告
val r5=r4.mapValues(xml=>{
xml.toList.sortWith((x,y)=>{
x._2>y._2
}).take(3);
})
//打印
r5.collect().foreach(println);
}
加油!!快结束了
7. RDD行为操作
1 基本
-
collect()–在驱动程序中,以数组的形式返回数据集的所有元素。
-
count()–返回RDD中元素的个数
-
first ()–返回第一个元素
-
take(n)–返回前n个元素
-
takeOrdered(n)—返回该RDD排序后的前n个元素组成的数组
-
countByKey()----针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
1 reduce(func)案例
- 作用:通过func函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
- 需求:创建一个RDD,将所有元素聚合得到结果。
val rdd2 = sc.makeRDD(Array(("a",1),("a",3),("c",3),("d",5)));
val map=rdd2.reduce((x,y)=>{
(x._1+y._1,x._2+y._2);
});
println(map);
-------
(aacd,12)
3 . aggregate案例
- 参数:(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
- 作用:aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。
- 需求:创建一个RDD,将所有元素相加得到结果
val rdd=sc.makeRDD(1 to 10,2);
var r=rdd.aggregate(0)(_+_,_+_);
println(r);
----
55
fold(num)(func)–折叠操作,aggregate的简化操作,seqop和combop一样。
countByKey()案例
文件操作
- saveAsTextFile(path)
作用:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 - saveAsSequenceFile(path)
作用:将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 - saveAsObjectFile(path)
作用:用于将RDD中的元素序列化成对象,存储到文件中。
恭喜你,终于读完了这个又臭又长的教程!!!
下一篇文章–RDD进阶知识