说明
本章主要继续对RDD进行介绍,主要内容有缓存机制,checkpoint机制,spark的运行流程,spark的shuffle过程,spark并行度等部分。
RDD的缓存和checkpoint机制
明确:缓存和checkpoint的作用都是在计算过程中增加容错率,减少时间成本,提高工作效率。
解释:rdd在计算时会有多个依赖,为了避免计算错误时从头开始计算,可以将中间依赖进行缓存或checkpoint,缓存或checkpoint也叫作rdd的持久化
,一般对某个计算特别复杂的rdd进行持久化 。
缓存
缓存是将数据存储在内存或者磁盘上,计算结束后缓存自动清空
缓存级别
概念:指定缓存的数据位置,默认是缓存到内存上。
StorageLevel.DISK_ONLY # 将数据缓存到磁盘上
StorageLevel.DISK_ONLY_2 # 将数据缓存到磁盘上 保存两份
StorageLevel.DISK_ONLY_3 # 将数据缓存到磁盘上 保存三份
StorageLevel.MEMORY_ONLY # 将数据缓存到内存 默认
StorageLevel.MEMORY_ONLY_2 # 将数据缓存到内存 保存两份
StorageLevel.MEMORY_AND_DISK # 将数据缓存到内存和磁盘 优先将数据缓存到内存上,内存不足可以缓存到磁盘
StorageLevel.MEMORY_AND_DISK_2 # 将数据缓存到内存和磁盘
StorageLevel.OFF_HEAP # 基本不使用 缓存在系统管理的内存上 jvm(内存)在系统上运行,系统内存
StorageLevel.MEMORY_AND_DISK_ESER # 将数据缓存到内存和磁盘 序列化操作,按照二进制存储,节省空间
缓存的使用
from pyspark import SparkContext
#导入缓存级别
from pyspark.storagelevel import StorageLevel
sc = SparkContext(appName='cache')
#获取文件数据转化为rdd
rdd = sc.textFile('hdfs://node1:8020/data/words.txt')
# 对字符串数据先进行切割
rdd_split = rdd.flatMap(lambda x: x.split(','))
# 将数据转化为k-v结构
rdd_kv = rdd_split.map(lambda x: (x, 1))
# 对kv数据分组后累加计算
rdd_reduce = rdd_kv.reduceByKey(lambda a, b: a + b)
# 分组计算进行缓存
# 默认级别设置 storageLevel=StorageLevel.MEMORY_ONLY
rdd_reduce.persist(storageLevel=StorageLevel.MEMORY_ONLY_2)
# 排序
rdd_sort = rdd_reduce.sortBy(lambda x: x[1], ascending=False)
# 查看数据
# 数据缓存需要使用action算子触发
res = rdd_sort.collect()
print(res)
# 后面使用rdd_reduce时,会从缓存处获取数据处理
rdd_sort2 = rdd_reduce.sortBy(lambda x:x[1])
res = rdd_sort2.collect()
print(res)
# 释放缓存,手动释放(了解)
# 实际开发中不需要手动释放,程序结束会自动是释放
rdd_reduce.unpersist()
rdd_sort3 = rdd_reduce.sortBy(lambda x:x[1])
res = rdd_sort3.collect()
print(res)
Checkpoint
将中间rdd数据存储起来,但是存储的位置是在分布式存储系统,可以进行永久保存,程序结束不会释放。如果需要删除就在hdfs上删除对应的目录文件。
checkpoint的使用
from pyspark import SparkContext
sc = SparkContext(appName='checkpoint')
# todo:指定checkpoint保存的路径
sc.setCheckpointDir('hdfs://node1:8020/checkpointdata')
# 获取文件数据转化rdd
rdd = sc.textFile('hdfs://node1:8020/data/words.txt')
# 对字符串数据先进行切割
rdd_split = rdd.flatMap(lambda x: x.split(','))
# 将数据转化为kv结构
rdd_kv = rdd_split.map(lambda x: (x, 1))
# 对kv数据分组后累加计算
rdd_reduce = rdd_kv.reduceByKey(lambda a, b: a + b)
# todo:分组计算的结果进行checkpoint
rdd_reduce.checkpoint()
# 排序
rdd_sort = rdd_reduce.sortBy(lambda x: x[1], ascending=False)
# 查看数据
# 数据checkpoint需要使用action算子触发
res = rdd_sort.collect()
print(res)
# 后面的rdd_reduce时,会从checkpoint处获取数据处理
rdd_sort2 = rdd_reduce.sortBy(lambda x:x[1])
res = rdd_sort2.collect()
print(res)
缓存和checkpoint的区别
生命周期:缓存在程序结束后自动删除,checkpoint程序结束后仍然保存在hdfs上
存储位置:缓存优先存储在内存上,也可以选择存储在本地磁盘上
checkpoint存储在hdfs上
依赖:缓存数据后会保留依赖,是临时存储,数据可能会丢失,所以需要保留依赖,当缓存丢失后可以重新计算。
checkpoint数据后会断开依赖,数据保存在hdfs,hdfs副本机制可以保证数据不丢失,所以没有必要保留依赖关系。
RDD的依赖
概念:新的rdd是由旧的rdd计算或转化得带的
窄依赖:父RDD和子RDD的分区是一对一的,触发窄依赖的一般都是transformation算子如map,flatmap,fliter等。
宽依赖:父RDD和子RDD的分区是一对多的,触发宽依赖的一般都是action算子如reduce,sortBy等。
DAG
用于管理依赖关系,是有向无环图(一种图算法)。
作用
管理rdd依赖关系,保证rdd按照依赖关系进行数据的顺序计算。会根据rdd的依赖关系把计算过程部分成多个计算步骤,每个计算步骤成为一个stage。在计算的rdd依赖关系中,一旦发生了宽依赖,就会进行步骤拆分, 生成新的stage。
为什么要划分stage
不同stage中的task可以并行计算, 互不影响
设有一个rdd1属于stage1, rdd1和rdd2存在宽依赖关系, 此时不能并行执行, rdd1分区数据是两个task共同计算的结果(线程同时计算会有抢占资源, 造成计算不准确)
Spark的运行流程(内核调度)
-
构建Spark Application的运行环境:启动
SparkContext
,它是Spark应用程序的入口点,负责与资源管理器(如Standalone、Mesos或YARN)通信,申请资源并建立执行Executor。 -
资源管理器分配Executor资源:资源管理器根据资源申请要求和Worker节点的心跳信息,决定在哪个Worker节点上分配资源,并启动
StandaloneExecutorBackend
。 -
构建DAG图:
SparkContext
构建有向无环图(DAG),将DAG分解成多个阶段(Stage),并将任务集(TaskSet)发送给任务调度器(Task Scheduler。 -
任务调度:
Task Scheduler
负责将任务分配给Executor执行。它将任务集发放给Executor,同时SparkContext
将应用程序代码发放给Executor。 -
Executor执行任务:Executor接收任务并在其上运行,执行完毕后释放所有资源。
-
任务结果反馈:执行结果将反馈给
Task Scheduler
,然后再反馈给DAG Scheduler
。 -
作业完成和资源释放:当所有任务执行完毕后,SparkContext向资源管理器注销并释放所有资源。
-
数据本地性和推测执行优化:Spark的优化机制包括数据本地性优化,即尽可能在数据所在的位置执行任务,以及推测执行,用于处理任务执行慢或失败的情况。
-
运行模式:Spark支持多种运行模式,包括本地模式、伪分布式模式以及与多种资源管理器(如Standalone、YARN、Mesos)集成的分布式模式。
Spark的shuffle流程
作用:将rdd的数据传递给下一个rdd,进行数据交换。
本质:数据交换
应用场景:当执行宽依赖的算子就会进行shuffle
有俩个部分组成,写map阶段,将上一个stage的计算结果写入,读reduce阶段,拉取上一个stage和当前stage进行合并。
spark的shuffle方法类:
hashshuffle:进行的是hash计算 。
sortshuffle:进行的是排序计算
SparkShuffle配置:
spark.shuffle.file.buffer
该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小(默认是32K)。将数据写到磁盘文件之前会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升
spark.reducer.maxSizeInFlight
该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。(默认48M)
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
spark.shuffle.io.maxRetries
and spark.shuffle.io.retryWai
t
shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。(默认是3次)spark.shuffle.io.retryWait:该参数代表了每次重试拉取数据的等待间隔。
调优建议:一般的调优都是将重试次数调高,不调整时间间隔。
spark.shuffle.memoryFraction=10
该参数代表了Executor 1G内存中,分配给shuffle read task进行聚合操作内存比例。
spark.shuffle.sort.bypassMergeThreshold=200
-
根据task数量决定sortshuffle的模式
-
task数量小于等于200 就采用bypass task大于200就采用普通模式
当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作。 调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些
Spark并行度
分为资源并行度和数据并行度。调整并行度,提高运行效率,合理分配资源
资源并行度
又称为物理并行,由executors节点数和cores核数决定。
定义:task在执行任务时能够使用到的cpu核心数量多任务,多个进程或多个线程执行任务
两种方式:并行 (多个任务同时执行) 并发 (多个任务交替执行)
spark中cpu核心数据设置:
--num-executors=2 设置executors数量
--executor-cores=2 设置每个executors中的cpu核心数,不能超过服务器cpu核心数
数据并行度
又称为逻辑并行,由task数量决定,task由分区数决定。
说明
为了保证task能充分利用cpu资源,实现并行计算,需要设置的task数量应该和资源并行度(cpu核心数)一致。task = cpu core 这样会导致计算快的task执行结束后,一些资源就会处于等待状态,浪费资源
在实际公司中就要根据公司资源并行度设置分区数。建议task数量是cpu core的2~3倍,只有task足够多才能更好的利用资源,但是如果task很多的话,资源少,那么就会先执行一批后再执行下一批。
并行度设置
资源并行度设置
交互模式设置
pyspark --master yarn --num-executors=3 --executor-cores=2
开发模式设置
spark-submit --master yarn --num-executors=3 --executor-cores=2 /root/python_spark/a.py
数据并行度设置
创建rdd对象时指定分区数
sc.parallelize(c=, numSlices=)
sc.textFile(name=, minPartitons=)
sc.wholeTextFiles(name=, minPartitons=)
调用rdd对象transformation算子时修改分区数
rdd.distinct(numPartitions=)
rdd.groupBy(numPartitions=)
调用rdd对象修改分区数的transformation算子
rdd/df.repartition(numPartitions=)
rdd/df.coalesce(numPartitions=,shuffle=False)