这是我参与「第四届青训营」笔记创作活动的第5天,学习内容为《大数据 Shuffle 原理与实践》,内容包括 Shuffle概述、Shuffle算子、Shuffle过程、Push Shuffle。
本节课的重点为:Shuffle算子、Shuffle过程、Push Shuffle。思维导图如下:
Shuffle概述
- 经典shuffle过程:
- 为什么Shuffle对性能如此重要:
- M*R次网络连接
- 大量的数据移动
- 数据丢失风险(重算)
- 可能存在大量的排序操作
- 大量的数据序列化、反序列化操作
- 数据压缩
Shuffle算子
- Spark中产生的Shuffle算子:
- repartition:重新改变分区
- Bykey:将key聚合一起的算子
- join:本身没在一起的数据按照条件放在一起
- Distinct:特殊的Bykey
- Spark中对Shuffle的抽象:宽依赖、窄依赖
出现宽依赖,拆分为2个stage(map、reduce)
两个stage之间产生shuffle
- 算子内部依赖关系(Shuffle Dependency):
- 创建会产生shuffle的RDD时,RDD会创建Shuffle Dependency来描述Shuffle相关的信息
- Partitioner:key->数字,数字代表分区
- Aggregator:目的是使传递的数据量更小
Shuffle过程
Hash Shuffle
- 不同partition写到不同文件中,每一个Map Task会为每一个Partition创建一个buffer,写满了就会flash到磁盘中,生成m*r个文件;
- 优点:不需要排序;
- 缺点:生成的文件多,同时打开的文件多;
- 优化:写数据优化。每个partition映射到一个文件片段,同时跑的任务数目为cpu核数,m*r->c*r,c为cpu核数。
Sort Shuffle
- 不再给每个partition一个buffer,所有数据都写在一个buffer里面,内存满时通过排序方式把相同partition数据放在一起;
- 优点:打开的文件少、支持map-side combine;
- 缺点:需要排序;
- 读数据:每个reduce task分别获取所有map task生成的属于自己的片段。
Register Shuffle
- Shuffle Register会根据不同的条件决定注册不同的ShuffleHandle
- 不同Shuffle Handle对应不同Shuffle Writer
Shuffle Writer和 Shuffle Reader实现
Shuffle Writer
- BypassMergeShuffleWriter:不需要排序、节省时间;写操作时会打开大量文件;类似Hash Shuffle。
- UnsafeShuffleWriter:
- 只适用于partition较少的情况;
- 使用类似内页存储序列化数据;
- 数据写入后不再反序列化
- 只根据partition排序Long Array,前24个bit记录partition,不支持combine
- unsafe:用堆外的内存,放入内存页;
- 用堆外内存原因:
- 没有java对象模型内存开销;
- 没有垃圾回收开销;
- 只按照partition排序,不排序record本身,数据序列化放到堆外内存后可直接写入磁盘/通过网络发送出去(放弃反序列化);
- SortShuffleWriter:
- 对partition排序,对key排序,内存满触发spill,排序数据放在外部存储。最终合并成data 和 index文件;
- 支持map侧的combine;
- 为什么没用堆外内存:需要对key作比较,即使放在堆外也要拿回来做比较;
- 需要combine->PartitionedAppendOnlyMap(hash table)
- 不需要combine->PartitionPairBuffer(array)
Shuffle Reader
- ShuffleBlcokFetchIterator
- 流程:对远程和本地数据块从逻辑上做一个分割(远程:构造fetchRequest对象,添加队列里,顺序是随机的)
- 远程,添加队列里面顺序是随机的原因:所有的map、task同时跑,如果不是随机的,可能出现所有的map task同时向一个节点发fetch请求
- 防止OOM的参数:数据块大小、请求数量每一个地址获取block数量、最大请求放到memory的size、控制NettyOOM次数
- External Shuffle Service
- 解耦数据计算和数据读取服务
- 优化了Spark作业的资源利用率
- 解决Executor为了服务数据的fetch请求导致无法退出问题
Shuffle优化及使用的技术
- Zero Copy(读写数据都可以用的技术)
- DMA:外部设备不通过CPU而直接与系统内存交换数据的接口技术
- 不使用Zero Copy:4次拷贝,大量上下游切换
- sendfile:简化2个通道之间数据传输的过程,减少cpu拷贝次数和上下游切换次数
- sendfile+DMA gather copy:省略了cpu拷贝,直接把内存空间读缓冲区中对应的数据描述信息(内存地址、地址偏移量)记录到相应的网络缓存里面。DMA根据地址和地址偏移量直接从readbuffer里面拷贝到网卡设备里,最终只剩两次DMA拷贝和两次上下游切换
- Netty Zero Copy
- 堆外内存,避免JVM堆内存到堆外内存的数据拷贝
- Netty一些对象的方法可以合并,包装,切分数组,避免发生内存拷贝
- Netty使用FileRegion实现文件传输,FileRegion底层封装一个方法,将文件缓冲区的数据直接传输到目标Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
- Shuffle优化
- 避免Shuffle:数据量较小的RDD作为广播变量,使用broadcast替代join;
- 使用可以map-side预聚合的算子:先聚合,再发送,发送的数据量更小,例如wordcount最后进行的就是sum操作;
- Shuffle参数优化
- 倾斜优化:提高并行度、Spark AQE Skew Join
Push Shuffle
- 为什么需要Push Shuffle?
- Avg IO Size太小,大量随机读问题,严重影响磁盘的吞吐;
- M*R次读请求,造成大量的网络连接,影响稳定性。
- Push Shuffle的实现
- Magnet:本质上,magnet维护了两份shuffle数据的副本;可靠性。
- Cloud Shuffle Service
- IO聚合:所有Mapper的同一Partition数据都远程写到同一个文件(或者多个文件)
- 备份:使用双磁盘副本(成本低,速度快)
- 代价:写入速度受到影响
- 写入优化:主从InMemory副本,写入内存认为写入成功
- 读写风险:p0-0,p0`-0同时失败,数据丢失
- cloud shuffle service AQE:在聚合文件时主动将文件切分为若干块,当触发AQE时,按照已经切分好的文件块进行拆分。
本节课涉及到Shuffle很多的知识点,初次学习感觉很困难,写的笔记若有不足之处,烦请在评论区批评指正!