Spark Shuffle 模块② - Hash Based Shuffle write
Spark 2.0 中已经移除 Hash Based Shuffle,但作为曾经的默认 Shuffle 机制,还是值得进行分析
Spark 最开始只有 Hash Based Shuffle,因为在很多场景中并不需要排序,在这些场景中多余的排序反而会损耗性能。
Hash Based Shuffle Write
该过程实现的核心是在 HashShuffleWriter#write(records: Iterator[Product2[K, V]]): Unit
其主要流程如下:
该函数的输入是一个 Shuffle Map Task 计算得到的结果(对应的迭代器),若在宽依赖中定义了 map 端的聚合则会先进行聚合,随后对于迭代器(若要聚合则为聚合后的迭代器)的每一项先通过计算 key 的 hash 值来确定要写到哪个文件,然后将 key、value 写入文件。
写入的文件名的格式是:shuffle_$shuffleId_$mapId_$reduceId
。写入时,若文件已存在会删除会创建新文件。
上图描述了如何处理一个 Shuffle Map Task 计算结果,在实际应用中,往往有很多 Shuffle Map Tasks 及下游 tasks,即如下情况(图摘自:JerryLead/SparkInternals-Shuffle 过程):
存在的问题
这种简单的实现会有几个问题,为说明方便,这里设 M = Shuffle Map Task 数量
,R = 下游 tasks 数量
:
- 产生过多文件:由于每个 Shuffle Map Task 需要为每个下游的 Task 创建一个单独的文件,因此文件的数量就是
M * R
。如果 Shuffle Map Tasks 数量是 1000,下游的 tasks 数是 800,那么理论上会产生 80w 个文件(对于 size 为 0的文件会特殊处理) - 打开多个文件对于系统来说意味着随机写,尤其是每个文件较小且文件特别多的情况。机械硬盘在随机读写方面的性能很差,如果是固态硬盘,会改善很多
- 缓冲区占用内存空间大:每个 Shuffle Map Task 需要开 R 个 bucket(为减少写文件次数的缓冲区),N 个 Shuffle Map Task 就会产生
N * R
个 bucket。虽然一个 Shuffle Map Task,对应的 buckets 会被回收,但一个节点上的 bucket 个数最多可以达到cores * R
个,每个 bucket 默认为 32KB。对于 24 核 1000 个 reducer 来说,占用内存就是 750MB
改进:Shuffle Consolidate Writer
在上面提到的几个问题,Spark 提供了 Shuffle Consolidate Files 机制进行优化。该机制的手段是减少 Shuffle 过程产生的文件,若使用这个功能,则需要置 spark.shuffle.consolidateFiles
为 true
,其实现可用下图来表示(图摘自:JerryLead/SparkInternals-Shuffle 过程)
即:对于运行在同一个 core 的 Shuffle Map Tasks,对于将要被同一个 reducer read 的数据,第一个 Shuffle Map Task 会创建一个文件,之后的就会将数据追加到这个文件而不是新建一个文件(相当于同一个 core 上的 Shuffle Map Task 写了文件不同的部分)。因此文件数就从原来的 M * R
个变成了 cores * R
个。当 M / cores
的值越大,减少文件数的效果越显著。需要注意的是,该机制虽然在很多时候能缓解上述的几个问题,但是并不能彻底解决。
参考
- 《Spark 技术内幕》
- JerryLead/SparkInternals - Shuffle 过程
- 网站访问状态和超时时间监控报警设置
- 为treeview添加客户端事件
- Flash/Flex学习笔记(20):贝塞尔曲线
- 磁盘挂载问题:Fdisk最大只能创建2T分区的盘,超过2T使用parted
- asp.net中几种页面元素的比较
- Flash/Flex学习笔记(19):颜色合成与分解的基本原理
- Flash/Flex学习笔记(18):画线及三角函数的基本使用
- Mapx自带的工具的理解
- 水晶报表的推模式
- Flash/Flex学习笔记(17):按键捕获
- 温故而知新:c#中的特性(attribute)
- 温故而知新:new与override的差异以及virtual方法与abstract方法的区别
- malloc函数及用法
- Centos下安装破解confluence6.3的操作记录
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- linux下用time(NULL)函数和localtime()获取当前时间的方法
- linux防火墙状态查看的方法实例
- 详解在linxu下Svn一键安装shell脚本
- Linux中screen命令及使用方法
- Linux中安装Composer的步骤分享
- Linux md5sum命令的使用方法
- linux文件管理命令实例分析【显示、查看、统计等】
- linux用户和组命令实例分析【切换、添加用户、权限控制等】
- Linux中Bash环境变量的配置方法
- Linux 中的防火墙 ufw 简介
- linux下如何实现快速拷贝大文件
- Linux硬链接与软链接原理及用法解析
- Linux通用java程序启动脚本代码实例
- 如何解决Ubuntu18.04循环登录/卡在开机界面/无法进入图形界面的问题
- CentOS7连接XShell与网络配置的方法