spark核心部分总结

spark-core

spark简介

  • 分布式计算引擎(大数据计算框架),用来替代MapReduce
  • 速度是MapReduce的一百倍(官方),实际检测大概十倍左右
  • spark会尽量将数据放在内存中进行计算(cache)
  • 使用DAG有向无环图 spark可以将多个MapReduce串联在一起
  • 粗粒度资源调度,spark在任务执行之前会将所需要的所有资源全部申请下来
  • spark生态体系
  • spark-sql 将sql转换成RDD进行计算
  • MLlib 机器学习
  • Graphx 图计算
  • spark-streaming 实时计算

运行模式

  • local[] 本地运行
  • 独立集群 基本已经淘汰
  • yarn
  • yarn-client
    1.Driver在本地启动
    2.一般用于上线前的测试
    3.spark程序运行的日志会全部发送到Driver端,会导致本地网卡流量剧增
  • yarn-cluster
    1.Driver在集群中启动
    2.上线使用
  • Mesos

RDD五大特性

RDD Resilient Distributed Dataset 弹性分布式数据集

  • A list of partitions
  • A function for computing each split
  • A list of dependencies on other RDDs
  • Optionally, a Partitioner for key-value RDDs
  • Optionally, a list of preferred locations to compute each split on
  1. RDD由一组分区组成,每一个block块对应一个分区
  2. 函数实际上是作用在每一个分区上,每一个分区都会有一个task来处理
  3. RDD之间存在依赖关系

宽依赖

  • 有shuffle的算子会产生宽依赖,分区对应关系是一对多

窄依赖

  • 没有shuffle的算子, 分区对应的关系是一对一

stage

  • 根据宽窄依赖划分stage (一组可以并行计算的task)

spark core 依赖版本和spark版本对应关系_数据

  1. 分区的算子必须作用在key-value格式的RDD上
  2. spark为task的计算提供了最佳计算位置,会尽量将task发送到数据所在的节点执行,移动计算而不是移动数据
  1. taskScheduler知道所有数据的位置
  2. task由taskScheduler发送

常用算子

转换算子

  • map
  • flatmap
  • filter
  • groupByKey
  • reduceByKey
  • join
  • union
  • sortBy
  • sample
  • mapValues
  • mapPartition

操作算子

  • reduce
  • foreach
  • collect
  • count
  • saveAsTextFile

转换算子是懒执行模式,即存在于逻辑层面,实际上并没有做任何操作,只有在出现操作算子时,才会将代码运行。


缓存

  • RDD中默认不保存数据
  • 如果多次使用同一个RDD,可以将RDD进行缓存(懒执行)
  • 使用方式

persist 方法

  • MEMORY_ONLY
  • MEMORY_ONLY_SER
  • MEMORY_AND_DISK_SER
  • 缓存的数据实际上是保存在Executor的内存或者磁盘上的,由blockmanager管理
val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")

    val stuRDD: RDD[String] = rdd1.map(line => {
      println("map")//测试检查用,看RDD算子运行几次
      line
    })

	//尝试不加后上面的map出现几次,相当于 stuRDD.persist(StorageLevel.MEMORY_ONLY)
    stuRDD.cache()

    //缓存到内存,内存足够为第一选择
//    stuRDD.persist(StorageLevel.MEMORY_ONLY)
//
//    第二选择,内存加压缩数据
//    stuRDD.persist(StorageLevel.MEMORY_ONLY_SER)
//
//    第三选择,内存加压缩数据加磁盘
//    stuRDD.persist(StorageLevel.MEMORY_AND_DISK_SER)

    //统计班级人数
    stuRDD
      .map(line => (line.split(",")(4),1))
      .reduceByKey(_+_)
      .foreach(println)

    //统计性别人数
    stuRDD
      .map(line => (line.split(",")(3),1))
      .reduceByKey(_+_)
      .foreach(println)

CheckPoint

  • 将RDD的数据保存到hdfs中,会切断RDD的依赖关系
  • 流程

1.当第一个job执行完成之后,会向前回溯,如果有RDD做了CheckPoint,会打上一个标记
2. 重新启动一个job任务计算RDD 的数据,将RDD的数据保存到hdfs

  • 优化

在CheckPoint之前可以先cache一下

//设置CheckPoint位置
  	sc.setCheckpointDir("spark/data/checkpoint")
    //读取分数表
    val studentRDD: RDD[String] = sc.textFile("spark/data/students.txt")

    val stuRDD: RDD[String] = studentRDD.map(line => {
      println("map...")//测试用,显示次数即为RDD的运行次数
      line
    })


    /**
      * checkpoint : 快照,  将rdd的数据持久化到hdfs, 数据不会丢失
      */
	//优化
    stuRDD.cache()
    stuRDD.checkpoint()

    // 统计i班级人数
    stuRDD
      .map(line => (line.split(",")(4), 1))
      .reduceByKey(_ + _)
      .foreach(println)

    // 统计性别人数
    stuRDD
      .map(line => (line.split(",")(3), 1))
      .reduceByKey(_ + _)
      .foreach(println)

    stuRDD
      .map(line => (line.split(",")(3), 1))
      .reduceByKey(_ + _)
      .foreach(println)

广播变量

  • 当在算子内使用到Driver端的一个变量的时候,这个变量会被封装到task中,变成一个变量副本发送到Executor中
  • 由于task的数量一般大于Executor的数量所有,会产生很多的变量副本,会降低任务执行的速度

使用广播变量

  1. 在Driver端定义一个广播变量
  2. 在算子中如果使用到广播变量,先去Executor中获取
  3. 如果Executor中没有这个广播变量,Executor会去Driver端获取广播变量
  4. 后续的task就可以直接使用
  • 使用广播变量之后变量副本 <= Executor数量
val student: RDD[String] = sc.textFile("students.txt")

    val list: List[String] = List("文科一班","文科二班","理科一班")

    //将集合广播
    val broadList: Broadcast[List[String]] = sc.broadcast(list)

    val result: RDD[String] = student.filter(line => {
      	val clazz: String = line.split(",")(4)
		//获取广播变量
      	val value: List[String] = broadList.value
      	value.contains(clazz)
    })
    result.foreach(println)

累加器

  • 在算子内部修改Driver端的一个变量是不会生效的,因为在算子里面的代码在Executor端运行,算子外面的代码在Driver端运行,属于不同的JVM
  • 累加器的使用
  1. 在Driver定义一个累加器
  2. 在Executor端进行累加
  3. 在Driver端读取累加结果
//定义累加器,默认值是0
    val acc: LongAccumulator = sc.longAccumulator

    rdd.foreach(i => {
      acc.add(1)
    })
    println(acc.value)

//不使用累加器
	var a: Int = 0
	rdd.foreach(i => {
		a+=1
	})
	println(a)

BlockManager

  • 在每一个Executor中都有一个块管理器,用来管理Executor中的数据
  • 管理范围
  1. RDD缓存的数据
  2. 广播变量和累加器
  3. shuffle文件

shuffle

  1. hashshuffle 小文件数 = map数 * reduce数
  2. hashshuffleManager 小文件数 = core数 * reduce数
  3. sortshuffle(默认) 小文件数 = 2 * map数
  4. sortshuffle bypass机制,在溢写的过程中不排序,当reduce的数量小于200的时候会触发

资源调度和任务调度

  • 当在代码中出现new SparkContext的时候开始资源调度
  • client模式
  1. Driver在本地启动
  2. 向RM申请启动AM
  3. AM启动后向RM申请资源
  4. RM分配资源启动Executor
  5. Executor启动之后反向注册给Driver

(AM ApplicationMaster RM ResourceManage)

  • cluster模式
  1. Driver和AM合二为一,功能合并
  2. AM启动后向RM申请资源
  3. RM分配资源启动Executor
  4. Executor启动之后反向注册给Driver
  • 任务调度
  1. 当代码中遇到Action算子的时候开始任务调度
  2. 构建DAG有向无环图
  3. DAGScheduler根据宽窄依赖切分stage
  4. DAGScheduler将stage以taskSet的形式发送给TaskScheduler
  5. TaskScheduler将task发送到Executor中执行(会尽量将task发送到数据所在的节点执行)
  6. Executor向TaskScheduler汇报任务执行情况
  • task失败之后TaskScheduler重试三次,如果因为shuffle file not found导致的异常,TaskSchedule不负责重试task,而是由DAGScheduler重试上一个stage
  • TaskSchedule重试三次还失败,由DAGScheduler重试stage