Spark分布式计算原理

一、RDD特征

1、Lineage:血统、遗传

  • RDD最重要的特性之一,保存了RDD的依赖关系
  • RDD实现了基于Lineage的容错机制

2、依赖关系

宽依赖
一个父RDD的分区被子RDD的多个分区使用

窄依赖
一个父RDD的分区被子RDD的一个分区使用

宽依赖对比窄依赖

  • 宽依赖对应shuffle操作,需要在运行时将同一个父RDD的分区传入到不同的子RDD分区中,不同的分区可能位于不同的节点,就可能涉及多个节点间数据传输
  • 当RDD分区丢失时,Spark会对数据进行重新计算,对于窄依赖只需重新计算一次子RDD的父RDD分区
  • 结论:相比于宽依赖,窄依赖对优化更有利

算子依赖

  • map(窄)
  • flatMap(窄)
  • filter(窄)
  • distinct(宽)
  • reduceByKey(宽)
  • groupByKey(宽)
  • sortByKey(宽)
  • union(窄)
  • join(宽)

二、DAG(有向无环图)工作原理

1、根据RDD之间的依赖关系,形成一个DAG
2、DAGScheduler将DAG划分为多个Stage

  • 划分依据:是否发生宽依赖(Shuffle)
  • 划分规则:从后往前,遇到宽依赖切割为新的Stage
  • 每个Stage由一组并行的Task组成

在这里插入图片描述
3、为什么划分Stage

(1)数据本地化

  • 移动计算,而不移动数据
  • 保证一个 Stage 内不会发生数据移动

(2)优化

  • 尽量避免 Shuffle
  • 提前部分聚合减少数据移动

三、Spark Shuffle过程

在分区之间重新分配数据

  • 父RDD中同一分区中的数据按照算子要求重新进入子RDD的不同分区中
  • 中间结果写入磁盘
  • 由子RDD拉取数据,而不是由父RDD推送
  • 默认情况下,Shuffle不会改变分区数量

Shuffle过程

四、RDD持久化

1、RDD缓存机制

  • 缓存是在计算结束后,直接将计算结果通过用户定义的存储级别(存储级别定义了缓存存储的介质,现在支持内存、本地文件系统和Tachyon)写入不同的介质
  • 缓存数据至内存/磁盘,可大幅度提升Spark应用性能
  • cache()或persist()后不能再有其他算子
  • cache()或persist()遇到Action算子完成后才生效
    val conf = new SparkConf().setMaster("local[*]").setAppName("CacheDemo")
    val sc = new SparkContext(conf)
    val rdd = sc.textFile("in/users.csv")
    rdd.cache()
    var start = System.currentTimeMillis()
    rdd.count()
    var end = System.currentTimeMillis()
    println("缓存时count时间:"+(end - start))

    start = System.currentTimeMillis()
    rdd.count()
    end = System.currentTimeMillis()
    println("缓存完成后count时间:"+(end - start))
    //缓存时count时间:627
    //缓存完成后count时间:34

2、缓存策略

(1)数据持久缓存到内存中

  • data.cache()

(2)设置缓存级别(StorageLevel

  • data.persist(StorageLevel.MEMORY_ONLY)

缓存级别

缓存级别 使用空间 CPU时间 是否在内存中 是否在磁盘上 备注
MEMORY_ONLY 默认为该级别
MEMORY_ONLY_2 数据存2份
MEMORY_ONLY_SER 数据序列化
MEMORY_ONLY_SER_2 数据序列化,数据存2份
MEMORY_AND_DISK 中等 部分 部分 如果数据在内存中放不下,则溢写到磁盘
MEMORY_AND_DISK_2 中等 部分 部分 数据存2份
MEMORY_AND_DISK_SER 部分 部分
MEMORY_AND_DISK_SER_2 部分 部分 数据存2份
DISK_ONLY
DISK_ONLY_2 数据存2份
NONE
OFF_HEAP

(3)清除缓存

  • data.unpersist

3、缓存应用场景

  • 从文件加载数据之后,因为重新获取文件成本较高
  • 经过较多的算子变换之后,重新计算成本较高
  • 单个非常消耗资源的算子之后

4、检查点

  • 检查点是在计算完成后,重新建立一个Job来计算
  • 为了避免重复计算,推荐先将RDD缓存,这样就能保证检查点的操作可以快速完成
  • 检查点会删除 RDD Lineage,而缓存不会
  • SparkContext 被销毁后,检查点数据不会被删除

设置检查点

	//设置检查点路径,可设置在hdfs、本地上
	sc.setCheckpointDir("in/check")
	rdd.checkpoint()

五、RDD共享变量

1、广播变量

允许开发者将一个只读变量(Driver端)缓存到每个节点(Executor)上,而不是每个任务传递一个副本

使用

val broadcastVar=sc.broadcast(Array(1,2,3))  //定义广播变量
broadcastVar.value 		//访问方式

2、累加器

只允许added操作,常用于实现计数

使用

val accum = sc.accumulator(0,"My Accumulator")
sc.parallelize(Array(1,2,3,4)).foreach(x=>accum+=x)
accum.value

六、RDD分区设计

1、分区大小限制为2G

2、分区太少

  • 不利于并发
  • 更容易受数据倾斜影响
  • groupBy, reduceByKey, sortByKey等内存压力增大

3、分区过多

  • Shuffle开销越大
  • 创建任务开销越大

4、经验

  • 每个分区大小建议为128MB
  • 如果分区小于但接近2000,则设置为大于2000

5、数据倾斜

指分区中的数据分配不均匀,数据集中在少数分区中

  • 严重影响性能
  • 通常发生在groupBy,join等之后

在这里插入图片描述
解决方案
使用新的Hash值(如对key加盐)重新分区

猜你喜欢

转载自blog.csdn.net/qq_42578036/article/details/109647993