Spark Shuffle中的数据结构

文章目录

  • 1.Shuffle中的三种数据结构
  • 2.AppendOnlyMap原理
    • 2.1 聚合
    • 2.2 扩容
    • 2.3 排序
    • 2.4 为什么是数组?
  • 3.ExternalAppendOnlyMap原理
    • 3.1 工作原理
    • 3.2 AppendOnlyMap大小估计
      • 3.2.1 为什么要估计大小?
      • 3.2.2 估计大小浅析
        • 3.2.2.1 什么时候采样?
        • 3.2.2.2 大小估算
        • 3.2.2.3 采样后
    • 3.3 Spill过程与排序
    • 3.4 全局聚合merge-sort Shuffle
  • 4.PartitionedAppendOnlyMap原理
  • 5.PartitionedPairBuffer原理

1.Shuffle中的三种数据结构

在Spark Shuffle机制原理中,介绍了三种Shuffle的数据结构。
在这里插入图片描述
Spark中的PartitionedAppendOnlyMap和ExternalAppendOnlyMap都基于AppendOnlyMap实现。因此,我们先介绍AppendOnlyMap的原理。

2.AppendOnlyMap原理

AppendOnlyMap实际上是一个只支持record添加和对Value进行更新的HashMap。AppendOnlyMap只使用数组来存储元素,根据元素的 Hash值确定存储位置,如果存储元素时发生Hash值冲突,则使用二次地址探测法来解决Hash值冲突。
对于每个新来的<K,V>record,先使用Hash(K)计算其存放位置,如果存放位置为空,就把record存放到该位置。如果该位置已经被占用,就使用二次探测法来找下一个空闲位置。
在这里插入图片描述

2.1 聚合

  • 首先要明确的是,我们是对相同的key做聚合,而不是对相同的key的哈希值做聚合,不同的key哈希值有可能相等。
  • 当两个key的哈希值相同时,后来的key会将Hash(Key)+n*n来找到该key应该在的位置。
  • 当相同的key到来时,直接在列表中相同key的位置进行聚合。

2.2 扩容

如果插入的record太多,则很快会被填满。Spark的解决方案是,如果AppendOnlyMap的利用率达到70%,那么就扩张一倍,扩张意味着原来的Hash()失效,因此对所有Key进行rehash,重新排列每个Key的位置。

2.3 排序

由于AppendOnlyMap采用了数组作为底层存储结构,可以支持快速排序等排序算法。先将数组中所有的<K,V>record转移到数组的前端,用begin和end来标示起始位置,然后调用排序算法对[begin,end]中的record进行排序。对于需要按Key进行排序的操作,如sortByKey(),可以按照Key值进行排序;对于其他操作,只按照Key的Hash值进行排序即可。
在这里插入图片描述

2.4 为什么是数组?

  • 减少内存开销:传统哈希表(如 Java HashMap)为解决哈希冲突,采用 “数组 + 链表 / 红黑树” 结构,每个节点需要额外存储指针(如链表节点的next引用),这会增加内存占用。而AppendOnlyMap仅使用单个数组存储键值对,通过二次探测法解决冲突,避免了指针开销,更适合存储海量数据。
  • 便于直接排序:AppendOnlyMap的底层数组可以直接作为排序的数据源。

3.ExternalAppendOnlyMap原理

3.1 工作原理

Spark基于AppendOnlyMap设计实现了基于内存+磁盘的ExternalAppendOnlyMap,用于Shuffle Read端大规模数据聚合。
ExternalAppendOnlyMap的工作原理是:

  • 先持有一个AppendOnlyMap来不断接收和聚合新来的record,AppendOnlyMap快被装满时检查一下内存剩余空间是否可以扩展,可直接在内存中扩展,不可扩展则对AppendOnlyMap中的record进行排序,然后将record都spill到磁盘上。
  • 因为record不断到来,可能会多次填满AppendOnlyMap,所以这个spill过程可以出现多次,最终形成多个spill文件。
  • 等record都处理完,此时AppendOnlyMap中可能还留存一些聚合后的record,磁盘上也有多个spill文件。因为这些数据都经过了部分聚合,还需要进行全局聚合(merge)。

3.2 AppendOnlyMap大小估计

3.2.1 为什么要估计大小?

一种简单的解决方法是在每次插入record或对现有record的Value进行更新后,都扫描一下AppendOnlyMap中存放的record,计算每个record的实际对象大小并相加,但这样会非常耗时。而且一般AppendOnlyMap会插入几万甚至几百万个record,如果每个record进入AppendOnlyMap都计算一遍,则开销会很大。
Spark设计
了一个增量式的高效估算算法,在每个record插入或更新时根据历史统计值和当前变化量直接估算当前AppendOnlyMap的大小,算法的复杂度是 O (1),开销很小。在record插入和聚合过程中会定期对当前AppendOnlyMap中的record进行抽样,然后精确计算这些record的总大小、总个数、更新个数及平均值等,并作为历史统计值。进行抽样是因为AppendOnlyMap中的record可能有上万个,难以对每个都精确计算。之后,每当有record插入或更新时,会根据历史统计值和历史平均的变化值,增量估算AppendOnlyMap的总大小,详见Spark源码中的SizeTracker.estimateSize()方法。抽样也会定期进行,更新统计值以获得更高的精度。这个后面有时间研究一下。

3.2.2 估计大小浅析

经过翻阅spark源码,发现估计不同类型对象的方式有所不同,这里只分析数组方式。
对于普通数组来说,元素类型是int,float等,可以直接获取准确大小,不需要进行估计了。但是AppendOnlyMap中通常是java复杂对象。

3.2.2.1 什么时候采样?

https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/util/collection/SizeTracker.scala#L67
https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/util/SizeEstimator.scala

  • 初始化时采样:初始化时调用 resetSamples(),将首次采样时机 nextSampleNum 设为 1(即第 1 次更新后采样)。nextSampleNum表示的就是下一次采样的时机。
  • 初始化后采样:nextSampleNum = math.ceil(numUpdates * SAMPLE_GROWTH_RATE).toLong,其中SAMPLE_GROWTH_RATE的值为1.1,经查阅,应该是一个经验值,numUpdates表示的是当前数组的更新次数,如果当前是100,那么下一次采样的时机为100+100+1.1=110次时。
3.2.2.2 大小估算

数组元素的大小由两部分构成,一个是数组对象本身的固定开销(对象头),这里的数组对象本身是由数组封装的,通常大小比较固定;一个是数组元素本身内存的开销,数组元素可能是java对象或者对象指针。

  • 一个数组元素的精确总大小 = 数组元素头+指针大小+指针指向的对象大小
  • 抽样时机与抽样个数: private val ARRAY_SIZE_FOR_SAMPLING = 400,这里的400的数字在源码中是写死的,也就是说当数组长度超过400的时候,才会去进行抽样,private val ARRAY_SAMPLE_SIZE = 100抽样的方式也很简单,进行两次抽样,每次抽取100个元素。
  • 抽样的大小:先取两次抽样的最小值为val size = math.min(s1, s2)(减小共享对象对抽样的影响)。整体元素的大小为max(s1, s2) + size × ((总长度 - 100) / 100)
3.2.2.3 采样后
  • 采样后,将采样结果封装成对象,放进一个队列中。并且该队列仅保留最近的两次采样。根据这两次最近的采样估算每次更新的平均字节数。
  • 每次更新平均字节数:val bytesDelta = 最近两次采样的大小差 / 最近两次采样的更新次数差,注意这两次指的不是上面大小计算的两次,而是两次大小估算整个流程输出的结果。
  • 本次采样估算:根据平均字节增长量,估算本次采样后的增量,当前估算大小 = 上次采样大小 + 增量

时间复杂度:估算过程仅依赖内存中的采样数据和累计更新次数,因此是 O(1) 时间复杂度,高效且适合高频调用。

3.3 Spill过程与排序

  • 当AppendOnlyMap达到内存限制时,会将record排序后写入磁盘中。排序是为了方便下一步全局聚合(聚合内存和磁盘上的record)时可以采用更高效的merge-sort(外部排序+聚合)。注意,这里为了后面的高效聚合,因此进行了排序
  • sortByKey()等操作定义了按照Key进行的排序。
  • 如groupByKey(),并没有定义Key的排序方法,也不需要输出结果是按照Key进行排序的。在这种情况下,Spark采用按照Key的Hash值进行排序的方法,这样既可以进行merge-sort,又不要求操作定义Key排序的方法。这种方法的问题是会出现Hash值冲突,也就是不同Key具有相同的Hash值。为了解决这个问题,Spark在merge-sort的同时会比较Key的Hash值是否相等,以及Key的实际值是否相等。

3.4 全局聚合merge-sort Shuffle

在这里插入图片描述

  • 当所有数据输入完成后,会对当前内存中的AppendOnlyMap进行排序,并且所有的spill文件都是排序好的,现在需要进行全局聚合。
  • 聚合使用的是一个最小堆的结构,将AppendOnlyMap中的第一个元素,和所有spill在磁盘上的文件都读取第一个元素维护一个最小堆。每次取堆顶元素进行聚合,直到堆顶元素不相同为止,再取堆顶元素进行下一轮聚合。

4.PartitionedAppendOnlyMap原理

PartitionedAppendOnlyMap用于在Shuffle Write端对record进行聚合(combine)。PartitionedAppendOnlyMap的功能和实现与ExternalAppendOnlyMap的功能和实现基本一样,唯一区别是PartitionedAppendOnlyMap中的Key是“PartitionId+Key”,这样既可以根据partitionId进行排序(面向不需要按Key进行排序的操作),也可以根据partitionId+Key进行排序(面向需要按Key进行排序的操作),从而在Shuffle Write阶段可以进行聚合、排序和分区。

5.PartitionedPairBuffer原理

PartitionedPairBuffer本质上是一个基于内存+磁盘的Array,随着数据添加,不断地扩容,当到达内存限制时,就将Array中的数据按照partitionId或partitionId+Key进行排序,然后spill到磁盘上,该过程可以进行多次,最后对内存中和磁盘上的数据进行全局排序,输出或者提供给下一个操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93338.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

告别在线转换风险:本地运行的PDF转Word技术评测

Word文档&#xff08;.docx&#xff09;是可编辑的主流办公格式&#xff0c;支持灵活修改文字、排版、图片、表格等。它的体积仅有5.5M&#xff0c;小巧不占空间&#xff0c;且转换不限文件大小&#xff0c;随用随转&#xff0c;毫无限制。初次使用需完成一次安装&#xff0c;之…

整体设计 符号学与诠释学融合的整体设计框架(本篇暂时命名)--PromptPilot (助手)答问之1

说明 本系列篇&#xff08;分多篇&#xff09;是就前面 已经和腾讯元宝就“整体设计”的讨论内容 再和 PromptPilot &#xff08;助手&#xff09;的再次沟通。但内容做了部分修正一边 更准确和完整。摘要&#xff08;CSDN的AI助手提取的&#xff09;符号学与诠释学融合的整体设…

Font shape `TU/ptm/m/n‘ undefined(Font) using `TU/lmr/m/n‘ instead

一、警告内容 这是 LaTeX 字体选择机制输出的信息。我们可以把 TU/ptm/m/n 分解来看&#xff1a; TU → 编码 (font encoding) TU 表示 Unicode TeX encoding&#xff0c;即新版 XeLaTeX/LuaLaTeX 下的 Unicode 字体编码。 ptm → 字体族 (family) ptm 代表 Times 字体 (PostS…

拒绝造轮子(C#篇)ZLG CAN卡驱动封装应用

拒绝造轮子&#xff08;C#篇&#xff09;ZLG CAN卡驱动封装应用 今天给大家介绍一个封装完善的CAN卡类。 背景 在面对常规开发场景&#xff0c;开发者对复杂SDK进行封装和测试。阅读相关开发资料和理解SDK的DEMO程序。 开篇 如果你也有同样的烦恼&#xff0c;那就来看看今…

机器学习相关算法:回溯算法 贪心算法 回归算法(线性回归) 算法超参数 多项式时间 朴素贝叶斯分类算法

整理了一张“机器学习相关算法与概念速览表”&#xff0c;既包含定义&#xff0c;也配上了容易记住的例子&#xff0c;让大家一眼就能抓住它们的特点&#xff1a; &#x1f916; 机器学习与相关算法&概念 名称定义生动例子典型应用场景回溯算法通过不断尝试和回退来寻找问…

vue+微信小程序 五角星

说明&#xff1a;这个是先画出一个72度菱形&#xff0c;长中长线和短中长线按照一定比例&#xff0c;然后把菱形分层十份&#xff0c;最后再把菱形进行旋转形成五角星&#xff0c;最后显示标签&#xff0c;因为一直对不上所以对标签做了点操作 <template><view class&…

Prometheus + Grafana 深度玩法:从零到智能化监控体系

0. 写在前面&#xff1a;为什么你需要“神器”而非“常用命令老杨折腾监控系统可是有年头了&#xff0c;最早还用过 Cacti、Zabbix&#xff0c;那会儿做个仪表盘都得像雕花一样慢慢刻。后来 Prometheus 出来之后&#xff0c;我的第一反应是&#xff1a;这玩意儿的时间序列和标签…

YOLO、DarkNet和深度学习如何让自动驾驶看得清?

【导读】 本文提出 DarkNet-YOLO 工业级实践框架&#xff0c;通过引入 残差优化结构 与 多尺度特征融合技术&#xff0c;在保持实时检测精度同时显著提升复杂场景适应性。 目录 一、目标检测的进化之路&#xff1a;从“两步走”到“一眼定乾坤” YOLO的核心思想&#xff1a…

使用 HTML5 Canvas 打造炫酷的数字时钟动画

在 Web 开发中&#xff0c;HTML5 的 canvas 元素为我们带来了强大的绘图能力&#xff0c;结合 JavaScript&#xff0c;可以实现各种酷炫的效果。今天&#xff0c;我们将深入剖析一段经典的 彩色数字时钟动画 代码&#xff0c;并理解它是如何通过物理模拟实现数字切换时的炫酷粒…

XCZU6CG-2FFVC900I Xilinx FPGA AMD ZynqUltraScale+ MPSoC

XCZU6CG-2FFVC900I Xilinx FPGA&#xff08; AMD&#xff09;Zynq UltraScale MPSoC 。在处理系统&#xff08;PS&#xff09;方面&#xff0c;XCZU6CG 系列通常集成了 ARM Cortex-A53 应用核与 Cortex-R5 实时核的组合&#xff08;典型为 A53 多核 R5 双核组合&#xff09;&…

Navicat 询问 AI | 优化 SQL 查询

近期&#xff0c;我们发布了 Navicat 17.3 版本。这一版本实现了全方位升级&#xff0c;包括对 AI 功能大升级、支持达梦、金仓、瀚高、支持阿里通义千问等 AI 大模型&#xff0c;支持凝思 OS 以及多项 UI 改进。今天&#xff0c;我们将深入介绍 Navicat AI 功能之“询问 AI ”…

4.6 Vue 3 中的模板引用 (Template Refs)

在 Vue 3 中&#xff0c;ref 是一个核心的响应式 API&#xff0c;但它在模板中还有另一个非常重要的用途&#xff1a;获取对 DOM 元素或子组件实例的直接引用。这就是我们所说的“模板引用”。核心概念目的&#xff1a;让你在父组件中能够直接访问并操作特定的 DOM 元素或子组件…

模式匹配自动机全面理论分析

模式匹配是什么 模式匹配是计算机科学中一个基础且重要的问题&#xff0c;广泛应用于文本编辑、信息检索、网络安全、生物信息学等多个领域。简单来说&#xff0c;模式匹配就是在一个主文本中查找一个或多个特定模式串的出现位置。随着计算机处理能力的提升和数据规模的扩大&am…

AI 搜索时代:引领变革,重塑您的 SEO 战略

随着谷歌转向人工智能驱动的答案&#xff0c;使用以关键字和反向链接为中心的过时和传统的 SEO 策略不再起到任何作用。 由于 Google AI Overviews 和零点击搜索的兴起&#xff0c;自然点击量正在下降&#xff0c;用户无需点击任何网站即可直接在 Google 的搜索结果页面上获得答…

【网站深入seo方法】

目录 ①对于更成熟的网站&#xff0c;简单的index.html的入口文件的seo已经无法满足&#xff0c;需要在商品详情不同商品被搜索时赋予不同的title和description。 ②通过设置站点所有页面都新增Canonical标签&#xff0c;指定规范链接地址给谷歌并规避联盟的重复内容页面。 ③…

ROS move_base 混合功能导航 RealSense D435i + 3D 点云地图 + 楼层切换 + 路径录制 + 路径规划

Mixed-Navigation 这个博客也是记录我们的一个开源项目&#xff0c;其作用是混合功能导航。由于现有的 Fast-Lio-Localization 只实现了定位功能&#xff0c;但对于路径规划和楼层切换没有具体实现&#xff0c;因此我们开出了这个仓库作为参考。该仓库的核心功能如下&#xff…

初识c语言————宏定义和调用

目录&#xff1a;一.不带参数的宏二.带参数宏一.不带参数的宏不带参数的宏是指用#define指令定义的简单文本替换规则&#xff0c;它没有参数列表&#xff0c;直接替换标识符为相应的文本其一般形式为&#xff1a;#define 宏名 文本例如&#xff1a;#define pi 3.14这个代…

数据结构:满二叉树 (Full Binary Tree) 和 完全二叉树 (Complete Binary Tree)

目录 重要的术语澄清 完美二叉树 (Perfect Binary Tree) 完全二叉树 (Complete Binary Tree) 大比拼 (Comparison) 相互关系的第一性推导 我们来深入探讨两种在算法中非常重要的、具有特定“形状”的二叉树&#xff1a;满二叉树 (Full Binary Tree) 和 完全二叉树 (Compl…

OpenJDK 17的C1和C2编译器实现中,方法返回前插入安全点(Safepoint Poll)的机制

OpenJDK 17 JIT编译器堆栈分析-CSDN博客 在OpenJDK 17的C1和C2编译器实现中&#xff0c;方法返回前插入安全点&#xff08;Safepoint Poll&#xff09;的机制主要涉及以下关键步骤&#xff0c;结合源代码进行分析&#xff1a; 1. 安全点轮询桩&#xff08;Safepoint Poll Stu…

【论文笔记】STORYWRITER: A Multi-Agent Framework for Long Story Generation

论文信息 论文标题&#xff1a;StoryWriter: A Multi-Agent Framework for Long Story Generation 论文作者&#xff1a;Haotian Xia, Hao Peng et al. (Tsinghua University) 论文链接&#xff1a;https://arxiv.org/abs/2506.16445 代码链接&#xff1a;https://github.com/…