Spark内核学习

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算子触发执行

  • action算子出发2任务的执行,每一个action算子都会触发一个job

  • 操作算子输出的时候时将转换算子中的输出一编,再输出操作算子的输出

常用算子

操作算子介绍

一、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到另一个节点执行,谁先执行完成以锥的结果为准

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值