Spark core
简单wordcount案例
spark文件基本流程
1、创建spark环境
//配置spark对象
val conf = new SparkConf()
//设置任务名
conf.setAppName(“wordcount”)
//指定spark代码运行方式,local:本地测试
conf.setMaster(“local”)
//spark 上下文对象 用于数据读取(后面使用sparksql的时候使用SparkSession)
2、RDD转换算子操作(因为转换算子是懒执行)
3、RDD操作算子(action算子)
字符拼接可以使用:s"…$变量"
println(s"groupByRDD:${groupByRDD.getNumPartitions}")
设置分区
val linesRDD: RDD[String] = sc.textFile(“data/words.txt”, minPartitions = 4)
- 默认一个block对应一个分区, 切片规则和mr是一样的
- 当设置了minPartitions,rdd的分数数会大于等于这个值,具体多少分区需要通过计算得到
- 计算原则是保证每一个切片的数据量是差不多的
注意:如果算子没有产生shuffle,返回的新的rdd的分区数默认等于前一个rdd的分区数
val groupByRDD: RDD[(String, Iterable[String])] = wordsRDD.groupBy((word: String) => word, numPartitions = 2)
- 如果算子产生了shuffle,可以手动设计分区数据, 默认也是等于前一个rdd的分区数
- 类似在MR中设置reduce的数量
- 一般来说保证每一个分区类的数据量在128m左右是比较合适的
- 如果资源无限的情况下,分区越多,执行的并发越高,效率越高
Cache缓存
多rdd的缓存
默认的缓存级别:MEMORY_ONLY(仅内存)
可以手动设置缓存级别:stuRDD.persist(StorageLevel.MEMORY_AND_DISK_SER)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UHgJ8s9L-1652974556096)(G:\数加科技\知识点总结\hadoop\spark\spark\cache.png)]
转换算子
转换算子是懒执行,需要一个action算子触发执行
常用算子
操作算子介绍
一、Map
parallelize
parallelize并行化集合是根据一个已经存在的Scala集合创建的RDD对象。集合的里面的元素将会被拷贝进入新创建出的一个可被并行操作的分布式数据集。
//基于scala集合构建rdd
/**
* numSlices : 指定分区数量
*/
val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 2)
mapPartitions
- mapPartitions: 将一个分区的数据传递给后面的函数,
- 一次处理一个分区的数据,需要返回一个迭代器
- 为什么是迭代器而不是集合,因为集合会将数据加载到内存中,
- 如果一个分区数据量太大会导致内存溢出
val rdd3: RDD[Int] = rdd1.mapPartitions((iter: Iterator[Int]) => {
//这里的map时scala中的api,因为返回的值不是RDD
val iterator: Iterator[Int] = iter.map((i: Int) => i * 2)
iterator
})
rdd3.foreach(println)
mapPartitionsWithIndex
相对于mapPartitions,多了分区编号
val rdd4: RDD[Int] = rdd1.mapPartitionsWithIndex({
case (index: Int, iter: Iterator[Int]) =>
println(s"当前分区的编号:$index")
iter
})
总结:mapPartitions 和 mapPartitionsWithIndex 只是将一个分区中的数据交给下一个函数进行处理,不改变 分区数量
二、Filter
filter算子: 对数据进行过滤,函数返回true保留数据,函数返回false过滤数据
三、FlatMap
- flatMap算子:将rdd中的数据一行一行传递给后面的函数,函数返回值必须是一个序列
- flatMap会将返回的序列展开,构建成一个新的rdd
val rdd1: RDD[String] = sc.parallelize(List("java,spark,scala,hadoop", "hadoop,hive,hbase"))
val rdd2: RDD[String] = rdd1.flatMap((str: String) => {
str.split(",")
})
四、sample
sample: 对数据进行抽样
val sampleRDD: RDD[String] = studentRDD.sample(withReplacement = false, 0.1)
- 不放回的抽样 以及 取样为样本的0.1 ( withReplacement 表示放回还是不放回 fraction表示倍数)
- val sampleRDD: RDD[String] = studentRDD.sample(false, 0.1)
五、groupBy与groupByKey
首先明白groupBy与groupByKey的区别:
1、使用groupBy 进行对数据的处理
- 指定一个分组的字段进行分组, 不需要一定是一个kv格式
- 返回的新的rdd的value里面保护所有的字段
2、groupByKey: rdd必须是一个kv格式
- 返回的新的rdd的迭代器中的数据只包含value, 后续在处理数据的时候方便一点
- groupByKey: 可以减少shuffle过程中传输的数据量,效率比groupBy高
val groupByRDD: RDD[(String, Iterable[(String, Int)])] = scoreRDD.groupBy(_._1)
val groupByKeyRDD: RDD[(String, Iterable[Int])] = scoreRDD.groupByKey()
六、reduceByKey
reduceByKey: 对相同的key的value进行聚合计算, 需要一个聚合函数
- reduceBykey会在map端进行预聚合,可以减少shuffle过程中需要传输的数据量,效率比GrouByKey高
- 能使用reducekeyKey的时候尽量使用reduceByKey
val countRDD: RDD[(String, Int)] = scoresRDD.reduceByKey((x: Int, y: Int) => {
val j: Int = x + y
j
})
使用reducebyKey改进wordcount进行统计单词数量
val countRDD: RDD[(String, Int)] = linesRDD
.flatMap(_.split(","))
.map({
case (word: String) =>
(word, 1)
})
.reduceByKey((x, y) => x + y)
//打印输出
countRDD.foreach(println)
七、Union
union :合并两个rdd, 不会对数据做去重, 两个rdd的类型要完全一致
- 在物理层面并没有合并,只是在逻辑层面合并了
- 也就是两个RDD里面的分区还是两个分区,只是多了一个RDD进行存储,弹性分布式数据集
八、join
join 唯一得注意的是左连接或者右连接的时候如果需要保留数据,需要对数据进行模式匹配
innerJoin: 两个表都有才能关联上
leftOuterJoin: 以左表为基础,如果右表没有这个key,补NOne
- Option: 可选择的值,有值或者没有值
- 因为有可能是空值的原因,所以得进行进一步的判断
- Option的类型有两种:一种是Some() 另一种是None
fullOuterJoin: 以两个表为基础,有一边有数据就会出来结果,列一边补None
val joinRDD: RDD[(String, (String, Int))] = idNameRDD.join(idAgeRDD)
val innerJoinRDD: RDD[(String, String, Int)] = joinRDD.map({
case (id: String, (name: String, age: Int)) =>
(id, name, age)
})
innerJoinRDD.foreach(println)
val leftRDD: RDD[(String, (String, Option[Int]))] = idNameRDD.leftOuterJoin(idAgeRDD)
/**
* 因为有可能是空值的原因,所以得进行进一步的判断
* Option的类型有两种:一种是Some() 另一种是None
*/
leftRDD.map({
case (id: String, (name: String, Some(age))) =>
(id, name, age)
case (id: String, (name: String, None)) =>
(id, name, 0)
}).foreach(println)
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)
九、mapValues
mapValues: key不变,对value做处理
val rdd: RDD[(String, Int)] = idAgeRDD.mapValues(age => age + 1)
十、sortBy、sortByKey
sortBy: 指定一个字段进行排序
sortByKey;通过key进行排序
val sortByKeyRDD: RDD[(String, Int)] = sumScoreRDD.sortByKey()
sortByKeyRDD.foreach(println)
默认是升序
val sortByRDD: RDD[(String, Int)] = sumScoreRDD.sortBy(kv => kv._2, ascending = false)
十一、distinct
distinct: 对数据去重,会产生shuffle
val distinctRDD: RDD[Int] = rdd1.distinct()
action算子介绍
action算子,每一个action算子都会产生一个job
一、count
统计rdd的数据行数
val count: Long = studentRDD.count()
二、sum
对rdd中的数据求和,rdd中的数据类型必须是数字
val sumAge: Double = studentRDD
.map(line => line.split(",")(2).toInt)
//对所有数据求和,只能是数字类型
.sum()
三、take
取top, 返回一个数组, 不能取太多, 会导致内存溢出
val top: Array[String] = studentRDD.take(10)
四、collect
将rdd转换成数组,如果rdd数据量比较大,会导致内存溢出 (1G)
val array: Array[String] = studentRDD.collect()
五、foreach与foreachPartition
foreach: 遍历rdd中的数据,也是一个action算子
foreachPartition: 一次将一个分区的数据传递给后面的函数
studentRDD.foreach(println)
studentRDD.foreachPartition((iter: Iterator[String]) => {
iter.foreach(println)
})
六、saveAsTextFile
将数据保存在hdfs中
HDFSUtil工具类
/**
* 删除hdfs中的一个路径,
*
* 如果是本地运行,就删除本地路径
*
*/
val conf = new Configuration()
//创建hdfs 文件系统对象
val fileSystem: FileSystem = FileSystem.get(conf)
//判断输出路径是否存在
if(fileSystem.exists(new Path(path))){
println("输出路径已存在,自动删除")
//删除路径
fileSystem.delete(new Path(path), true)
HDFSUtil.deletePath("data/test")
studentRDD.saveAsTextFile("data/test")
两个经典案例(务必会写*******)
1、统计偏科最严重的前100名学生
基本思路:
- 通过方差评估偏科程度
- 1、计算方差
- 2、计算每个人多个科目分数的平均值
val conf = new SparkConf()
conf.setMaster("local")
conf.setAppName("Student")
val sc = new SparkContext(conf)
val scoresRDD: RDD[String] = sc.textFile("data/score.txt")
//整理数据
val studentRDD: RDD[(String, String, Double)] = scoresRDD
.map(_.split(","))
.filter(_.length == 3)
.map({
case Array(id: String, cid: String, sco: String) =>
(id, cid, sco.toDouble)
})
//以学号进行分组
val groupByRDD: RDD[(String, Iterable[(String, String, Double)])] = studentRDD.groupBy(kv => kv._1)
//求方差
val stdRDD: RDD[(String, Double)] = groupByRDD.map({
case (id: String, iter: Iterable[(String, String, Double)]) =>
//取出一个学生的所有成绩
val scores: List[Double] = iter.map(_._3).toList
//求取平均值
val avg: Double = scores.sum / scores.length
//根据 求和(Xi - X0) / N来进行求方差
val std: Double = scores.map(i => (i - avg) * (i - avg)).sum / scores.length
(id, std)
})
//按照方差排序 且获得前100名学生
val top: Array[(String, Double)] = stdRDD.sortBy(_._2, ascending = false).take(100)
top.foreach(println)
2、统计每课都及格的学生
基本思路:
需要考虑每门成绩在对应的总成绩
val conf = new SparkConf()
conf.setAppName("student")
conf.setMaster("local")
val sc = new SparkContext(conf)
//读取score成绩表
val scoresRDD: RDD[String] = sc.textFile("data/score.txt")
val subjectRDD: RDD[String] = sc.textFile("data/subject.txt")
val scoresKVRDD: RDD[(String, (String, Int))] = scoresRDD
.map(_.split(","))
.filter(_.length == 3)
.map({
case Array(sid: String, cid: String, sco: String) =>
(cid, (sid, sco.toInt))
})
val subjectKVRDD: RDD[(String, String)] = subjectRDD
.map(_.split(","))
.filter(_.length == 3)
.map({
case Array(cid: String, _: String, sumSco: String) =>
(cid, sumSco)
})
// val value: RDD[(String, ((String, Int), String))] = scoresKVRDD.join(subjectKVRDD)
//
// val value1: RDD[(String, String, Int, Int)] = value.map({
// case (cid: String, ((sid: String, sco: Int), sumSco: String)) =>
// (sid, cid, sco, sumSco.toInt)
// }).filter({
// case (_: String, _: String, sco: Int, sumSco: Int) =>
// sco.toDouble >= sumSco * 0.6
// })
//
// val value2: RDD[(String, String, Int, Int)] = value1.groupBy(_._1).filter(_._2.size == 6).flatMap(_._2)
scoresKVRDD
.join(subjectKVRDD)
.map({
case (cid: String, ((sid: String, sco: Int), sumSco: String)) =>
(sid, cid, sco, sumSco.toInt)
})
.filter({
case (_: String, _: String, sco: Int, sumSco: Int) =>
sco.toDouble >= sumSco * 0.6
})
.groupBy(_._1)
.filter(_._2.size == 6)
.flatMap(_._2)
.foreach(println)
累加器
算子内的代码运行在Executor端
在spark写代码的时候不能在算子内取修改算子外的一个普通变量,
就算修改了在算子外也不会生效
//1、定义累加器
val countAcc: LongAccumulator = sc.longAccumulator
studentRDD.foreach(stu=>{
countAcc.add(1)
})
//3、在算子外获取累加的结果
println(countAcc.value)
广播
在算子中想要外围的集合可以使用广播
//在算子内不能使用其它的rdd来写代码
/* scoreRDD.foreach(sco => {
studentRDD.foreach(println)
})*/
//将rdd的数据拉取到Driver端,构建成一个数组
val studentArray: Array[String] = studentRDD.collect()
//转换成kv格式
val kvStuRDD: Array[(String, String)] = studentArray.map(stu => {
val split: Array[String] = stu.split(",")
val id: String = split(0)
(id, stu)
})
//转换成map集合
val stuMap: Map[String, String] = kvStuRDD.toMap
//将一个普通的变量广播出去
val stuMapBro: Broadcast[Map[String, String]] = sc.broadcast(stuMap)
/**
* mapjoin: 在map端进行表关联,不会产生shuffle
* 当一个大表和一个小表进行关联的时候可以使用mapjoin
*
*/
val joinRDD: RDD[(String, String)] = scoreRDD.map(sco => {
val split: Array[String] = sco.split(",")
val id: String = split(0)
//使用id到学生表的map集合中获取学生的信息
//在算子内获取广播变量的值
val broValue: Map[String, String] = stuMapBro.value
val stuInfo: String = broValue.getOrElse(id, "默认值")
(stuInfo, sco)
})
joinRDD.foreach(println)
面试部分
spark和MR的区别
- spark比mr快
1、spark可以基于内存进行迭代
2、spark可以将数据缓存在内存,如果数据量大会不稳定,会导致内存溢出- spark比mr简洁
依赖于scala语言简介性- spark功能更强大
spark rdd
弹性的分布式数据集
rdd中默认没有数据,rdd只是一个逻辑概念
rdd五大特性
1、rdd由一组分区组成,默认一个block对应一个分区, 分区越多计算的并行度就越高
2、算子实际上是作用在每一个分区上的,一个分区由一个task处理,
3、rdd之间有依赖关系,宽依赖,窄依赖,有shuffle是宽依赖,没有shuffle是窄依赖
4、分区类的算子只能作用在kv格式的rdd上,groupByKey reduceBykey, soerByKey, join
5、spark为task的计算提供了最佳计算位置,会尽量将task发生到数据所在的节点执行,移动计算而不是移动数据
分区决定因素
1、读取hdfs数据
切片的规则和mr是一致的,默认一个block对应一个切片
可以设计最小分区数据,真实的分区需要通过计算得到(保证每一个每一个分区内的数据量是相对均衡的)
2、窄依赖算子
产生新的rdd的分区数等于前一个rdd的分区数,不能修改
因为没有产生shuffle,不能改变分区数据
比如 map filter flatMap mapPartition
3、宽依赖算子
默认等于前一个rdd的分区数
可以手动设计分区数
缓存
对同一个rdd进行多次使用的时候可以将rdd的数据缓存起来
第一次需要从hdfs中读取数据,后续的job直接从缓存中读取数据
缓存级别
1、MEMORY_ONLY 数据量不大,内存充足
2、MEMORY_AND_DIS_SER 数据量较大的时候,一班适用于rdd前的计算逻辑很长的时候
常用算子(上面有介绍)
map
flatMap
union
Saimple
filter
groupBy groupByKey
redyceByKey
可以用groupByKey的时候可以考虑用reduceByKey
join
将两个格式为kv的RDD进行连接(连接的种类有很多)
sortBy sortByKey
MapValues mappartition mapPartitionWithIndex
distinct
take
count
sum
foreach foreachPartition
collect
save
算子
转换算子是懒执行,不能单独执行,需要操作算子触发执行
操作算子会触发任务执行,每一个操作算子都会触发一个任务
reduceByKey和grouByKey的区别
reduceByKey会在map端做预聚合
预聚合可以减少shuffle过程中需要传输的数据量,效率更高
环境搭建(必问)
local
本地运行,一般用于写代码的测试使用
独立集群
为spark搭建一个独立的资源管理集群,不依赖huadoop,可以独立运行
yarn 所有的计算引擎都可以运行在yarn上
将spark任务提交到yarn运行
运行模式
yarn-client
可以在本地看到详细的日志
yarn-cluster
本地不会打印详细的日志
spark on yarn 中yarn-client与yarn-cluster的区别
yarn-client
Driver在本地运行
可以本地看到任务执行的详细进度以及日志
如果在本地大量使用client模式提交任务会导致本地网卡流量剧增
一般用于上线前的测试yarn-cluster
Driver在随机的一个nodeManager上执行
一般用于上线使用
在本地看不到详细日志
spark程序结构
Driver
负责任务调度
Executor
负责执行task
资源调度和任务调度
资源调度
1、在本地启动Driver向RM申请启动AM
2、RM会随机分配一个节点启动AM
3、AM向RM申请启动Executor
4、RM分配一个节点启动Executor
5、Executor反向注册给Driver端的TaskSheduler任务调度:
1、资源调度完成且出现action算子时,根据转换算子构建有向无环图DAG
2、DAGSheduler根据宽依赖划分stage,每一个stage都是一组可以并行计算的task
3、DAGSheduler根据stage的顺序以TaskSet的形式传递给TaskSheduler
4、TaskSheduler将task发送到Executor上进行执行
5、Task在Executor线程池中运行,完成后会反馈给TaskSheduler重试机制
如果Task执行失败,TaskSheduler会重试三次
如果还失败DAGSheduler会重试四次
如果是因为shuffle文件找不到,导致reduce端拉取不到数据,则TaskSheduler不负责重试,而是由DAGSheduler重试上一个stage推测执行
用
在本地看不到详细日志
spark程序结构
Driver
负责任务调度
Executor
负责执行task
资源调度和任务调度
资源调度
1、在本地启动Driver向RM申请启动AM
2、RM会随机分配一个节点启动AM
3、AM向RM申请启动Executor
4、RM分配一个节点启动Executor
5、Executor反向注册给Driver端的TaskSheduler任务调度:
1、资源调度完成且出现action算子时,根据转换算子构建有向无环图DAG
2、DAGSheduler根据宽依赖划分stage,每一个stage都是一组可以并行计算的task
3、DAGSheduler根据stage的顺序以TaskSet的形式传递给TaskSheduler
4、TaskSheduler将task发送到Executor上进行执行
5、Task在Executor线程池中运行,完成后会反馈给TaskSheduler重试机制
如果Task执行失败,TaskSheduler会重试三次
如果还失败DAGSheduler会重试四次
如果是因为shuffle文件找不到,导致reduce端拉取不到数据,则TaskSheduler不负责重试,而是由DAGSheduler重试上一个stage推测执行
如果有的task执行很慢,taskScheduler会发生一个一样的task到另一个节点执行,谁先执行完成以锥的结果为准
931

被折叠的 条评论
为什么被折叠?



