大数据面试--持续更新

hadoop

HDFS读数据流程

image-20230527200826866

1 首先是客户端向NameNode(是HDFS的话事人)发送下载文件请求。

2 NameNode收到请求之后,会做两个判断(1)判断该客户端是否有下载对应文件的权限(2)判断HDFS集群上是否有该文件。

3 判断通过后,NameNode向客户端相应元数据(存储块信息在哪)

4 客户端会创建一个读流(FSDataInputStream),去哪个节点读数据有两原则(1)节点最近(2)负载均衡,防止都读一个节点,结合以上两个原则选择节点读数据。

5 假设请求的文件被分为两个数据块,读请求是串行的,第一块读完之后,才读第二块进行追加。

HDFS写数据流程

image-20230527200905930

1 客户端向NameNode请求上传文件路径

2 NameNode收到请求之后,也会做两次判断(1)判断该客户是否有写权限,若没就直接拒绝(2)检查目标文件路径是否存在,若存在则写入失败。之后NN向客户端相应可以上传文件。

3 客户端向NN请求上传第一个Block(0-128M),请NN返回DataNode(NN要告诉客户端往哪个存)

4 NN向客户端返回DataNode信息会有如下原则(1)就近节点,优先本地(2)其他机架节点(3)其他机架另一个节点<优先级1->3>,NN向客户端返回dn1,dn2,dn3节点,表示采用这三个节点存储数据

5 客户端创建一个写流(FSDataOutputSteam),假若选择的dn1,会先在dn1,dn2,dn3之间建立通道,这样只需往dn1一个节点上写入即可,dn2和dn3的会从通道中取到同样的数据写入,增加了写入效率。

6 在通道中传输的最小单元叫packet(64k),在创建写流的时候,还会创建一个缓冲队列,该队列存储(chunk512byte+chunksum4byte<校验位>)当516byte攒够了64k会形成一个packet,放入缓冲队列中,然后从缓冲队列中在通道中传输。

7 当数据都写入完毕之后,各个节点会给上流节点应答成功。

8 写入流获得所有dn的ack之后,会告知NameNode我写完了,你更新一下元数据信息。

Yarn工作机制

image-20230527201006431

0 Mr程序提交到客户端所在的节点,通过job.waitForCompletion()来创建YarnRunner,本地模式下是localRunner。

1 yarnRunner向集群“老大”ResouceManager申请一个Application。

2 ResourceManager返回给yarnRunner所在节点一个”Application资源提交路径“(hdfs://…/)以及application_id

3 YarnRunner所在节点会放该hdfs路径上提交job所需的资源(1、job.split<控制开启多少个maptask> 2、Job.xml <配置文件>3、wc.jar<代码>)

4 资源提交完毕之后,该节点会向ResourceManager申请运行mrAppMaster(可以理解为是控制其他任务执行的老大)

5 ResourceManager将用户的请求初始化成一个Task,接着将该Task放入FIFO调度队列。

6 此时有一个比较空闲的NodeManager,它会主动领取ResourceManager调度队列中的任务。

7 领到任务之后,这个NodeManager会先创建容器(任何任务的执行都是在容器中执行的,容器中有对应的cpu、网络资源、磁盘io等资源)在容器中启动了一个MRAppmaster。

8 它会向集群资源的路径下载Job资源(job切片)。

9 拿到切片信息之后,会再次向ResourceManager申请,此时是申请运行MapTask容器,假若切片是两个,就会开启两个MapTask。

10 其他NodeManager领到任务之后,创建容器(这两个容器<cpu+ram+jar>有可能在一个NodeManager上也有可能在一个NodeManager上)

11 当以上两个容器拿到对应资源和jar之后,最初容器中的MRAppmaster向运行MapTask的容器发送MapTask程序启动脚本,此时每个MapTask对应的进程yarnchild,maptask任务的结果就是把job按照分区持久化到磁盘。

12 map阶段运行完之后,MRAppmaster会再次向ResourceManager申请两个容器,运行ReduceTask程序也是对应的yarnchild进程。

13 reduce向map获取相应的分区的数据。

14 reduce执行完毕之后,由MRAppmaster告知ResourceManager释放MapTask和ReduceTask以及MRAppmaster所占用的资源释放掉

谈谈你对 container 的理解

  • Container作为资源分配和调度的基本单位,其中封装了的资源如内存,CPU,磁盘,网络 带宽等。目前yarn仅仅封装内存和CPU
  • Container由ApplicationMaster向ResourceManager申请的,由ResouceManager中的资源调度器,异步分配给ApplicationMaster
  • Container的运行是由ApplicationMaster向资源所在的NodeManager发起的, Container运行时需提供内部执行的任务命令

MapReduce详细工作流程

image-20231025184853398

1

2

3

4 Mrappmaster最主要的是读取job切片信息,来确定开启几个MapTask任务(详细见yarn工作机制)。

5 TextInputReader默认是按行读取(RecorderReader),读取到MapTask中。

环形缓冲区(默认100M):一半存数据,一半存索引元数据。当环形缓冲区写到80%除,会进行反向写,留出的20%是为了让环形缓冲区的数据有足够的时间溢写,即将发生覆盖的时候会等待。

8 向环形缓冲区中写入数据的时候就会将数据标记分区,未来会分区1会进入reduce1,分区2会进入reduce2。到达80%进行溢写前对数据进行快速排序。由于数据是连续的,如果对数据进行排序的话移动元素太多,这时候只需要对修改索引kv起始位置。

9 产生了大量的溢写文件(每一个溢写文件都是环形缓冲区的80%的数据)。

10 将相同分区的溢写文件进行归并排序(保证每个分区内部是有序的),归并完之后会存储在磁盘上。

11 进行预聚合,将2个<a,1>传入reduce效率低于传入1个<a,2>。

image-20231025185009950

12 左下角Mrappmaster是整个程序的老大,当所有的MapTask任务完成之后,启动相应数量的ReduceTask,并告知ReduceTask处理数据范围(不同分区发送到不同的reduceTask)。

13 reduceTask主动从mapTask对应分区拉取数据,将拉取的数据再进行一次全局排序(也是归并排序),这样相同的key的数据就紧挨着,这样就可以把所有的<a,x>一次性全读到。

15 几乎不用

16 当reduce聚合完成的时候,由OutPutFormat核心组件的RecordWriter往外写。

NameNode工作机制

image-20231026082834895

NameNode

edit.001 日志文件,它以追加方式记录了每个编辑操作的详细信息,包括操作类型、文件路径、时间戳等。

fsimage 当NameNode启动时,它会读取fsimage文件,将其中的元数据信息加载到内存中,从而恢复文件系统的状态。

edits.001+fsimage(合在一起就是) 当NameNode启动时,它会读取fsimage文件,将其中的元数据信息加载到内存中,从而恢复文件系统的状态。随后,NameNode会读取编辑日志(Edit log),将其中的变更操作应用到内存中的元数据信息上,最终得到当前文件系统的状态。

1 第一次启动namenode会先进行格式化,创建 fsimage 和 edits 文件。如果不是第一次启动, 直接加载 编辑日志和镜像文件到内存。

2 客户端对元数据进行增删改的请求。

3 namenode先记录操作日志,更新滚动日志。(若先写内存,如果突然断电再次启动namenode时,不能恢复到断电前的一次操作)

4 然后再在内存中对数据进行增删改查。

Secondary NameNode

1 secondary namenode 询问 namenode 是否需要 checkpoint。直接带回 namenode 是否检查结果。

2 secondary namenode 请求执行 checkpoint。

3 namenode 滚动正在写的 edits 日志(理解为历史日志),这时候增删改请求都是写到edits_inprogress_002中。

4 将历史日志和镜像文件拷贝到 secondary namenode。

5 secondary namenode 加载历史日志和镜像文件到内存,并合并。

6 生成新的镜像文件 fsimage.chkpoint。

7 拷贝 fsimage.chkpoint 到 namenode。

8 namenode 将 fsimage.chkpoint 重新命名成 fsimage,覆盖原来的fsimage。

Shuffle机制

Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle。

image-20231025211219644

1 从map方法出来之后,会先进入getPartition方法中,标记数据是哪个分区,然后进入到环形缓冲区(见MapReduce详细工作流程)。

2 溢写之前排序,排序方法:快排,排序对象:key的索引,按照什么排:字典。

3 第一次溢写combiner可选(减少传输数据量)

4 将溢写文件根据分区进行归并

5 还可以进行combiner,之后也可以进行压缩。

6 写到磁盘。

7 reduce主动拉取自己指定的分区,拉来的数据先尝试放到内存,内存不够溢写到磁盘。

8 将内存和磁盘的数据拿过来进行一次大归并。

9 按照相同的key分组,然后进入reduce方法,reduce处理的数据是 key(v1,v2,v3…)这时候就可以对集合里的v1v2v3做加减乘除了。

压缩

image-20231026101927820

**提示:**如果面试过程问起,我们一般回答压缩方式为 Snappy,特点速度快,缺点无法切分(可以回答在链式 MR 中,Reduce 端输出使用 bzip2 压缩,以便后续的 map 任务对数 据进行 split)

切片机制

1 简单地按照文件的内容长度进行切片
2 切片大小,默认等于 Block 大小
3 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
提示:切片大小公式:max(0,min(Long_max,blockSize))

Hadoop优化

HDFS小文件影响

1 影响 NameNode 的寿命,因为文件元数据存储在 NameNode 的内存中。

2 影响计算引擎的任务数量,比如每个小的文件都会生成一个 Map 任务。

数据输入小文件处理

1 合并小文件:对小文件进行归档(Har)、自定义 Inputformat 将小文件存储成 SequenceFile 文件。

2 采用 ConbinFileInputFormat 来作为输入,解决输入端大量小文件场景。

3 对于大量小文件 Job,可以开启 JVM 重用。(每个小文件都需要启动一个独立的Java虚拟机(JVM),这会带来较大的开销。JVM重用是指在同一个作业中,多个任务(Task)共享同一个JVM实例。即使处理多个小文件,也只需要启动一个JVM,而不是为每个小文件都启动一个新的JVM。这样可以减少JVM的启动时间和资源开销,提高整体的作业执行效率。)

Map阶段

1 增大环形缓冲区大小。由 100m 扩大到 200m。

2 增大环形缓冲区溢写的比例。由 80%扩大到 90%:通过调整io.sort.mb及sort.spill.percent参数值,增大触发spill的内存上限,减少 spill(溢写)次数,从而减少磁盘 IO。。

3 减少对溢写文件的 merge 次数:通过调整io.sort.factor参数,增大merge的文件数目,减少merge的次数,从而缩短mr处理时间。。

4 不影响实际业务的前提下,采用 Combiner 提前合并,减少 I/O。

Reduce阶段优化

1 合理设置Map和Reduce数。都不能设置太少,也不能设置太多。太少,会导致Task等待,延长处理时间;太多,会导致 Map、Reduce任务间竞争资源,造成处理超时等错误。

2 设置Map、Reduce共存:调整slowstart.completedmaps参数,使Map运行到一定程度后,Reduce也开始运行,减少Reduce的等待时间。

3 规避使用Reduce,因为Reduce在用于连接数据集的时候将会产生大量的网络消耗。

4 增加每个Reduce去Map中拿数据的并行数

5 集群性能不错的前提下,增大Reduce端存储数据内存的大小。

IO传输优化

1 采用数据压缩的方式,减少网络IO的的时间。比如安装Snappy和LZOP压缩编码器。

2 使用SequenceFile二进制文件。

整体调优

1 MapTask默认内存大小为1G,可以增加MapTask内存大小为4-5G。

2 ReduceTask默认内存大小为1G,可以增加ReduceTask内存大小为4-5G。

3 可以增加MapTask的cpu核数,增加ReduceTask的CPU核数。

4 增加每个Container的CPU核数和内存大小。

5 调整每个Map Task和Reduce Task最大重试次数。

spark

hadoop 和 spark 区别

1 Hadoop 底层使用 MapReduce 计算架构,只有 map 和 reduce 两种操作,表达能力比较欠缺, 而且在 MR 过程中会重复的读写 hdfs,造成大量的磁盘 io 读写操作,所以适合高时延环境下批处理计算的应用。

2 Spark 是基于内存的分布式计算架构,提供更加丰富的数据集操作类型,主要分成转化操作和行动操作,包括 map、reduce、filter、flatmap、groupbykey、reducebykey、union 和 join 等,数据分析更加快速,所以适合低时延环境下计算的应用。

3 spark 与 hadoop 最大的区别在于迭代式计算模型。基于 mapreduce 框架的 Hadoop 主要分 为 map 和 reduce 两个阶段,两个阶段完了就结束了,所以在一个 job 里面能做的处理很有限;spark 计算模型是基于内存的迭代式计算模型,可以分为 n 个阶段,根据用户编写 的 RDD 算子和程序,在处理完一个阶段后可以继续往下处理很多个阶段,而不只是两个阶段。所以 spark 相较于 mapreduce,计算模型更加灵活,可以提供更强大的功能。但是 spark 也有劣势,由于 spark 基于内存进行计算,虽然开发容易,但是真正面对大数据的时候,在没有进行调优的情况下,可能会出现各种各样的问题,比如 OOM 内存溢出 等情况,导致 spark 程序可能无法运行起来,而 mapreduce 虽然运行缓慢,但是至少可以慢慢运行完。

spark解决了hadoop的哪些问题?

  • MR:抽象层次低,需要使用手工代码来完成程序编写,使用上难以上手;
  • Spark:Spark采用RDD计算模型,简单容易上手。
  • MR:只提供map和reduce两个操作,表达能力欠缺;
  • Spark:Spark采用更加丰富的算子模型,包括map、flatmap、groupbykey、reducebykey等;
  • MR:一个job只能包含map和reduce两个阶段,复杂的任务需要包含很多个job,这些job之间的管理以来需要开发者自己进行管理;
  • Spark:Spark中一个job可以包含多个转换操作,在调度时可以生成多个stage,而且如果多个map操作的分区不变,是可以放在同一个task里面去执行;
  • MR:中间结果存放在hdfs中;
  • Spark:Spark的中间结果一般存在内存中,只有当内存不够了,才会存入本地磁盘,而不是hdfs;
  • MR:只有等到所有的map task执行完毕后才能执行reduce task;
  • Spark:Spark中分区相同的转换构成流水线在一个task中执行,分区不同的需要进行shuffle操作,被划分成不同的stage需要等待前面的stage执行完才能执行。
  • MR:只适合batch批处理,时延高,对于交互式处理和实时处理支持不够;
  • Spark:Spark streaming可以将流拆成时间间隔的batch进行处理,实时计算。

hadoop 和 spark 的 shuffled 的区别

  • 从逻辑角度来讲,Shuffle 过程就是一个 GroupByKey 的过程,两者没有本质区别。只是 MapReduce 为了方便 GroupBy 存在于不同 partition 中的 key/value records,就提前对 key 进行排序。Spark 认为很多应用不需要对 key 排序,就默认没有在 GroupBy 的过程中对 key 排序。
  • 从数据流角度讲,两者有差别。MapReduce 只能从一个 Map Stage shuffle 数据,Spark 可以从多个 Map Stages shuffle 数据(这是 DAG 型数据流的优势,可以表达复杂的数据流操作。
  • Shuffle write/read 实现上有一些区别。以前对 shuffle write/read 的分类是 sort-based 和 hash-based。MapReduce 可以说是 sort-based,shuffle write 和 shuffle read 过程都是基于 key sorting 的 (buffering records + in-memory sort + on-disk external sorting)。早期的 Spark 是 hash-based,shuffle write 和 shuffle read 都使用 HashMap-like 的数据结构进行 aggregate (without key sorting)。但目前的 Spark 是两者的结合体,shuffle write 可以是 sort-based (only sort partition id, without key sorting),shuffle read 阶段可以是 hash-based。因此, 目前 sort-based 和 hash-based 已经“你中有我,我中有你”,界限已经不那么清晰。
  • 从实现角度来看,两者也有不少差别。 Hadoop MapReduce 将处理流程划分出明显的几个阶段:map(), spill, merge, shuffle, sort, reduce() 等。每个阶段各司其职,可以按照过程式的编程思想来逐一实现每个阶段的功能。在 Spark 中,没有这样功能明确的阶段, 只有不同的 stage 和一系列的transformation(),所以 spill, merge, aggregate 等操作需要蕴含在 transformation() 中。

Spark效率为什么高于mr

spark是借鉴了Mapreduce,并在其基础上发展起来的,继承了其分布式计算的优点并进行了改进,spark生态更为丰富,功能更为强大,性能更加适用范围广,mapreduce更简单,稳定性好。主要区别

(1)spark把运算的中间数据(shuffle阶段产生的数据)存放在内存,迭代计算效率更高,mapreduce的中间结果需要落盘

(2)Spark容错性高,它通过弹性分布式数据集RDD来实现高效容错,RDD是一组分布式的存储在节点内存中的只读性的数据集,这些集合是弹性的,某一部分丢失或者出错,可以通过整个数据集的计算流程的血缘关系来实现重建,mapreduce的容错只能重新计算

(3)Spark更通用,提供了transformation和action这两大类的算子,另外还有流式处理sparkstreaming模块、图计算等等,mapreduce只提供了map和reduce两种操作,流计算及其他的模块支持比较缺乏

(4)Spark框架和生态更为复杂,有RDD,血缘lineage、执行时的有向无环图DAG,stage划分等,很多时候spark作业都需要根据不同业务场景的需要进行调优以达到性能要求,mapreduce框架及其生态相对较为简单,对性能的要求也相对较弱,运行较为稳定,适合长期后台运行。

(5)Spark计算框架对内存的利用和运行的并行度比mapreduce高,Spark运行容器为executor,内部ThreadPool中线程运行一个Task,mapreduce在线程内部运行container,container容器分类为MapTask和ReduceTask.程序运行并行度高

(6)Spark对于executor的优化,在JVM虚拟机的基础上对内存弹性利用:storage memory与Execution memory的弹性扩容,使得内存利用效率更高

spark有几种部署方式

  1. Local:运行在一台机器上,通常是练手或者测试环境
  2. Standalone:构建一个基于 Master+Slaves 的资源调度集群,Spark 任务提交给 Master 运行。是 Spark 自身的一个调度系统
  3. Yarn: Spark 客户端直接连接 Yarn,不需要额外构建 Spark 集群。有 yarn-client 和 ya rn-cluster 两种模式,主要区别在于:Driver 程序的运行节点
  4. Mesos:国内大环境比较少用

spark提交任务参数

在提交任务时的几个重要参数

  • executor-cores —— 每个 executor 使用的内核数,默认为 1,官方建议 2-5 个
  • num-executors —— 启动 executors 的数量,默认为 2
  • executor-memory —— executor 内存大小,默认 1G
  • driver-cores —— driver 使用内核数,默认为 1
  • driver-memory —— driver 内存大小,默认 512M

提交任务的样式:

bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \      
--deploy-mode cluster \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10

简述 Spark 的架构与作业提交流程

==注:==结合yarn工作机制去理解。

Yarn Cluster 模式:

image-20231105142028437

Cluster 模式将用于监控和调度的 Driver 模块启动在 Yarn 集群资源中执行。一般应 用于实际生产环境。

  1. 在 YARN Cluster 模式下,任务提交后会和 ResourceManager 通讯申请启动 ApplicationMaster,
  2. 随后 ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster,此时的 ApplicationMaster 就是 Driver。
  3. Driver 启动后向 ResourceManager 申请 Executor 内存,ResourceManager 接到 ApplicationMaster 的资源申请后会分配 container,然后在合适的 NodeManager 上启动 Executor 进程
  4. Executor 进程启动后会向 Driver 反向注册,Executor 全部注册完成后 Driver 开始执行 main 函数,
  5. 然后在Executor进程中创建Executor计算对象
  6. 之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每 个 stage 生成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。

Yarn Client 模式:

image-20231105134024423
  1. Client 模式将用于监控和调度的 Driver 模块在客户端执行,而不是 Yarn 中,所以一般用于测试
  2. Driver 在任务提交的本地机器上运行
  3. Driver 启动后会和 ResourceManager 通讯申请启动 ApplicationMaster
  4. ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster(ExecutorLauncher),负责向 ResourceManager 申请 Executor 内存
  5. ResourceManager 接到 ApplicationMaster 的资源申请后会分配 container,然后 ApplicationMaster 在资源分配指定的 NodeManager 上启动 Executor 进程
  6. Executor 进程启动后会向 Driver 反向注册,Executor 全部注册完成后 Driver 开始执行 main 函数
  7. 然后在Executor进程中创建Executor计算对象
  8. 之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行

Spark三大数据结构(RDD、累加器、广播变量)

RDD

Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于处理不同的应用场景。三大数据结构分别是:
➢ RDD : 弹性分布式数据集
➢ 累加器:分布式共享只写变量
➢ 广播变量:分布式共享只读变量

1、什么是 RDD

image-20230528155913531

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。

➢ 弹性
⚫ 存储的弹性:内存与磁盘的自动切换;
⚫ 容错的弹性:数据丢失可以自动恢复;
⚫ 计算的弹性:计算出错重试机制;
⚫ 分片的弹性:可根据需要重新分片。
➢ 分布式:数据存储在大数据集群不同节点上
➢ 数据集:RDD 封装了计算逻辑,并不保存数据
➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现
➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在
新的 RDD 里面封装计算逻辑
➢ 可分区、并行计算

作用:

提供了一个抽象的数据模型,将具体的应用逻辑表达为一系列转换操作(函数)。另外不同RDD之间的转换操作之间还可以形成依赖关系,进而实现管道化,从而避免了中间结果的存储,大大降低了数据复制、磁盘IO和序列化开销,并且还提供了更多的API(map/reduec/filter/groupBy…),RDD在Lineage依赖方面分为两种Narrow Dependencies与Wide Dependencies,用来解决数据容错时的高效性以及划分任务时候起到重要作用。

2、如何创建RDD

  • 从集合(内存)中创建 RDD
    从集合中创建 RDD,Spark 主要提供了两个方法:parallelize 和 makeRDD,从底层代码实现来讲,makeRDD 方法其实就是 parallelize 方法

    val rdd1 = sparkContext.parallelize(
    	List(1,2,3,4)
    )
    val rdd2 = sparkContext.makeRDD(
    	List(1,2,3,4)
    )
    
  • 从外部存储(文件)创建 RDD
    由外部存储系统的数据集创建 RDD 包括:本地的文件系统,所有 Hadoop 支持的数据集,比如 HDFS、HBase 等。

    val fileRDE: RDD[String] = sparkContext.textFile("input")
    
  • 从其他 RDD 创建
    主要是通过一个 RDD 运算完后,再产生新的 RDD。

  • 直接创建 RDD(new)
    使用 new 的方式直接构造 RDD,一般由 Spark 框架自身使用。

(共享变量)累加器、广播变量

累加器:

累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在 Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后, 传回 Driver 端进行 merge。

广播变量:

广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个 或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表, 广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务 分别发送。

RDD机制

1 rdd分布式弹性数据集,简单的理解成一种数据结构,是spark框架上的通用货币。所有算子都是基于rdd来执行的,不同的场景会有不同的rdd实现类,但是都可以进行互相转换。rdd执行过程中会形成dag图,然后形成lineage保证容错性等。从物理的角度来看rdd存储的是block和node之间的映射。

2 RDD是spark提供的核心抽象,全称为弹性分布式数据集。

3 RDD在逻辑上是一个hdfs文件,在抽象上是一种元素集合,包含了数据。它是被分区的,分为多个分区,每个分区分布在集群中的不同结点上,从而让RDD中的数据可以被并行操作(分布式数据集)

4 比如有个RDD有90W数据,3个partition,则每个分区上有30W数据。RDD通常通过Hadoop上的文件,即HDFS或者HIVE表来创建,还可以通过应用程序中的集合来创建;RDD最重要的特性就是容错性,可以自动从节点失败中恢复过来。即如果某个结点上的RDD partition因为节点故障,导致数据丢失,那么RDD可以通过自己的数据来源重新计算该partition。这一切对使用者都是透明的。

5 RDD的数据默认存放在内存中,但是当内存资源不足时,spark会自动将RDD数据写入磁盘。比如某结点内存只能处理20W数据,那么这20W数据就会放入内存中计算,剩下10W放到磁盘中。RDD的弹性体现在于RDD上自动进行内存和磁盘之间权衡和切换的机制。

spark工作机制?

用户在client端提交作业后,会由Driver运行main方法并创建spark context上下文(负责作业的全局调度)。执行算子,形成dag图交给dag scheduler,按照依赖关系划分stage输入task scheduler(负责分发任务)。WorkerNode上的Executor会主动向Task Scheduler申请任务,Task Scheduler根据申请情况将分发给Worker Node,让Executor(驻留在Worker Node中)派一个线程去执行。(到底分发给谁,是有一个基本原则:计算向数据靠拢,发现数据在机器A上,就把计算程序扔给机器A)任务在Executor运行得到结果再反馈给Task Scheduler再由它传递给DAG Scheduler然后交由SparkContext做最后处理,如返回给用户、写入HDFS等。

**SparkContext:**向资源管理器(yarn等)申请资源,并把作业分解成不同阶段,把每个阶段的任务调度到不同节点上去执行,会向executor分配资源如cpu内存,分配好资源之后启动Executor进程,

spark有哪些组件?

  • master:管理集群和节点,不参与计算。
  • worker:计算节点,进程本身不参与计算,和master汇报。
  • Driver:运行程序的main方法,创建spark context对象。
  • spark context:控制整个application的生命周期,包括dagsheduler和task scheduler等组件。
  • client:用户提交程序的入口。

持久化

231312


RDD 通过 Cache 或者 Persist 方法将前面的计算结果缓存,默认情况下会把数据以缓存在 JVM 的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的 action 算子时,该 RDD 将会被缓存在计算节点的内存中,并供后面重用。

mapRDD.cache()或者mapRDD.persist(),cache其实就是调用的无参persist默认StorageLevel.MEMORY_ONLY是持久化到内存(JVM的堆内存中),想持久化到磁盘就mapRDD.persist(StorageLevel.DISK_ONLY)

缓存有可能丢失,存储于内存的数据由于内存不足而被删除,RDD 的缓存容错机制(cache 操作会增加血缘关系,不改变原有的血缘关系)保证了即使缓存丢失也能保证计算的正确执行。通过基于 RDD 的一系列转换,丢失的数据会被重算,由于 RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部 Partition。

Spark 会自动对一些 Shuffle 操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用 persist 或 cache。

image-20230528143650701

注意:持久化操作是在行动算子执行时完成的。

checkpoint

所谓的检查点其实就是通过将 RDD 中间结果写入磁盘,当spark应用程序特别复杂,从初始的RDD开始到最后整个应用程序完成有很多的步骤,而且整个应用运行时间特别长,这种情况下就比较适合使用checkpoint功能。如果检查点之后有节点出现问题,可以从检查点开始重做血缘(等同于改变数据源),减少了开销。对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。

持久化与CheckPoint 比较

1)Cache 缓存只是将数据保存起来,不切断血缘依赖。Checkpoint 检查点切断血缘依赖。
2)Cache 缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint 的数据通常存储在 HDFS 等容错、高可用的文件系统,可靠性高。
3)建议对 checkpoint()的 RDD 使用 Cache 缓存,这样 checkpoint 的 job 只需从 Cache 缓存中读取数据即可,否则需要再从头计算一次 RDD。因为checkpoint是单独计算的,会再执行一遍作为checkpoint的检查点结果。(之后在RDD所处的job运行结束之后,会启动一个单独的job,来将checkpoint过的RDD数据写入之前设置的文件系统,进行高可用、容错的类持久化操作。)

持久化和CheckPoint的应用场景

cache: 对于会被重复使用,但是数据量不是太大的 RDD,可以将其 cache()到内存当中。 cache 是每计算出一个要 cache 的 partition 就直接将其 cache 到内存中。缓存完之后,可以 在任务监控界面 storage 里面看到缓存的数据。

**checkpoint:**对于 computing chain 计算链过长或依赖其他 RDD 很多的 RDD,就需要进行 checkpoint,将其放入到 HDFS 或者本地文件夹当中。需要注意的是,checkpoint 需要等到 job 完成了,再启动专门的 job 去完成 checkpoint 操作,因此 RDD 是被计算了两次的。一般使用的时候配合 rdd.cache(),这样第二次就不用重新计算 RDD 了,直接读取 cache 写磁盘。

Spark Shuffle

图示

…原理

依赖

image-20230528135833925

Task和分区的关系。

1班(分区1)的1、2进入new不需要等2班(分区2),故没有阶段这一概念。

image-20230528140811163阶段1每个分区都是完成状态才可以进入下一个阶段。

image-20230528141017126

OneToOne(窄依赖)

image-20230527203221994

shuffle依赖(宽依赖)

其实是没有宽依赖的,不是窄就是宽。

image-20230528135854538

如何划分state

遇到一个宽依赖就划分一个state

spark排除故障

1、故障一:控制 reduce 端缓冲大小以避免 OOM

在Shuffle过程,reduce端task并不是等到map端task将其数据全部写入磁盘后再去拉取,而 是map端写一点数据,reduce端task就会拉取一小部分数据,然后立即进行后面的聚合、算子 函数的使用等操作reduce端task能够拉取多少数据,由reduce拉取数据的缓冲区buffer来决定, 因为拉取过来的数据都是先放在buffer中,然后再进行后续的处理,buffer的默认大小为 48MB

reduce端task会一边拉取一边计算,不一定每次都会拉满48MB的数据,可能大多数时候拉取一部分数据就处理掉了

虽然说增大reduce端缓冲区大小可以减少拉取次数,提升Shuffle性能,但是有时map端的数据量非常大,写出的速度非常快,此时reduce端的所有task在拉取的时候,有可能全部达到自己缓冲的最大极限值,即48MB,此时,再加上reduce端执行的聚合函数的代码,可能会创建大量的对象,这可难会导致内存溢出,即OOM 如果一旦出现reduce端内存溢出的问题,我们可以考虑减小reduce端拉取数据缓冲区的大小, 例如减少为12MB

在实际生产环境中是出现过这种问题的,这是典型的以性能换执行的原理。reduce端拉取数据 的缓冲区减小,不容易导致OOM,但是相应的,reudce端的拉取次数增加,造成更多的网络 传输开销,造成性能的下降

注意,要保证任务能够运行,再考虑性能的优化

2、故障二:JVM GC 导致的 shuffle 文件拉取失败

在 Spark 作业中,有时会出现 shuffle file not found 的错误,这是非常常见的一个报错,有 时出现这种错误以后,选择重新执行一遍,就不再报出这种错误

出现上述问题可能的原因是 Shuffle 操作中,后面 stage 的 task 想要去上一个 stage 的 task 所在的 Executor 拉取数据,结果对方正在执行 GC,执行 GC 会导致 Executor 内所有的工作 现场全部停止,比如 BlockManager、基于 netty 的网络通信等,这就会导致后面的 task 拉取 数据拉取了半天都没有拉取到,就会报出 shuffle file not found 的错误,而第二次再次执行 就不会再出现这种错误

可以通过调整 reduce 端拉取数据重试次数和 reduce 端拉取数据时间间隔这两个参数来对 Sh uffle 性能进行调整,增大参数值,使得 reduce 端拉取数据的重试次数增加,并且每次失败后 等待的时间间隔加长

JVM GC 导致的 shuffle 文件拉取失败,设置如下参数:

val conf = new SparkConf()
    .set("spark.shuffle.io.maxRetries","60")
    .set("spark.shuffle.io.retryWait","60")

3、故障三:解决各种序列化导致的报错

当 Spark 作业在运行过程中报错,而且报错信息中含有 Serializable 等类似词汇,那么可能是 序列化问题导致的报错。

序列化问题要注意以下三点:

  1. 作为 RDD 的元素类型的自定义类,必须是可以序列化的;
  2. 算子函数里可以使用的外部的自定义变量&
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值