RDD 是 Resilient Distributed Dataset — 弹性分布式数据集的简称。它是 Spark 的基本数据结构。它是一个不可变的对象集合,在集群的不同节点行进行计算。
Spark RDD transformation 操作是一个从现有的 RDD 生成新 RDD 的函数。如 map(),filter(),reduceByKey() 等。
诸如 map、filter 这样的操作,其数据来自于上一个单独的分区。即输出RDD分区中的数据,来自于父RDD中的单个分区。-- 输入、输出,分区数不变,两者的分区是一一对应的。
窄依赖有:map,flatmap,mapPartition,filter,sample、union 等。
在子 RDD 单个分区中计算结果所需的数据可能存在于父 RDD 的多个分区中,因此宽依赖也被成为 shuffle translation。
宽依赖有:intersection、distinct、reduceByKey、GroupByKey、join、cartesian、repartition、coalesce 等。
Tips:窄依赖是父 RDD 的一个分区最多仅能在子 RDD 一个分区;宽依赖是子 RDD 的一个分区依赖于多个父 RDD 的分区(或:父 RDD 的每个分区都可能被多个子 RDD 分区所使用),所以宽依赖会造成 shuffle。所以窄依赖是父、子 RDD 分区是一对一的关系,而宽依赖是父、子 RDD 的分区是多对多的关系。
借图: https://blog.csdn.net/tswisdom/article/details/79882308
窄依赖 | 宽依赖 |
---|---|
每个parentRDD 的 partition 最多被 childRDD 的一个 partition所使用。一对一 | 多个 childRDD 的partition会依赖于同一个 parentRDD 的 partition。多对多,发生 shuffle 。stage 划分的根据 |
Spark 中的 action 操作,返回 RDD 计算的最终结果,其结果是一个值,而不是一个 RDD。action 触发血缘关系中 RDD 上的 transformation 操作的真正计算,计算结果返回 Driver 或被写入数据库。
这种设计使 Spark 运行更加高效。
常见的 action:first、take、reduce、collect、count 等。
创建 RDD 有三种方式:
在新学习 Spark 时,先使用集合来创建 RDD。即在 Driver 程序中创建集合并将其传递给SparkContext 的 parallelize() 方法,这种方法很少使用在正式环境中。
--scala
val conf = new SparkConf().setAppName(appName).setMaster(master)
val sc=new SparkContext(conf)
--java
SparkConf conf = new SparkConf().setAppName(appName).setMaster(master);
JavaSparkContext sc = new JavaSparkContext(conf);
appName:是用来显示在 Spark 集群的 WebUI 上的 Tag 标签,用来区分项目。
master:是一个 spark、yarn、mesos 集群的 url,在实际项目中,集群运行时不会对 master 进行硬编码,而是用 spark-submit 启动应用程序,并传递 master 给应用程序。但是在进行本地测试或单元测试时,可以使用 local 字符串来运行 spark。
Spark 的提交方式:
./bin/spark-submit \
--class
--master
--deploy-mode
--conf =
... # other options
\
[application-arguments]
eg:./bin/spark-sumit --class packageName+className --deploy-mode cluster/client
--scala
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
--java
List data = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD distData = sc.parallelize(data);
Spark 可以从 Hadoop 支持的任意存储源创建分布式数据集,包括本地文件系统,HDFS,Cassandra,HBase,Amazon S3 等,Spark 支持文本文件,SequenceFiles 和任何其它 Hadoop InputFormat。
对于文本文件,RDD 可以使用 SparkContext 的 textFile 方法来创建数据集,此方法可接受文件的 URL。如本地路径或 hdfs://,s3a:// 等 URL。创建数据集,并将其作为行集合读取。
--scals
val distFile = sc.textFile("data.txt")
Val textFile = sc.textFile("filePath")
创建之后,就可以对 RDD 进行相应的操作了。。
distFile.map(s => s.length).reduce((a, b) => a + b);
读取文件的注意事项
textFile("/my/directory")
textFile("/my/directory/*.txt")
textFile("/my/directory/*.gz").
除了文本文件外,spark 的 scala API 还支持其他几种数据格式:
启动 HDFS -> 读取 hdfs 上的数据
这里和读取本地数据一样,一定要保证所有 worker 节点都能访问到 HDFS 上的文件。
val textFileRDD=sc.textFile("hdfs://bigdata01:9000/testdata/order.txt")
val count=textFileRDD.count()
println("count:"+count)
打包应用程序, maven --> install --> run spark
上传 jar 包到服务器
./spark-submit --class sparkcore.learnTextFile --deploy-mode client /opt/sparkapp/learnTextFile.jar
spark API 严重依赖有将 Driver 程序中的函数传递到集群上运行。
在 scala 语言环境下,可以采用两种方式实现函数的传递:
object MyFunctions {
def func1(s: String): String = { ... }
}
myRdd.map(MyFunctions.func1)
在 java 中,函数由实现 org.apache.spark.api.java.function 包中的接口的类表示。有两种方式可以创建函数:
JavaRDD lines = sc.textFile("data.txt");
JavaRDD lineLengths = lines.map(new Function() {
public Integer call(String s) { return s.length(); }
});
int totalLength = lineLengths.reduce(new Function2() {
public Integer call(Integer a, Integer b) { return a + b; }
});
class GetLength implements Function {
public Integer call(String s) { return s.length(); }
}
class Sum implements Function2 {
public Integer call(Integer a, Integer b) { return a + b; }
}
JavaRDD lines = sc.textFile("data.txt");
JavaRDD lineLengths = lines.map(new GetLength());
int totalLength = lineLengths.reduce(new Sum());
在Java8,可以使用 Lambda 表达式来处理。
map 算子: 将传入的函数应用于 RDD 中的每一条记录,返回有函数结果组成的新 RDD。函数的结果值是一个对象而不是一个集合。
----map操作 scala版本
val textFileRDD=sc.textFile("in/README.md")
val uppercaseRDD=textFileRDD.map(line => line.toUpperCase)
for (elem <- uppercaseRDD.take(3)) {
println(elem)
}
flatmap 操作:与 map 操作类似,但是传入 flatmap 的函数可以返回0个,1个或多个结果值。即函数结果值是一个集合而不是一个对象。
----flatmap操作 scala版本
val flatMapRDD=textFileRDD.flatMap(line =>line.split(" "))
flatMapRDD.take(3).foreach(word => println(word))
注:map:针对每一条输入进行操作,并为每一条输入返回一个对象。(一行输入就返回一个对象);flatmap:针对每一条输入进行操作,并最终针对所有输入返回一个对象(所有输入返回一个对象)。它们都是属于窄依赖。
从 map 和 flatmap 的比较可以看出,map 函数时一对一的转换,它将集合的每个数据元素转换成结果集合的一个数据元素;而 flatmap 则是一对多的转换,将每个元素转换为0或更多的元素。
filter 函数返回一个新的 RDD,只包含满足过滤条件的元素。这个也是一个窄依赖的操作,不会将数据从一个分区转移到其他分区。即不会发生 shuffle 操作。
//过滤出包含单词"spark"的行,然后打印出来
val filterRDD=textFileRDD.filter(line =>line.contains("spark"))
println("count:"+filterRDD.count())
返回 RDD 中非重复记录。它会发生数据从一个分区转移到另外其他分区的情况。即会发生 shuffle 操作。
在这里插入代码片
在 mapPartition 函数中,map 函数同时应用于每个 partition 分区。即 mapPartition 会针对分区进行操作。同时对比 foreachPartition 操作,该操作是一个 action 算子,其也是针对每个分区进行 foreach 操作,能提高性能。
//map每一个分区,然后再map分区中的每一个元素
val mapPartitionRDD=textFileRDD.mapPartitions(partition => {
partition.map(line =>line.toUpperCase)
})
这个操作是在 mapPartition 的基础上,还为传入的函数提供一个整数值,表示分区的索引,map 在分区上依次应用。
val mapPartitionsWithIndexRDD=textFileRDD.mapPartitionsWithIndex((index,partition) => {
partition.map(line =>index +" : "+line.toUpperCase)
})
使用 union 函数,可以在新的 RDD 中获得两个 RDD 元素。这个函数的关键规则是两个 RDDs 应该属于同一类型。
val rdd1 = sc.parallelize(Seq((1,"jan",2016),(3,"nov",2014),(16,"feb",2014)))
val rdd2 = sc.parallelize(Seq((5,"dec",2014),(17,"sep",2015)))
val rdd3 = sc.parallelize(Seq((6,"dec",2011),(16,"may",2015)))
val rddUnion = rdd1.union(rdd2).union(rdd3)
使用 intersection 函数,在新的 RDD 中只能得到两个 RDD 的公共元素。这个函数的关键规则是这两个 RDDs 是同一类型的。
val rdd1 = sc.parallelize(Seq((1,"jan",2016),(3,"nov",2014, (16,"feb",2014)))
val rdd2 = sc.parallelize(Seq((5,"dec",2014),(1,"jan",2016)))
val comman = rdd1.intersection(rdd2)
这种数据集的典型模式是每一行都是一个 key 映射到一个或多个 value。因此,spark 提供了一个名为 PairRDD 的数据结构,而不是常规的 RDD。这使得处理此数据更加简单和高效。
PairRDDs 是一个特殊类型的 RDD,可以存储键 - 值对。
创建 PairRDD 的步骤:
--- scala
val tuple = List(("zhangsan","spark"), ("lisi","kafka"), ("Mary","hadoop"), ("James","hive"))
val pairRDD = sc.parallelize(tuple)
pairRDD.foreach(t =>println(t._1+","+t._2))
--- java
List> tuple = Arrays.asList(new Tuple2<>("Lily", 23),
new Tuple2<>("Jack", 29),
new Tuple2<>("Mary", 29),
new Tuple2<>("James",8));
JavaPairRDD pairRDD = sc.parallelizePairs(tuple);
--- scala
val inputStrings = List("Lily 23", "Jack 29", "Mary 29", "James 8")
val regularRDDs = sc.parallelize(inputStrings)
val pairRDD = regularRDDs.map(s => (s.split(" ")(0), s.split(" ")(1)))
--- java
List inputStrings = Arrays.asList("Lily 23", "Jack 29", "Mary 29", "James 8");
JavaRDD regularRDDs = sc.parallelize(inputStrings);
JavaPairRDD pairRDD = regularRDDs.mapToPair(getPairFunction());
private static PairFunction getPairFunction() {
return s -> new Tuple2<>(s.split(" ")[0], Integer.valueOf(s.split(" ")[1]));
}
PairRDDs 允许使用常规 RDDs 可用的所有 transformation,支持与常规 RDDs 相同的功能。
由于 PairRDDs 包含元组,所以我们需要传递操作元组而不是单个元素的函数给 Spark。
filter
案例: 过滤value
val tuple = List(("zhangsan","spark"), ("lisi","kafka"), ("Mary","hadoop"), ("James","hive"))
val pairRDD = sc.parallelize(tuple)
val filterRDD=pairRDD.filter(t =>t._2.equals("spark"))
filterRDD.foreach(t =>println(t._1+","+t._2))
reduceByKey
案例:wordcount
val lines = sc.textFile("in/word_count.text")
val wordRdd = lines.flatMap(line => line.split(" "))
val wordPairRdd = wordRdd.map(word => (word, 1))
val wordCounts = wordPairRdd.reduceByKey((x, y) => x + y)
for ((word, count) <- wordCounts.collect()) println(word + " : " + count)
combineByKey
案例:计算平均分数
val scores=List(
ScoreDetail("A","Math",98),
ScoreDetail("A","English",88),
ScoreDetail("B","Math",75),
ScoreDetail("B","English",78),
ScoreDetail("C","Math",90),
ScoreDetail("C","English",80),
ScoreDetail("D","Math",91),
ScoreDetail("D","English",80)
)
val scoresWithKey=for(i <- scores)yield (i.studentName,i)
val scoreWithKeyRDD=sc.parallelize(scoresWithKey).partitionBy(new HashPartitioner(3)).cache()
scoreWithKeyRDD.foreachPartition(partition =>println(partition.length))
scoreWithKeyRDD.foreachPartition(partition =>{
partition.foreach(item => println(item._1+":"+item._2))
println("**********************")
})
val avgScoresRDD=scoreWithKeyRDD.combineByKey(
(x:ScoreDetail) => (x.score,1),
(acc:(Float,Int),x:ScoreDetail) => (acc._1+x.score,acc._2+1),
(acc1:(Float,Int),acc2:(Float,Int)) => (acc1._1+acc2._1,acc1._2+acc2._2)
).map({
case (key,value) =>(key,value._1/value._2)
})
avgScoresRDD.collect().foreach(println)
}
sortByKey
案例:针对键值排序
val data =sc.parallelize(Seq(("maths",52), ("english",75), ("science",82), ("computer",65), ("maths",85)))
val sorted = data.sortByKey()
sorted.foreach(println)
join
根据 key 值来自组合两个不同的 RDD。
val data = sc.parallelize(Array(('A',1),('b',2)))
val data2 =sc.parallelize(Array(('A',4),('A',6),('b',7),('c',3),('c',8)))
val result = data.join(data2)
//输出:(A,(1,4)),(A,(1,6)),(b,(2,7))
println(result.collect().mkString(","))
/**
* 输出:
* A:(1,4)
* A:(1,6)
* b:(2,7)
*/
count 返回 RDD 中的数据数量。
在这里插入代码片
从 RDD 返回 n 个元素。它试图减少访问的分区数量,但不能使用此方法来控制访问元素的顺序。
在这里插入代码片
如果 RDD 中的元素有序,那么可以使用 top 从 RDD 中提取前几个元素
//map()操作将用它的长度映射每一行。top(3)将从mapFile返回3条默认排序的记录
val data =sc.textFile("spark_test.txt").rdd
val mapFile = data.map(line => (line,line.length))
val res = mapFile.top(3)
res.foreach(println)
countByValue 返回,每个元素出现在 RDD 中的次数。
val sc =new SparkContext(conf)
val textFileRDD=sc.textFile("in/README.md")
val flatMapRDD=textFileRDD.flatMap(line =>line.split(" "))
val countValue=flatMapRDD.countByValue()
countValue.foreach(t => println(t._1+" : "+t._2))
}
reduce 函数将 RDD 中的两个元素作为输入,然后生成与输入元素相同类型的输出。
val rdd1 = sc.parallelize(List(20,32,45,62,8,5))
val sum = rdd1.reduce(_+_)
println(sum)
collect 函数时将整个 RDDs 内容返回给 Driver 程序的最常见最简单的操作。collect 主要应用在单元测试中,在使用 collect 方法时,期望 Driver 是满足数据大小的,否则会出现内存溢出。
val data = sc.parallelize(Array(('A',1),('b',2),('c',3)))
val data2 =sc.parallelize(Array(('A',4),('A',6),('b',7),('c',3),('c',8)))
val result = data.join(data2)
println(result.collect().mkString(","))
当希望对 RDD 的每个元素操作时,但它不应该返回给 Driver 程序时。这种情况下使用 foreach 函数是非常适合的。
在这里插入代码片
类似于 mapPartition,针对每个分区进行 foreach 操作。但是 foreachPartition 是 action 操作,同时 foreachPartition 没有返回值。
在这里插入代码片
在这里插入代码片
在这里插入代码片
在这里插入代码片