Spark 内核的设计原理
导读:本期是 DataFun 深入浅出 Apache Spark 第一期的分享,主讲老师耿嘉安开场介绍了自己的从业经历,当前就职的数新网络与 Spark 相关的两款产品赛博数智引擎 CyberEngine 和赛博数据智能平台 CyberData。
本次分享题目为《Spark 内核的设计原理》,主要介绍:
初识 Spark
Spark 有哪些特点
Spark 基本概念
Spark 的核心功能
Spark 模型设计
Spark 部署架构
▌初识 Spark
Spark 是一个通用的并行计算框架,由加州伯克利大学的 AMP 实验室在 2009 年开发,并且在 2010 年以 0.6 版本开源,2013 年左右成为 Apache 旗下在大数据领域最活跃的开源项目之一。
![](https://static001.geekbang.org/infoq/9a/9ac79b26aa2c0e8366b63735b88d9aef.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
▌Spark 有哪些特点
Spark 的基本特点可归纳为以下五点:灵活的内存管理,灵活的并行度控制,可选的 Shuffle 排序,避免重新计算,减少磁盘 I/O。
![](https://static001.geekbang.org/infoq/ab/ab0c02be4684cfcc6e687f8f4cfb2492.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
1.灵活的内存管理
减少了内存溢出的问题,用逻辑规划将 JVM 内存按照堆内/堆外,存储/计算分为四个象限。其中执行和存储内存的边界并不固定,通过逻辑规划来进行约定,可以相互借用,以此来提高内存资源的利用率,减少资源的浪费。Spark 1.4 版本在内存管理器上通过“钨丝计划(Tungsten)”实现了一种与操作系统内存页非常相似的数据结构,内存管理可以直接向操作系统申请/释放内存,避免了 JVM 额外的开销,使得内存分配效率更加接近硬件。同时 Spark 有任务级别的内存管理,任务的计算属于执行内存的一部分。
![](https://static001.geekbang.org/infoq/40/409dd48c8ece443437dd99897d87dbed.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
2.灵活的并行度控制 Spark
通过 Shuffle 依赖的关系把不同的环节抽象为 Stage 的概念,允许多个 Stage 既可以串行执行,又可以并行执行提升并行度。同时 Stage 内部由一系列的 RDD 组成,RDD 允许用户自定义自身的并行度,能够更加有效应对海量数据的场景。
3.科学的 Shuffle 排序
Hadoop 早期的 MR 在 Shuffle 之前会有固定的排序,便于后期 Reduce 端拉取数据。对于 Spark 这点是可选步骤,根据场景特点可在 Shuffle 之前的 Map 端或者之后的 Reduce 端进行排序。
4.避免重新计算
从 Stage 构建出来的 DAG 血缘关系能够在执行失败的时候重新调度,加上对检查点的支持可以避免重复计算,这点对于大数据量场景下的稳定运行非常关键。
5.减少磁盘 I/O
最早期的时候,Spark 和 MapReduce 类似,在 Map 端生成中间数据时,会针对每个 Reduce 端生成文件,从而产生很多小文件。后期 Spark 版本优化引入分区索引,Map 端不会为下游 Reduce 端单独生成小文件,而是通过索引构建文件的方式,用偏移量为下游 Reduce 端拉取数据做服务。每个分区是顺序写的方式,在 Reduce 端读数据的时候也是顺序读取,避免了随机读,减少了大量的磁盘 I/O 开销。同时 Spark Driver 端支持把通过 spark-submit 命令提交的 jar 包等资源文件缓存到本地服务内存中,在 Executor 端执行的时候可以直接通过 Netty 拉过去,也是一个节省 I/O 方面的改进。
6.Spark 的其他特点
包括检查点支持,易于使用(支持 Java,Scala,Python 等编程语言),交互式(Spark Shell)和 SQL 分析(借鉴了 ANSI SQL 等标准的实用语法和功能),批流一体,丰富的数据支持,高可用,丰富的文件格式支持。
![](https://static001.geekbang.org/infoq/12/1244ad025bb6703af2d7e78118a9019b.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
![](https://static001.geekbang.org/infoq/2d/2d405b00c34713865ce65173702abd43.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
▌Spark 基本概念
![](https://static001.geekbang.org/infoq/09/0994a91a40f63c07667b53f01f273516.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
1. 弹性分布式数据集 RDD
Spark 的转换(transform)API 可以将 RDD 封装为一系列具有血缘关系(DAG)的 RDD。
Spark 的动作(action)API 会将 RDD 及其 DAG 提交到 DAGScheduler。
转换 API 和动作 API 总归都是在处理数据,因此 RDD 的祖先一定是一个跟数据源相关的 RDD,负责从数据源迭代读取数据。
![](https://static001.geekbang.org/infoq/3d/3d3db6c011a2d9b53cccc87dba1fd43d.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
上图展示了 Stage 抽象和 RDD 分区的概念,体现了并行性;同时也展示了宽窄依赖的关系:图内的 map 和 union 是窄依赖,join 和 groupby 是宽依赖。
2. 有向无环图 DAG
在图论中,如何一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG 图)。Spark 使用 DAG 来反映各 RDD 之间的依赖或血缘关系。
![](https://static001.geekbang.org/infoq/5a/5a9ea6fd59f767d161e559bbcdc664a8.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
3. 分区 Partition
![](https://static001.geekbang.org/infoq/fe/fe6adb54b7fccdb88e7f690e4b72a963.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
上图 A RDD 有三个分区(Partition),通过分区器(Partitioner)(用户可以自定义)来进行区分,这样在 Stage 内部来进一步实现并行。
![](https://static001.geekbang.org/infoq/31/3180da5ba0e452f448e7a4a73d86be85.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
4. 宽窄依赖 Dependency
Narrow Dependency 为窄依赖:子 RDD 依赖于父 RDD 中固定的 Partition。分为 OneToOne Dependency 和 Range Dependency 两种;
Shuffle Dependency 为宽依赖:子 RDD 对父 RDD 中所有的 Partition 都可能产生依赖。子 RDD 对父 RDD 各个 Partition 的依赖将取决与分区计算器(Partitioner)的算法。
![](https://static001.geekbang.org/infoq/ad/ad103ec75af5534d331f0922e63b93f1.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
5. Job/Stage/Task
执行一个动作 API 产生一个 Job。Spark 会在 DAGscheduler 阶段来划分不同的 Stage,Stage 分为 ShuffleMapStage 和 ResultStage 两种。每个 Stage 中都会按照 RDD 的 Partition 数量创建多个 Task。ShuffleMapStage 中的 Task 为 ShuffleMapTask。ResultStage 中的 Task 为 ResultTask,类似于 Hadoop 中的 Map 任务和 Reduce 任务。
![](https://static001.geekbang.org/infoq/86/8658abcaea943e390f1e8a9922167a0d.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
6.Spark 为什么用 Scala(vs Java)
相比早期的 Java,Scala 能更好的支持面向函数的编程。同时对比 Java,Scala 拥有丰富的类型推断,丰富的语法糖和更现代的语法特性。但是同时其学习成本较高,可读性也不如 Java。
![](https://static001.geekbang.org/infoq/84/84f0ab91e6ac21c8be343132a5dab9a4.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
▌Spark 核心功能
1.基础设施 SparkConf:
用于管理 Spark 应用程序的各种配置信息;
Spark 内置的 RPC 框架:用于跨机器节点不同组件间的通信,使用 Netty 实现,有同步和异步的多种方式,Spark 各个组件的通信都依赖于此 RPC 框架。
事件总线:SparkContext 内部各个组件是如何通信的;
度量系统:由 Spark 中的多种度量源(Source)和多种度量输出(Sink)构成,完成对整个 Spark 集群中各个组件运行状态的监控。
![](https://static001.geekbang.org/infoq/76/76bbb62b29e8fab4f853d4e1b7485d45.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
2.SparkContext
内部隐藏了网络通信,分布式部署,消息通信,存储体系,计算引擎,度量系统,文件服务,Web UI 等内容,应用程序开发者只需要使用 SparkConext 提供的 API 完成功能开发。
3.SparkEnv
是 Spark 中的 Task 运行所必须的组件,内部封装了 RPC 环境(RPCEnv),序列化管理器(Spark 可以参数指定序列化方式),广播管理器(BroadcastManager),map 任务输出跟踪器(MapOutputTracker),存储体系,度量系统(MetricsSystem),输出提交协调器(OutputCommitCoordinator)等 Task 运行所需的各种组件。
![](https://static001.geekbang.org/infoq/1a/1a82294629f3ae22a4cefa91db9d4bd4.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
4.存储体系
Spark 优先考虑使用内存,在内存不足时才会考虑使用磁盘,在大数据场景下有性能提升。执行和存储内存之间是软边界,可以互相借用。同时通过钨丝计划可以更有效的利用系统的内存资源。在 Spark 早期还提供了以内存为中心的高容错的分布式文件系统 Alluxio(原名 Tachyon)供用户选择,除了 JVM 内存和磁盘,用户还可以写入 Alluxio。这个支持才从原码里被移除前,用户可以从 Spark 外围比如 S3 把数据加载到 Alluxio,使 Alluxio 和 Spark 之间可以更好的进行配合。
![](https://static001.geekbang.org/infoq/dd/dd72cce953e11fde5757962fca06ecf1.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
5. 调度系统
DAG 调度(DAGScheduler)负责创建 Job,将 DAG 中的 RDD 划分到不同的 Stage,给 Stage 创建对应的 Task,抽象成 Taskset,并将 Taskset 批量提交给 TaskScheduler。Task 调度(TaskScheduler)负责按照 FIFO 或者 FAIR 等调度算法对批量 Task 进行调度;
TaskScheduler 的资源来自外部的调度系统(如 Standalone,Yarn 或者 K8s),外部调度系统分配过资源后,TaskScheduler 会进一步把资源分配给 Task;同时将 Task 发送到集群管理器,分配给当前应用的 executor,由 executor 负责执行工作。 以 Yarn 为例,Yarn 把资源分配给 Spark Driver 后,Spark Driver 与 Yarn 的 NodeManager 进行通信,NodeManager 会帮 Spark 启动对应的 Executor,之后 Spark Diver 会分发任务到 Executor 上,Executor 会在本地的 JVM 中经过反序列化之后去调用对应的方法函数。
![](https://static001.geekbang.org/infoq/83/83f86136f3de04856be2e67abdd66c08.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
6. 计算引擎
内存管理器:分为堆外/堆内,计算/存储;
任务内存管理器:计算内存被 Task 分享,每个 Task 会有自己的任务内存管理器;
Tungten(钨丝):除了 JVM 内存外,可以将计算和存储在堆外去进行开辟;
外部排序器:根据任务的不同,可以在 Map 端或者 Reduce 端对 ShuffleMapTask 计算的中间结果进行排序聚合等操作;
Shuffle 管理器:用于将各个分区对应的 ShuffleMapTask 产生的中介结果持久化到磁盘,并在 Reduce 端按照分区远程拉取 ShuffleMapTask 产生的中间结果。
![](https://static001.geekbang.org/infoq/f3/f34d55a5f03ab785085571218d424731.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
1.编程模型
![](https://static001.geekbang.org/infoq/f7/f7c23d52e8a4fad4021e2ac3854d12d0.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
如上图 word count 的代码,通过动作 API 触发提交到 Driver 里,Driver 环境下有一些类,比如 DAGScheduler 会进行任务的划分,血缘的构建;TaskScheduler 进行资源的调度;BlockManager 管理需要存储的任务;RpcEnv 进行通信。Driver 环境与集群资源管理器交互来进行资源的申请,并分发任务到对应的工作节点比如 Yarn 的 NodeManager,节点帮助拉起 Executor 去执行计算,Executor 的中间或最终结果会访问存储。
2. RDD 计算模型
RDD 的每个分区(Partition)是靠分区计算器(Partitioner)得到,他们可以在多个节点的多个 Executor 上并行的执行。
![](https://static001.geekbang.org/infoq/01/01fd8c5a8ae71f7c362bf6b67ffe424d.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
![](https://static001.geekbang.org/infoq/93/9353e69ac00bab9a0bd6dd12bc330c12.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
▌Spark 的部署架构
1. 集群管理器(Cluster Manager)
资源需要稳健的平台进行管理,不管是 Spark 自带的 Standalone 还是 Yarn 或者 K8s 都有自己自带的多节点分布式的管理能力,同时支持故障容错等功能。
![](https://static001.geekbang.org/infoq/83/83e959cfc130db09b8b642c85366b59e.png)
2. Worker
在拉起 Executor 的过程中,不同的集群管理器会选择不同的 Worker;比如 Standalone 的 Worker,Yarn 的 NodeManager,K8s 的 Node。Cluster 和 Worker 在技术选型阶段让 Spark 提交到哪种集群上就已经确定了。
![](https://static001.geekbang.org/infoq/8f/8f0c96c0d689091552d64bec0fc29c97.png)
3. Executor 和 4. DriverExecutor
是在集群内拉起;Driver 对于 Yarn 和 K8s 可以选择在集群外或者内部执行。
5. Application
举个例子:用户写了一个类,其中的 main 方法调了一批 Spark API,把这个打成 jar 包,用 spark-submit 命令提交,运行在客户端,即为应用程序。如果 Driver 运行在客户端,Driver 是应用程序 JVM 进程的一部分。 如果 Driver 运行在集群上,Driver 的进程和应用程序的 JVM 进程是分开的。在资源分配上,集群管理器分配给应用程序/Driver 的是一级资源,拉起 Executor 将资源分配给任务是二级分配。
![](https://static001.geekbang.org/infoq/b3/b329c3218511a47df5ff7647b2e55941.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
▌公司简介
浙江数新网络有限公司是一家专注于多云数据智能平台和数据价值流通的服务商。公司总部位于杭州,在上海、北京、深圳等各地设有分支机构,服务网络覆盖全国各区域,客户遍布全球 50+城市。数新网络自成立以来就在人工智能领域进行了深入的探索,已有成熟的产品、基于场景的解决方案及不同行业的成功案例。帮助金融、能源电力等行业相关企业实现数字化、智能化转型,提升企业新质生产力。
数新网络自主研发的一站式多云数据智能平台,主要包括赛博数智引擎 CyberEngine、赛博数据平台 CyberData、赛博智能平台 CyberAI,可提供基于大数据的大模型调优、深度学习、价值流通等多种服务。数新网络自主研发的赛博数智引擎 CyberEngine 基于开源开放的设计理念,兼容开源引擎并进行深度优化,开放式架构支持主流引擎生态,支持多元异构引擎灵活插拔,支持流批一体、湖仓一体、数智一体等场景化能力。在此基础上,CyberEngine 以 Spark、Flink 作为主计算引擎,以 Spark 为例,基于 Spark 实现数新网络的流批引擎、统一查询引擎,在性能、稳定性、云原生化等方面全面优于社区开源版本。
▌数新网络高级架构专家 Spark Committer
耿嘉安
2014 阿里巴巴御膳房主力开发
2016 软件开发 &大数据开发,出版畅销书籍《深入理解 Spark 》
2016 艺龙网大数据架构师,主导开发大数据平台
2017 360 大数据专家,出版畅销书籍《 Spark 内核设计的艺术》
2018 360 高级大数据专家,主导开发 XSQL 查询平台
2020 麒麟高级性能专家,主导 Kylin 执行引擎加速 2024 数新网络高级架构专家
分享嘉宾|耿嘉安 数新网络高级架构专家 Spark Committer
编辑整理|董晨
内容校对|李瑶 耿嘉安 包卓娜 王妍
出品社区|DataFun
评论