这是我参与「第四届青训营 」笔记创作活动的的第6天
今天笔记主要分为四个部分:
shuffle概述
在开源实现的MapReduce中,存在Map,Shuffle,Reduce三个阶段
Map阶段:在单机上进行的针对一小块数据的计算过程。
Shuffle阶段:在map阶段的基础上,进行数据移动,为后续的reduce阶段做准备。
Reduce阶段:对移动后的数据进行处理,依然时在单机上处理一小份数据。
为什么Shuffle对性能非常重要?
- M*R次网络连接
- 大量的数据移动
- 数据丢失风险
- 可能存在大量的排序操作
- 大量的数据序列化、反序列化操作
- 数据压缩
shuffle算子
repartition:改变分区
ByKey:给定一个key-value对,根据key聚合到一起计算的算子
Join:把本身没有在一起的数据根据某种条件放到一起
Distinct:特殊的ByKey
shuffle过程
写数据
Hash Shuffle
每个partition会映射到一个独立文件
优化前:
优化后:
每个partition会映射到一个文件片段
sort shuffle
每个task生成一个包含多有partition数据的文件
读数据
每个reduce task分别获取所有map task生成的属于自己的片段
Shuffle过程的触发流程
Shuffle Handle的创建
Register Shuffle时做的最重要的事情是根据不同条件创建不同的Shuffle Handle。
shuffle Handle 与 Shuffle Writer的对应关系
BypassMergeShuffleWriter
- 不需要排序,节省时间
- 写操作的时候会打开大量文件
- 类似于Hash Shuffle
UnsafeShuffleWriter
- 使用类似内存页储存序列化数据
- 数据写入后不再反序列化
UnsafeShuffleWriter对内存的管理:
Long Array前24bit记录partition,如果partition超过2^24,Long Array就会溢出。
- 只根据partition排序Long Array
- 数据不移动
SortShuffleWriter
- 支持combine
- 需要combine时,使用PartitionAppendOnlyMap,本质是个HashTable
- 不需要combine时PartitionPairBuffer本质是个array
Reader实现
- 使用基于netty的网络通信框架
- 位置信息记录在MapOutputTracker中
- 主要发送两种类型请求:OpenBlocks请求、Chunk请求或Stream请求
ShuffleBlockFetchIterator
- 区分local和remote节省网络消耗
- 防止OOM
- maxBytesInFlight 限制数据块大小
- maxReqsInFlight 限制请求数量
- maxBlocksInFlightPerAddress 限制每一个地址上获取的block的数量
- maxReqSizeShuffleToMem 限制放到Mem里面最大的请求大小
- maxAttemptsOnNettyOOM 限制OOM次数
External Shuffle Service
ESS作为一个存在于每个节点上的agent为所有的Shuffle Reader提供服务,从而优化了Spark作业的资源利用率,MapTask在运行结束后可以正常退出。
Shuffer优化使用的技术-Zero Copy
DMA(direct Memory Access):直接存储器存取,是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。
Netty Zero Copy
- 可堆外内存,避免JVM堆内存到堆外内存的数据拷贝
- CompositeByteBuf\Unpooled.wrappedBuffer\ByteBuf,slice可以合并、包装、切分数组、避免发生内存拷贝。
- Netty使用FileRegion实现文件传输,FileRegion底层封装了FileChannel#transferTo()方法,可以将文件缓冲区的数据直接传输到目标Chennel,避免内核缓冲区和用户态缓冲区之间的数据拷贝。
常见的问题
- 数据存储在本地磁盘,没有备份
- IO并发:大量RPC请求(M*R)
- IO吞吐:随机读,写放大(3X)
- GC频繁,影响NodeManager
Shuffle优化
- 避免shuffle:使用broadcast替代join
- 使用可以map-side预聚合的算子
- 参数优化
- 倾斜优化
影响:
- 作业运行时间变长
- Task OOM导致作业失败
优化方法:
- 提高并行度(足够简单,只缓解、不根治)
- AQE Skew Join
AQE根据shuffle文件统计数据自动检测倾斜数据,将那些倾斜的分区打散成小的子分区,然后各自进行join
\
push shuffle
为什么需要push shuffle?
- AVG IO size太小,造成了大量随机IO,严重影响了磁盘的吞吐
- M*R次读请求,造成了大量的网络连接,影响稳定性
Magnet
Magnet实现原理
- Spark driver组件,协调整体的shuffle操作
- map任务的shuffle writer过程完成后,增加了一个额外的操作push-merge,将数据复制一份推到远程的shuffle服务上
- magnet shuffle service是一个强化版的ESS。将隶属于同一个shuffle partition的block,会在远程传输到magent后被merge到一个文件中
- reduce任务magent shuffle service接收合并好的shuffle数据
- bitmap:存储已merge的mapper id,防止重复merge
- position offset:如果本次block没有正常merge,可以恢复到上一个block的位置
- currentMapId:标识当前正在append的block,保证不同的mapper的block能依次append
Mangent可靠性
- 如果Map task输出的Block没有成功Push到magent上,并且反复重试仍然失败,则reduce task直接从ESS上拉取原始的block数据。
- 如果magent上的block因为重复或者冲突等原因,没有正常完成merge的过程。则reduce task 直接拉去未完成的merge的block。
- 如果reduce拉取已经merge好的block失败,则会直接拉取merge前的原始block。
- 本质上,magent中维护了两份shuffle数据的副本。