2020 Google开发者大会重磅开幕 了解详情

我用Rust徒手重写了一个Spark,并把它开源了

2019 年 11 月 08 日

我用Rust徒手重写了一个Spark,并把它开源了

本文作者Raja Sekar已经有三年多Spark的使用经验,他认为Spark的DataFrame非常优秀,可以解决大多数分析工作负载问题,但仍然有一些地方使用RDD会更方便。于是,他萌生出了一个使用原生语言重新实现Spark的想法,想看看重写后在性能和资源管理效率方面可以达到怎样的效果。最后他选择了最近很火的Rust,重写后的FastSpark不仅在运行速度上比Spark更快,而且能够节省相当多的内存,作者接下来的目标也很简单:将其作为Apache Spark的替代方案。


这一切都始于我对各种分布式调度器和分布式计算框架的研究,而 Spark 就是其中的一个。因为有三年多使用 Spark 的经验,所以我对 Spark 的内部原理已经有了一定的了解。Spark 之所以取得巨大成功,不仅是因为速度和效率,还因为它提供了非常直观的 API。pandas 之所以这么流行,也是因为这个。否则的话,如果出于对性能的考虑,人们可以选择其他更好的替代方案,比如 Flink、Naiad 或者像 OpenMP 这样的 HPC 框架。


Spark 是一个通用的分布式框架,RDD 非常适合用来处理非结构化数据或复杂的任务。而现在的 Spark 都是关于 DataFrame 和 SQL,它们比 RDD 更受欢迎。DataFrame 的性能比 RDD 更好。RDD 是 Spark 生态系统的基础,那为什么 DataFrame 会获得更好的性能呢?是因为做了查询优化吗?以最简单的查询为例,你可以使用 RDD 定义出最好的数据和计算流,但 DataFrame 仍然可能胜过你定义的查询。秘密在于 Tungsten 引擎。Spark 的性能主要依赖内存。Spark 在处理典型任务时,JVM 很快就会把资源消耗光。因此,Spark 使用“sun.misc.Unsafe”来直接管理原始内存。这样造成的结果是 DataFrame API 不如 RDD 灵活。它们只能处理固定的预定义数据类型,所以你不能在数据流中自由地使用数据结构或对象。在实际当中,DataFrame 可以解决大多数分析工作负载,但仍然有一些地方使用 RDD 会更方便。


于是,我有了一个使用原生语言重新实现 Spark 的想法,看看它在性能和资源管理效率方面可以达到怎样的效果。Spark 已经经过多年的优化,所以我不指望在性能上能有巨大的差别,如果有,很可能是在内存使用方面。另外,我希望它可以像 Spark 一样通用。我决定使用 Rust 来实现,因为我也没有太多其他的选择。虽然 C++也非常适合做这个,但我更喜欢 Rust 的简洁,而且 Rust 在很多方面与 Scala 相似。如果你看过代码库里的示例代码,就会知道它与典型的 Scala Spark 代码有多么相似。我实现了一个非常基本的版本,支持部分 RDD 操作和转换。不过我还没有实现文件的读取和写入,所以暂时需要手动编写代码来读取文件。


FastSpark 项目开源链接:


https://github.com/rajasekarv/native_spark


性能基准测试使用了一个 1.2TB 的 CSV 文件。


  • 使用了5个GCloud节点(1主4副);

  • 机器类型:n1-standard-8(8个vCPU,30GB内存)。


这是一个简单的 reduceBy 操作,参考代码:FastSparkSpark


  • FastSpark使用的时间为:19分钟35秒

  • Apache Spark使用的时间为:1小时2分钟


这个结果十分令人惊讶。我并不指望在运行速度方面会有什么不同,我比较期待的是内存的使用情况。因为任务运行在分布式进程上,无法测量 CPU 使用时间,所以我测量了执行节点在程序运行期间的 CPU 使用情况。


FastSpark


FastSpark


Apache Spark


Apache Spark


可以看到,FastSpark 进行了大量的 I/O 操作,CPU 使用率约为 28%,而 Spark CPU 使用率始终为 100%。iotop 显示,FastSpark 在执行期间 I/O 完全饱和,而 Spark 只使用了一半多。


在执行节点上,FastSpark 峰值的内存利用率不超过 150MB,而 Spark 达到了 5-6GB,并在这个范围内波动。这种巨大的差异可能是由于 JVM 对象分配造成的,对象分配是非常昂贵的操作。我最初的实现版本比当前版本慢了两倍多。最初的实现版本使用了很多克隆和装箱,移除掉一部分之后就带来了巨大的性能提升。


同样的逻辑使用 Rust 实现比 FastSpark RDD 要快两倍多。性能分析显示,上述的 FastSpark 程序在分配和系统调用方面占用了 75%的运行时间,主要是因为 Rust 实现的版本为动态分派装箱了大量数据。


下面是在本地运行 4 种不同实现版本的结果(处理 10GB 数据)。文件是 CSV 格式,并且保存在硬盘上(是的,我的硬盘很慢),这里主要关注 user 时间。


Rust 基本版本:


real 6m05.203s
user 1m02.049s
sys 0m8.572s

复制代码


FastSpark 版本:


real 6m32.884s
user 1m48.904s
sys 0m4.489s

复制代码


Apache Spark RDD 版本:


real 10m14.646s
user 14m45.452s
sys 0m9.021s

复制代码


Apache Spark DataFrame版本


real 8m23.422s
user 10m34.975s
sys 0m8.882s

复制代码


CPU 密集型任务


Spark 的分析任务几乎总是需要消耗大量 CPU,因为我们通常会使用压缩文件,如 Parquet 或 Avro。下面是读取 Parquet 文件(从 10GB CSV 文件生成,800MB 左右)并执行相同操作的结果。


FastSpark 版本:


real 1m31.327s
user 4m50.342s
sys 0m1.922s

复制代码


现在变成需要消耗大量 CPU,它把所有的 CPU 时间都花在解压缩和哈希操作上。


Spark DataFrame 版本:


real 0m55.227s
user 2m03.311s
sys 0m2.343s

复制代码


这就是 Dataframe API 提供的优化结果。代码与之前是一样的,只是用 Parquet 代替了 CSV 格式。但需要注意的是,Spark SQL 生成的代码只选择需要的列,而 FastSpark 使用 get_row_iter 遍历所有行。


我写了一段读取文件的代码,只读取需要的列,让我们看看结果。代码请参考这里


FastSpark(只选择需要的列):


real 0m13.582s
user 0m34.753s
sys 0m0.556s

复制代码


这样的速度相当快了。它仍然是 IO 密集的。此外,它只使用了大约 400MB 内存,而 Spark DataFrame 使用了 2-3GB。这是我更喜欢 RDD API 的原因之一,我们可以对数据流进行更灵活的控制。虽然抽象对于大多数应用程序来说是没问题的,但有时候我们需要完成一些任务,而具有良好的性能的底层 API 更适用。


实际上,这可以让 FastSpark DataFrame 变得比 Apache Spark DataFrame 更强大、更通用。与 Spark 不一样的是,FastSpark DataFrame 可以支持任意的数据类型,还可以通过为数据类型实现自定义散列来连接具有不同数据类型的列。不过 FastSpark DataFrame 还没有开源出来,因为现在还处在试验阶段。我倾向于选择类似 pandas 那样的设计,不仅灵活,还具有很高的性能。如果有可能,它还可以与 Python 对象进行互操作,但又不同于 PySpark。


这个工作流非常简单,也在非常大型的数据集上运行过,所以我选择了它。当然,这也可能是我的个人偏好。如果可能的话,请读者自己运行代码,并向我反馈结果。Spark 经过了很多优化,特别是 shuffle,在这方面我的实现(非常简单)比 Spark 要慢得多。另外,使用 FastSpark 执行 CPU 密集型任务通常会更快。


主要目标:


  • 将其作为Apache Spark的替代方案。这意味着用户端API应该要保持一致。

  • 在与Python集成方面比PySpark做得更好。


已完成:


  • 项目处于非常初级的POC阶段,只支持少数的RDD操作和转换。

  • 分布式调度器已实现,但离投入生产还差得很远,而且非常脆弱。容错和缓存还没有完成,但应该很快就能完成。


未来规划:


  • 通用的文件读取器很快就可以完成。文件接口与Spark的不一样。支持HDFS还需要做大量的工作,但支持其他文件系统应该很简单。这个项目的主要目标之一是让它成为Apache Spark的完全替代品(至少支持Python和R语言等非JVM语言),所以我将尽量保持用户端API的兼容性。

  • 因为代码是试验性的,所以其中有很多硬编码的东西。支持配置和部署将是下一个优先事项。

  • shuffle操作实现得还很简单,性能也不好,这个需要改进。


原文链接:


https://medium.com/@rajasekar3eg/fastspark-a-new-fast-native-implementation-of-spark-from-scratch-368373a29a5c


2019 年 11 月 08 日 16:44 25594

评论 7 条评论

发布
用户头像
真的是徒手吗? 不拿支笔吗?
2019 年 11 月 19 日 15:12
回复
用户头像
Rust编写的程序稳定性和处理性能都很不错,使用Rust写了一个Java性能分析工具Flare Profiler,可以稳定用于压测分析Java服务性能瓶颈,欢迎一起探讨学习 Java & Rust: https://github.com/kylixs/flare-profiler
2019 年 11 月 18 日 10:22
回复
用户头像
无独有偶,我用golang徒手撸了一个Flink,与Flink保持完全兼容的SQL接口,支持各种标准窗口,EventTime, watermark, checkpoint & recovery等。
2019 年 11 月 17 日 19:18
回复
目前可以网上一键生成流式作业的可执行文件:http://creek.baidubce.com/
2019 年 11 月 17 日 19:18
回复
用户头像
rust兼具底层编程语言的灵活性和高级语言的易用性,设计新颖,开发团队也在不断提供新的库新的语法功能,以后会有很多项目用Rust编写,前几天我也刚好早起打卡读书读到这本《The Rust Programming Language》,欢迎一起探讨学习https://v.huya.com/play/233707788.html
2019 年 11 月 15 日 10:38
回复
最近看到用Swift做数据科学这种做法,觉得可以了解一下。
2020 年 01 月 28 日 23:30
回复
用户头像
还好我在学rust...
2019 年 11 月 11 日 14:12
回复
没有更多评论了
发现更多内容

代码重构--架构师必备技能

李广富

架构师训练营第三周课后作业

竹森先生

极客大学架构师训练营

大白话Java多线程,小白都能看的懂的哦

java金融

Java 多线程 线程安全 创建线程方式 什么是多线程

Week 03- 作业一:设计模式

dean

极客大学架构师训练营

代码重构练习三

李广富

week3

Geek_2e7dd7

【架构师训练营 - 周总结 -3】设计模式、重构

小动物

总结 极客大学架构师训练营 第三周

从单机事务到分布式事务

ElvinYang

Tweak原理与越狱防护

大冯宇宙

架构师训练营week3学习总结

Frank Zeng

week 3学习总结

Geek_2e7dd7

【week03】总结

chengjing

week3 作业& 手撕单例模式

不在调上

万恶的NPE差点让我半个月工资没了

java金融

Java 程序员 互联网 NPE 空指针

week3 学习总结

不在调上

极客大学架构师训练营

架构师训练营第 3 周 _ 学习总结

方舟勇士

课程总结

组织协同-研发项目责任矩阵

飞哥

研发管理 团队组织

【week03】作业1

chengjing

元年云“宽能力”拓宽成长型企业数字化升级之路

人称T客

第三周总结

andy

【架构师训练营 - 作业 -3】组合模式

小动物

极客大学架构师训练营 作业 第三周

第三周作业

戴维斯

极客大学架构师训练营

奈学教育《百万架构师》课程大纲

奈学教育

极客大学架构师训练营

第三周作业

andy

中心化是人性,去中心化是技术

CECBC区块链专委会

区块链技术 去中心化 超级节点

瓷都景德镇牵手蚂蚁区块链,重塑非遗陶瓷产业

CECBC区块链专委会

区块链技术 溯源 防篡改 景德镇 非遗

week 3

陈皮

架构师训练营Week03

Frank Zeng

奈学教育《百万架构师》课程大纲

古月木易

极客大学架构师训练营

有益思考一则:概率与格局

石君

思考 思维方式 格局

项目交付二三事

飞哥

持续交付

2020中国技术力量年度榜单盛典

2020中国技术力量年度榜单盛典

我用Rust徒手重写了一个Spark,并把它开源了-InfoQ