深入理解spark

本文详细介绍了Spark的架构,包括RDD、宽依赖与窄依赖、shuffle机制、广播变量和累加器的使用,以及数据倾斜的处理方法。深入探讨了Spark的内存管理,特别是Executor内存的分配与管理,并提供了基础调优和算子调优的策略,如资源参数设置、使用缓存与checkpoint、优化shuffle操作等,旨在帮助读者深入理解Spark并提升性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 结构

分区,只是将数据分区,一个分区对应一个task。

  • Driver
    Spark 驱动器节点,用于执行 Spark 任务中的 main 方法,负责实际代码的执行工作。
    Driver 在 Spark 作业执行时主要负责:
    ➢ 将用户程序转化为作业(job)
    ➢ 在 Executor 之间调度任务(task)
    ➢ 跟踪 Executor 的执行情况
    ➢ 通过 UI 展示查询运行情况
  • Executor 是集群中工作节点(Worker)中的一个 JVM 进程,负责在 Spark 作业中运行具体任务(Task),任务彼此之间相互独立。Spark 应用(Application)启动时,Executor 节点被同时启动,并且始终伴随着整个 Spark 应用(Application)的生命周期而存在。如果有 Executor 节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他 Executor 节点上继续运行。
    Executor 有两个核心功能:
    ➢ 负责运行组成 Spark 应用(Application)的任务,并将结果返回给驱动器进程(Driver)
    ➢ 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存数据加速运算

在这里插入图片描述
一个action对应一个job,一个job多个stage,划分stage的边界是不同节点取数据,到shuffle结束,一个stage对应多个task,task由dirver发送到executor,task运行在executor中,exector运行在worker node上,exector由dirver在worker node上申请资源创建,application包括driver program和executors。

2. 四种部署模式

  • 1)本地模式
    Spark不一定非要跑在hadoop集群,可以在本地,起多个线程的方式来指定。将Spark应用以多线程的方式直接运行在本地,一般都是为了方便调试,本地模式分三类
    local:只启动一个executor
    local[k]:启动k个executor
    local[*]:启动跟cpu数目相同的 executor
  • 2)standalone模式
    分布式部署集群,自带完整的服务,资源管理和任务监控是Spark自己监控,这个模式也是其他模式的基础。
  • 3)Spark on yarn模式
    分布式部署集群,资源和任务监控交给yarn管理,但是目前仅支持粗粒度资源分配方式,包含cluster和client运行模式,cluster适合生产,driver运行在集群子节点,具有容错功能,client适合调试,dirver运行在客户端。
  • 4)Spark On Mesos模式。
    官方推荐这种模式(当然,原因之一是血缘关系)。正是由于Spark开发之初就考虑到支持Mesos,因此,目前而言,Spark运行在Mesos上会比运行YARN上更加灵活,更加自然。用户可选择两种调度模式之一运行自己的应用程序:
    (1)粗粒度模式(Coarse-grained Mode):每个应用程序的运行环境由一个Dirver和若干个Executor组成,其中,每个Executor占用若干资源,内部可运行多个Task(对应多少个“slot”)。应用程序的各个任务正式运行之前,需要将运行环境中的资源全部申请好,且运行过程中要一直占用这些资源,即使不用,最后程序运行结束后,回收这些资源。
    (2)细粒度模式(Fine-grained Mode):鉴于粗粒度模式会造成大量资源浪费,Spark OnMesos还提供了另外一种调度模式:细粒度模式,这种模式类似于现在的云计算,思想是按需分配。

3. RDD(重要)

3.1. RDD概念

rdd是一个弹性的分布式数据集(在代码中,是一个抽象类)
弹性的分布式的数据集

  • 弹性:在一定范围内进行变化不影响整体的情况
  • rdd详细介绍
    在这里插入图片描述

park RDD是什么?RDD特征介绍

3.5 RDD序列化

在这里插入图片描述

4. spark宽依赖窄依赖(dag划分)

  • 窄依赖(narrow dependency)
    相当于map操作
    父级RDD里的每个partition都对应子级RDD里的唯一一个partition的依赖关系

  • 宽依赖(shuffle dependency)
    相当于reduce操作
    父级RDD里的每个partition都对应子级RDD里的多个partition的依赖关系
    宽依赖分区会有所变化,所以一定执行shuffle操作,必执行磁盘IO操作
    在这里插入图片描述
    Stage划分基于数据依赖关系的,一般分为两类:宽依赖(Shuffle Dependency)与窄依赖(Narrow Dependency)。

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

窄依赖,父RDD的分区最多只会被子RDD的一个分区使用。

区分宽窄依赖,主要从父RDD的Partition流向来看:流向单个RDD就是窄依赖,流向多个RDD就是宽依赖。

Spark Stage划分,就是从最后一个RDD往前推算,遇到窄依赖(NarrowDependency)就将其加入该Stage,当遇到宽依赖(ShuffleDependency)则断开。每个Stage里task的数量由Stage最后一个RDD中的分区数决定。如果Stage要生成Result,则该Stage里的Task都是ResultTask,否则是ShuffleMapTask。

ShuffleMapTask的计算结果需要shuffle到下一个Stage,其本质上相当于MapReduce中的mapper。Result Task则相当于MapReduce中的reducer。因此整个计算过程会根据数据依赖关系自后向前建立,遇到宽依赖则形成新的Stage。

Stage的调度是由DAG Scheduler完成的。由RDD的有向无环图DAG切分出了Stage的有向无环图DAG。Stage以最后执行的Stage为根进行广度优先遍历,遍历到最开始执行的Stage执行,如果提交的Stage仍有未完成的父Stage,则Stage需要等待其父Stage执行完才能执行。

在这里插入图片描述
分解task的工作是由sc来执行的,不过sc存在于driver中,所以我们也可以理解为是由driver来执行的

container是一个虚拟的资源,是用来执行任务的。
1个Executor可以有多个线程,同一时间内,一个线程只能执行一个task

  • spark任务执行大致可以分为三个阶段
  1. 任务切割(分配)
  2. 资源分配
  3. 任务执行

5. shuffle机制

在这里插入图片描述
减少数据落盘,就会提高效率,算子若存在预聚合功能,那么就可以提高shuffle的性能

在Spark 1.2以前,默认的shuffle计算引擎是HashShuffleManager。该ShuffleManager而HashShuffleManager有着一个非常严重的弊端,就是会产生大量的中间磁盘文件,进而由大量的磁盘IO操作影响了性能。因此在Spark 1.2以后的版本中,默认的ShuffleManager改成了SortShuffleManager。

5.1. Hash shuffle

5.1.1. 普通机制

每一个task都会产生reduce端分区数量的小文件,影响性能。
小文件数量 = task数量 * reduce分区数量
在这里插入图片描述

5.1.2. 合并机制

一个Executor中的所有task共用文件
小文件数量 = Excutor数量 * reduce分区数量
在这里插入图片描述

5.2. Sort shuffle

为了尽可能避免小文件过多,sort shuffle采用的机制是
一个文件 + 一个索引文件
在这里插入图片描述

5.2.1. 普通机制

在上图的基础上,还有一个缓冲区
在这里插入图片描述

5.2.2. bypasss机制

在这里插入图片描述

  • bypass运行机制的触发条件如下:
    1)shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。
    2)不是聚合类的shuffle算子(比如reduceByKey)。
  • 与普通机制的区别:
    第一,磁盘写机制不同;
    第二,不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。

6. 广播变量和累加器

6.1. 广播变量

广播变量的定义:

广播变量可以让程序高效地向所有工作节点发送一个较大的只读值,以供一个或多个spark操作使用,在机器学习中非常有用。若不用广播变量,那么数据就会向每一个task发送一个,若使用广播变量,那就只会给Executor发送一个。广播变量是类型为spark.broadcast.Broadcast[T]的一个对象,其中存放着类型为T的值。它由运行SparkContext的驱动程序创建后发送给会参与计算的节点,非驱动程序所在节点(即工作节点)访问改变量的方法是调用该变量的value方法,这个值只会被发送到各节点一次,作为只读值处理。

广播变量的使用场景:

如果我们要在分布式计算里面分发大对象,例如字典、集合、黑白名单等,这个都会由Driver端进行分发,一般来讲,如果这个变量不是广播变量,那么每个task就会分发一份,这在task数目十分多的情况下Driver的带宽会成为系统的瓶颈,而且会大量消耗task服务器上的资源,如果将这个变量声明为广播变量,那么这时每个executor拥有一份,这个executor启动的task会共享这个变量,节省了通信的成本和服务器的资源。

6.2. 累加器

累加器的定义:

spark累加器就是定义在驱动程序的一个变量,但是在集群中的每一个任务都会有一份新的副本。在各个任务中更新副本的值都不会对驱动程序中的值产生影响,只有到最后所有的任务都计算完成后才会合并每一个任务的副本到驱动程序。

累加器的使用场景:

异常监控,调试,记录符合某特性的数据的数目等。如果一个变量不被声明为一个累加器,那么它将在被改变时不会再driver端进行全局汇总,即在分布式运行时每个task运行的只是原始变量的一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分布式计数的功能。

7. spark数据倾斜

数据倾斜的原因
在进行 shuffle 的时候,必须将各个节点上相同的 key 拉取到某个节点上的一个 task 来进行处理,比如按照 key 进行聚合或 join 等操作。此时如果某个 key 对应的数据量特别大的话,就会发生数据倾斜。比如大部分 key 对应10条数据,但是个别 key 却对应了100万条数据,那么大部分 task 可能就只会分配到10条数据,然后1秒钟就运行完了;但是个别 task 可能分配到了100万数据,要运行一两个小时。
因此出现数据倾斜的时候,Spark 作业看起来会运行得非常缓慢,甚至可能因为某个 task 处理的数据量过大导致内存溢出。

优化思路
思路1. 过滤异常数据
思路2. 提高 shuffle 并行度
思路3. 自定义 Partitioner
思路4. Reduce 端 Join 转化为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若能绽放光丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值