引言
随着大数据技术架构的演进,存储与计算拆散的架构能更好的满足用户对升高数据存储老本,按需调度计算资源的诉求,正在成为越来越多人的抉择。相较 HDFS,数据存储在对象存储上能够节约存储老本,但与此同时,对象存储对海量文件的写性能也会差很多。
腾讯云弹性 MapReduce(EMR) 是腾讯云的一个云端托管的弹性开源泛 Hadoop 服务,反对 Spark、Hbase、Presto、Flink、Druid 等大数据框架。
近期,在反对一位 EMR 客户时,遇到典型的存储计算拆散利用场景。客户应用了 EMR 中的 Spark 组件作为计算引擎,数据存储在对象存储上。在帮忙客户技术调优过程中,发现了 Spark 在海量文件场景下写入性能比拟低,影响了架构的整体性能体现。
在深入分析和优化后,咱们最终将写入性能大幅晋升,特地是将写入对象存储的性能晋升了 10 倍以上,减速了业务解决,取得了客户好评。
本篇文章将介绍在存储计算拆散架构中,腾讯云 EMR Spark 计算引擎如何晋升在海量文件场景下的写性能,心愿与大家一起交换。文章作者:钟德艮,腾讯后盾开发工程师。
一、问题背景
Apache Spark 是专为大规模数据处理而设计的疾速通用的计算引擎,可用来构建大型的、低提早的数据分析应用程序。Spark 是 UC Berkeley AMP lab (加州大学伯克利分校的 AMP 实验室)所开源的类 Hadoop MapReduce 的通用并行框架,Spark 领有 Hadoop MapReduce 所具备的长处。
与 Hadoop 不同,Spark 和 Scala 可能严密集成,其中的 Scala 能够像操作本地汇合对象一样轻松地操作分布式数据集。只管创立 Spark 是为了反对分布式数据集上的迭代作业,然而实际上它是对 Hadoop 的补充,能够在 Hadoop 文件系统中并行运行,也能够运行在云存储之上。
在这次技术调优过程中,咱们钻研的计算引擎是 EMR 产品中的 Spark 组件,因为其优异的性能等长处,也成为越来越多的客户在大数据计算引擎的抉择。
存储上,客户抉择的是对象存储。在数据存储方面,对象存储领有牢靠,可扩大和更低成本等个性,相比 Hadoop 文件系统 HDFS,是更优的低成本存储形式。海量的温冷数据更适宜放到对象存储上,以降低成本。
在 Hadoop 的生态中,原生的 HDFS 存储也是很多场景下必不可少的存储抉择,所以咱们也在下文退出了与 HDFS 的存储性能比照。
回到咱们想解决的问题中来,先来看一组测试数据,基于 Spark-2.x 引擎,应用 SparkSQL 别离对 HDFS、对象存储写入 5000 文件,别离统计执行时长:
从测试后果能够看出,写入对象存储耗时是写入 HDFS 的 29 倍,写入对象存储的性能要比写入 HDFS 要差很多。而咱们察看数据写入过程,发现网络 IO 并不是瓶颈,所以须要深刻分析一下计算引擎数据输入的具体过程。
二、Spark数据输入过程分析
1. Spark数据流
先通过下图了解一下 Spark 作业执行过程中数据流转的次要过程:
首先,每个 task 会将后果数据写入底层文件系统的长期目录 _temporary/task_[id],目录后果示意图如下所示:
到此为止,executor 上的 task 工作其实曾经完结,接下来将交由 driver,将这些后果数据文件 move 到 hive 表最终所在的 location 目录下,共分三步操作:
第一步,调用 OutputCommiter 的 commitJob 办法做临时文件的转存和合并:
通过下面示意图能够看到,commitJob 会将 task_[id] 子目录下的所有数据文件 merge 到了下层目录 ext-10000。
接下来如果是 overwrite 笼罩写数据模式,会先将表或分区中已有的数据挪动到 trash 回收站。
在实现上述操作后,会将第一步中合并好的数据文件,move 到 hive 表的 location,到此为止,所有数据操作实现。
2. 定位剖析根因
有了上面对 Spark 数据流的剖析,当初须要定位性能瓶颈在 driver 端还是 executor 端?察看作业在 executor 上的耗时:
发现作业在 executor 端执行时长差别不大,而总耗时却差别却十分大, 这阐明作业次要耗时在 driver 端。
在 driver 端有 commitJob、trashFiles、moveFiles 三个操作阶段,具体是在 driver 的哪些阶段耗时比拟长呢?
咱们通过 spark-ui 察看 Thread dump (这里通过手动刷新 spark-ui 或者登录 driver 节点应用 jstack 命令查看线程堆栈信息),发现这三个阶段都比较慢, 上面咱们来剖析这三局部的源码。
3. 源码剖析
(1)JobCommit阶段
Spark 应用的是 Hadoop 的 FileOutputCommitter 来解决文件合并操作, Hadoop 2.x 默认应用 mapreduce.fileoutputcommitter.algorithm.version=1,应用单线程 for 循环去遍历所有 task 子目录,而后做 merge path 操作,显然在输入文件很多状况下,这部分操作会十分耗时。
特地是对对象存储来说,rename 操作并不仅仅是批改元数据,还须要去 copy 数据到新文件。
(2)TrashFiles阶段
trashFiles 操作是单线程 for 循环来将文件 move 到文件回收站,如果须要被笼罩写的数据比拟多,这步操作会十分慢。
(3)MoveFiles阶段
与后面问题相似,在 moveFiles 阶段也是采纳了单线程 for 循环形式来 move 文件。
4. 问题小结
- Spark 引擎写海量文件性能瓶颈在Driver端;
- 在 Driver 的 CommitJob、TrashFiles、MoveFiles 三个阶段执行耗时都比拟长;
- 三个阶段耗时长的起因都是因为单线程循环挨个解决文件;
- 对象存储的 rename 性能须要拷贝数据,性能较差,导致写海量文件时耗时特地长。
三、优化后果
能够看到社区版本大数据计算引擎在解决对象存储的拜访上还在肯定的性能问题,次要起因是大多数数据平台都是基于 HDFS 存储,而 HDFS 对文件的 rename 只须要在 namenode 上批改元数据,这个操作是十分快的,不容易碰到性能瓶颈。
而目前数据上云、存算拆散是企业降低成本的重要考量,所以咱们别离尝试将 commitJob、trashFiles、moveFile 代码批改成多线程并行处理文件,晋升对文件写操作性能。
基于同样的基准测试,应用 SparkSQL 别离对 HDFS、对象存储写入 5000 个文件,咱们失去了优化后的后果如下图所示:
最终写 HDFS 性能晋升 41%,写对象存储性能晋升 1100% !
四、结语
从上述的剖析过程看到,问题的要害是 Spark 引擎某些解决局部的单线程限度造成的。单线程限度其实是比拟常见的技术瓶颈。尽管咱们在一开始也有猜想这种可能性,但具体限度在哪一部分还须要理清思路,虚浮的查看源代码和屡次调试。
另外剖析中也发现了对象存储本身的限度,其 rename 性能须要拷贝数据,导致写海量文件耗时长,咱们也还在继续进行改良。
对存储计算拆散利用场景深刻优化,晋升性能,更好的满足客户对存储计算拆散场景下降本增效的需要,是咱们腾讯云弹性 MapReduce(EMR) 产品研发团队近期的重要指标,欢送大家一起交换探讨相干问题。