初识Spark-core
RDD的概念
RDD是指弹性分布式数据集,可以把RDD当成list使用
RDD五大特性
1、RDD由一组分区组成,默认一个block块对应一个分区
2、算子是作用在每一个分区上的,每一个切片对应一个task
3、RDD之间有一系列的依赖关系,有shuffle:宽依赖,没有shuffle:窄依赖
4、分区类的算子只能作用在KV格式的RDD上[groupBykey,sortBykey,reduceBykey]
5、spark为task提供了最佳计算位置,spark会将task发送到所在的数据结点执行
缓存级别的选择
首先了解读写速度
从内存中读取数据>从磁盘中读取数据>从网络中读取数据
缓存级别的选择,常用的有两个
1、如果内存充足,数据量不大,选择MEMORY_ONLY(默认)【仅内存】
2、如果内存不能完全放下所有的数据,选择MEMORY_AND_DISK_SER【内存和磁盘并且压缩】
算子的概念
算子的分类
1、转换算子(Transformations)
返回值是RDD
转换算子是懒执行,需要一个操作算子触发执行
2、操作算子(action)
返回值不是RDD
每一个action算子会触发一个任务
转换算子
1、map:一对一
map算子: 将rdd中的数据一行一行传递给后面的函数,及那个函数的返回值构建成一个新的rdd
mapPartitions: 将一个分区的数据传递给后面的函数,
一次处理一个分区的数据,需要返回一个迭代器
为什么是迭代器而不是集合,因为集合会将数据加载到内存中,
如果一个分区数据量太大会导致内存溢出
object Demo6Map {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("map")
conf.setMaster("local")
val sc = new SparkContext(conf)
/**
* 构建rdd方法
* 1、读取文件
* 2、基于scala集合构建rdd --- 一班用于测试
*
*/
//基于scala集合构建rdd
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 2)
//getNumPartitions 不是一个算子,是一个普通的方法
println(s"rdd1:${rdd1.getNumPartitions}")
/**
* map算子: 将rdd中的数据一行一行传递给后面的函数,及那个函数的返回值构建成一个新的rdd
*
*/
val rdd2: RDD[Int] = rdd1.map((i: Int) => i + 1)
rdd2.foreach(println)
/**
* mapPartitions: 将一个分区的数据传递给后面的函数,
* 一次处理一个分区的数据,需要返回一个迭代器
*
* 为什么是迭代器而不是集合,因为集合会将数据加载到内存中,
* 如果一个分区数据量太大会导致内存溢出
*
*/
val rdd3: RDD[Int] = rdd1.mapPartitions((iter: Iterator[Int]) => {
println("=" * 100)
//对这个迭代器进行处理,这里是scala api
val iterator: Iterator[Int] = iter.map((i: Int) => i * 2)
//返回一个迭代器
iterator
})
rdd3.foreach(println)
/**
* mapPartitionsWithIndex: 多了分区编号
*/
val rdd4: RDD[Int] = rdd1.mapPartitionsWithIndex {
case (index: Int, iter: Iterator[Int]) =>
println(s"当前分区的编号:$index")
iter
}
rdd4.foreach(println)
}
}
2、filter:过滤
filter算子: 对数据进行过滤,函数返回treu保留数据,函数返回false过滤数据
object Demo7Filter {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("filter")
conf.setMaster("local")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9))
/**
* filter算子: 对数据进行过滤,函数返回treu保留数据,函数返回false过滤数据
*
*/
val filterRDD: RDD[Int] = rdd1.filter((i: Int) => {
//保留偶数
i % 2 == 0
})
filterRDD.foreach(println)
}
}
3、flatmap:一行变多行
flatMap算子:将rdd中的数据一行一行传递给后面的函数,函数返回值必须是一个序列
flatMap会将返回的序列展开,构建成一个新的rdd
object Demo8FlatMap {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("filter")
conf.setMaster("local")
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.parallelize(List("java,spark,scala,hadoop", "hadoop,hive,hbase"))
/**
* flatMap算子:将rdd中的数据一行一行传递给后面的函数,函数返回值必须是一个序列
* flatMap会将返回的序列展开,构建成一个新的rdd
*
*/
val rdd2: RDD[String] = rdd1.flatMap((str: String) => {
str.split(",")
})
rdd2.foreach(println)
}
}
4、sample:抽样
参数:(是否放回,抽样比列)
抽样不精确
object Demo9Sample {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("sample")
conf.setMaster("local")
val sc = new SparkContext(conf)
val studentRDD: RDD[String] = sc.textFile("data/students.txt")
/**
* sample: 对数据进行抽样
*/
val sampleRDD: RDD[String] = studentRDD.sample(false, 0.1)
sampleRDD.foreach(println)
}
}
5、groupBy&groupBykey
groupBy
指定一个分组的字段进行分组, 不需要一定是一个kv格式
返回的新的rdd的value里面保护所有的字段
groupBykey
rdd必须是一个kv格式
返回的新的rdd的迭代器中的数据只包含value, 后续在处理数据的时候方便一点,指定key进行分组,返回值只有(k,v),可以减少shuffle过程传输的数据量
object Demo10Group {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("group")
conf.setMaster("local")
val sc = new SparkContext(conf)
//统计学生的总分
val linesRDD: RDD[String] = sc.textFile("data/score.txt")
//取出学号和分数
val scoreRDD: RDD[(String, Int)] = linesRDD
.map(line => line.split(","))
.filter(arr => arr.length == 3)
.map {
case Array(id: String, _: String, sco: String) =>
(id, sco.toInt)
}
/**
* groupBy: 指定一个分组的字段进行分组, 不需要一定是一个kv格式
* 返回的新的rdd的value里面保护所有的字段
*
*/
val groupByRDD: RDD[(String, Iterable[(String, Int)])] = scoreRDD.groupBy(kv => kv._1)
groupByRDD
.map {
case (id: String, iter: Iterable[(String, Int)]) =>
val sumSco: Int = iter.map(_._2).sum
(id, sumSco)
}
.foreach(println)
/**
* groupByKey: rdd必须是一个kv格式
* 返回的新的rdd的迭代器中的数据只包含value, 后续在处理数据的时候方便一点
*
* groupByKey: 可以减少shuffle过程中传输的数据量,效率比groupBy高
*
*/
val groupByKeyRDD: RDD[(String, Iterable[Int])] = scoreRDD.groupByKey()
groupByKeyRDD
.map {
case (id: String, iter: Iterable[Int]) =>
(id, iter.sum)
}
.foreach(println)
while (true) {
}
}
}
6、ReduceByKey
对相同的key的value进行聚合计算, 需要一个聚合函数
reduceBykey会在map端进行预聚合,可以减少shuffle过程中需要传输的数据量,效率比GrouByKey高
能使用reducekeyKey的时候尽量使用reduceByKey
对同一个key的value做聚合
聚合次数=value数-1
局限性:不能处理太复杂的逻辑
使用范围:必须是K,V格式才能使用
object Demo11ReduceByKey {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("group")
conf.setMaster("local")
val sc = new SparkContext(conf)
//统计学生的总分
val linesRDD: RDD[String] = sc.textFile("data/score.txt")
//取出学号和分数
val scoreRDD: RDD[(String, Int)] = linesRDD
.map(line => line.split(","))
.filter(arr => arr.length == 3)
.map {
case Array(id: String, _: String, sco: String) =>
(id, sco.toInt)
}
/**
* groupByKey: 先分组,分组之后对对value进行聚合计算
*
*/
scoreRDD
.groupByKey()
.map(kv => (kv._1, kv._2.sum))
.foreach(println)
/**
* reduceByKey: 对相同的key的value进行聚合计算, 需要一个聚合函数
*
* reduceBykey会在map端进行预聚合,可以减少shuffle过程中需要传输的数据量,效率比GrouByKey高
* 能使用reducekeyKey的时候尽量使用reduceByKey
*
*/
val countRDD: RDD[(String, Int)] = scoreRDD.reduceByKey((x: Int, y: Int) => {
val j: Int = x + y
//println(s"$x + $y = $j")
j
})
countRDD.foreach(println)
while (true) {
}
}
}
7、union
合并两个rdd,不会对数据做去重,两个rdd的数据类型要完全一致,在物理层面没有合并,只是在逻辑层面合并了
object Demo13Union {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("union")
conf.setMaster("local")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6))
val rdd2: RDD[Int] = sc.parallelize(List(3, 4, 5, 6, 7, 8, 9))
println(s"rdd1:${rdd1.getNumPartitions}")
println(s"rdd2:${rdd2.getNumPartitions}")
/**
* union :合并两个rd, 不会对数据做去重, 两个rdd的类型要完全一致
* 在物理层面并没有合并,只是在逻辑层面合并了
*/
val unionRDD: RDD[Int] = rdd1.union(rdd2)
println(s"unionRDD:${unionRDD.getNumPartitions}")
unionRDD.foreach(println)
}
}
8、join
join分类
join:内连接,取出左右两个表都有的数据
leftOuterjoin:左连接,以左表为基础,如果右表没有这个数据补None
rightOuterjoin:右连接,以右表为基础,如果左表没有这个数据补None
fullOuterjoin:全连接,左右两个并集的数据,没有数据的补None
object Demo14Join {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("union")
conf.setMaster("local")
val sc = new SparkContext(conf)
val idNameRDD: RDD[(String, String)] = sc.parallelize(List(
("000", "晓伟"),
("001", "张三"),
("002", "李四"),
("003", "王五"))
)
val idAgeRDD: RDD[(String, Int)] = sc.parallelize(List(
("001", 23),
("002", 24),
("003", 25),
("004", 23))
)
/**
* innerJoin: 两个表都有才能关联上
*
*/
val innerJoinRDD: RDD[(String, (String, Int))] = idNameRDD.join(idAgeRDD)
//整理数据
innerJoinRDD
.map {
case (id: String, (name: String, age: Int)) =>
(id, name, age)
}
.foreach(println)
/**
* leftOuterJoin: 以左表为基础,如果右表没有这个key,补NOne
*
* Option: 可选择的值,有值或者没有值
*/
val leftJoinRDD: RDD[(String, (String, Option[Int]))] = idNameRDD.leftOuterJoin(idAgeRDD)
//整理数据
leftJoinRDD
.map {
//关联上的处理方式
case (id: String, (name: String, Some(age))) =>
(id, name, age)
//没有关联上的处理方式
case (id: String, (name: String, None)) =>
//给年龄一个默认值
(id, name, 0)
}
.foreach(println)
/**
*
* fullOuterJoin: 以两个表为基础,有一边有数据就会出来结果,列一边补None
*
*/
val fullJoinRDD: RDD[(String, (Option[String], Option[Int]))] = idNameRDD.fullOuterJoin(idAgeRDD)
fullJoinRDD
.map {
case (id: String, (Some(name), None)) =>
(id, name, 0)
case (id: String, (None, Some(age))) =>
(id, "默认值", age)
case (id: String, (Some(name), Some(age))) =>
(id, name, age)
}
.foreach(println)
}
}
9、mapvalues
key不变,只对values做处理
object Demo15MapValues {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("union")
conf.setMaster("local")
val sc = new SparkContext(conf)
val idAgeRDD: RDD[(String, Int)] = sc.parallelize(List(
("001", 23),
("002", 24),
("003", 25),
("004", 23))
)
/**
* mapValues: key不变,对value做处理
*
*/
val rdd: RDD[(String, Int)] = idAgeRDD.mapValues(age => age + 1)
rdd.foreach(println)
}
}
10、sortBy&sortBykey
sortBy: 指定一个字段进行排序
sortByKey;通过key进行排序
默认是升序
object Demo16Sort {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("group")
conf.setMaster("local")
val sc = new SparkContext(conf)
val linesRDD: RDD[String] = sc.textFile("data/score.txt")
//取出学号和分数
val scoreRDD: RDD[(String, Int)] = linesRDD
.map(line => line.split(","))
.filter(arr => arr.length == 3)
.map {
case Array(id: String, _: String, sco: String) =>
(id, sco.toInt)
}
//计算总分
val sumScoreRDD: RDD[(String, Int)] = scoreRDD.reduceByKey(_ + _)
/**
* sortBy: 指定一个字段进行排序
* sortByKey;通过key进行排序
* 默认是升序
*
*/
val sortByRDD: RDD[(String, Int)] = sumScoreRDD.sortBy(kv => kv._2, ascending = false)
sortByRDD.foreach(println)
}
}
11、distinct:去重
distinct: 对数据去重,会产生shuffle
object Demo17Distinct {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("group")
conf.setMaster("local")
val sc = new SparkContext(conf)
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 7, 87, 9, 4, 4, 3, 2, 4, 5, 6))
/**
* distinct: 对数据去重,会产生shuffle
*
*/
val distinctRDD: RDD[Int] = rdd1.distinct()
distinctRDD.foreach(println)
}
}
操作算子(action)
action算子,每一个action算子都会产生一个job
1、count
统计RDD数据的行数
sql中count(1)与count(*)的区别
count(1) ,这一行分组的字段都为null不计数
count(*) ,这一行所有的都为null不计数
2、sum
对rdd中的数据求和,rdd中的数据类型必须是数字
3、take
取top, 返回一个数组, 不能取太多, 会导致内存溢出
4、collect
将rdd转换成数组,如果rdd数据量比较大,会导致内存溢出 (1G)
5、foreach
遍历rdd中的数据,也是一个action算子
6、foreachPartition
一次将一个分区的数据传递给后面的函数
7、saveAsTextFile
将数据保存在hdfs中
object Demo18Action {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("group")
conf.setMaster("local")
val sc = new SparkContext(conf)
val studentRDD: RDD[String] = sc.textFile("data/students.txt")
/**
* action算子,每一个action算子都会产生一个job
*
*/
/**
* count: 统计rdd的数据行数
*/
val count: Long = studentRDD.count()
println(count)
/**
* sum: 对rdd中的数据求和,rdd中的数据类型必须是数字
*
*/
val sumAge: Double = studentRDD
.map(line => line.split(",")(2).toInt)
//对所有数据求和,只能是数字类型
.sum()
println(sumAge / count)
/**
* take: 取top, 返回一个数组, 不能取太多, 会导致内存溢出
*
*/
val top: Array[String] = studentRDD.take(10)
top.foreach(println)
/**
* collect: 将rdd转换成数组,如果rdd数据量比较大,会导致内存溢出 (1G)
*/
val array: Array[String] = studentRDD.collect()
array.foreach(println)
/**
* foreach: 遍历rdd中的数据,也是一个action算子
* foreachPartition: 一次将一个分区的数据传递给后面的函数
*
*/
studentRDD.foreach(println)
studentRDD.foreachPartition((iter: Iterator[String]) => {
iter.foreach(println)
})
/**
* saveAsTextFile: 将数据保存在hdfs中
*
*/
HDFSUtil.deletePath("data/test")
studentRDD.saveAsTextFile("data/test")
while (true) {}
}
}