Spark| 软件性能优化| spark性能优化案例
1. 软件性能优化
如何进行软件性能优化,系统地了解软件性能优化。所谓性能优化具体要做些什么呢?
关于软件性能优化,有个著名的论断。
- 1. 你不能优化一个没有经过性能测试的软件。
- 2. 你不能优化一个你不了解其架构设计的软件。
如果没有性能测试,那么你就不会知道当前软件的主要性能指标有哪些。通常来说,软件的主要性能指标包括:
-
响应时间:完成一次任务(请求)花费的时间。
-
并发数:同时处理的任务数(请求数)。
-
吞吐量:单位时间完成的任务数(请求数、事务数、查询数……)。
-
性能计数器:System Load,线程数,进程数,CPU、内存、磁盘、网络使用率等。
如果没有性能指标,我们也就不清楚软件性能的瓶颈,优化前和优化后也是无从对比。这样的优化工作只能是主观臆断:别人这样做说性能好,我们也这样优化。
而如果不了解软件的架构设计,你可能根本无从判断性能瓶颈产生的根源,也不知道该从哪里优化。
所以性能优化的一般过程是:
- 1. 做性能测试,分析性能状况和瓶颈点。
- 2. 针对软件架构设计进行分析,寻找导致性能问题的原因。
- 3. 修改相关代码和架构,进行性能优化。
- 4. 做性能测试,对比是否提升性能,并寻找下一个性能瓶颈。
大数据软件性能优化
在大数据使用、开发过程的性能优化一般可以从以下角度着手进行。
1. SQL 语句优化。使用关系数据库的时候,SQL 优化是数据库优化的重要手段,因为实现同样功能但是不同的 SQL 写法可能带来的性能差距是数量级的。我们知道在大数据分析
时,由于数据量规模巨大,所以 SQL 语句写法引起的性能差距就更加巨大。典型的就是 Hive 的 MapJoin 语法,如果 join 的一张表比较小,比如只有几 MB,那么就可以用 MapJoin
进行连接,Hive 会将这张小表当作 Cache 数据全部加载到所有的 Map 任务中,在 Map 阶段完成 join 操作,无需 shuffle。
2. 数据倾斜处理。数据倾斜是指当两张表进行 join 的时候,其中一张表 join 的某个字段值对应的数据行数特别多,那么在 shuffle 的时候,这个字段值(Key)对应的所有记录都会被
partition 到同一个 Reduce 任务,导致这个任务长时间无法完成。淘宝的产品经理曾经讲过一个案例,他想把用户日志和用户表通过用户 ID 进行 join,但是日志表有几亿条记录的用
户 ID 是 null,Hive 把 null 当作一个字段值 shuffle 到同一个 Reduce,结果这个 Reduce 跑了两天也没跑完,SQL 当然也执行不完。像这种情况的数据倾斜,因为 null 字段没有意
义,所以可以在 where 条件里加一个 userID != null 过滤掉就可以了。
3. MapReduce、Spark 代码优化。了解 MapReduce 和 Spark 的工作原理,了解要处理的数据的特点,了解要计算的目标,设计合理的代码处理逻辑,使用良好的编程方法开发大数
据应用,是大数据应用性能优化的重要手段,也是大数据开发工程师的重要职责。
4. 配置参数优化。根据公司数据特点,为部署的大数据产品以及运行的作业选择合适的配置参数,是公司大数据平台性能优化最主要的手段,也是大数据运维工程师的主要职责。比
如 Yarn 的每个 Container 包含的 CPU 个数和内存数目、HDFS 数据块的大小和复制数等,每个大数据产品都有很多配置参数,这些参数会对大数据运行时的性能产生重要影响。
5. 大数据开源软件代码优化。曾经和杭州某个 SaaS 公司的大数据工程师聊天,他们的大数据团队只有 5、6 个人,但是在使用开源大数据产品的时候,遇到问题都是直接修改
Hadoop、Spark、Sqoop 这些产品的代码。修改源代码进行性能优化的方法虽然比较激进,但是对于掌控自己公司的大数据平台来说,效果可能是最好的。
Spark 性能优化
有了上面这些性能优化原则和过程,我们在了解 Spark 架构和代码的基础上,就可以进行性能优化了。
关于性能测试,我们使用的是 Intel 为某视频网站编写的一个基于 Spark 的关系图谱计算程序,用于计算视频的级联关系。我们使用 5 台服务器对样例数据进行性能测试,程序运行总体性能如下图。
将 4 台 Worker 服务器上主要计算资源利用率指标和这张图各个 job 与 stage 的时间点结合,就可以看到不同运行阶段的性能指标如何,从而发现性能瓶颈。
从这些图我们可以看到,CPU、内存、网络、磁盘这四种主要计算资源的使用和 Spark 的计算阶段密切相关。主要通过这些图来分析 Spark 的性能问题,进而寻找问题根源,并进一
步进行性能优化。
基于软件性能优化原则和 Spark 的特点,Spark 性能优化可以分解为下面几步。
- 1. 性能测试,观察 Spark 性能特性和资源(CPU、Memory、Disk、Net)利用情况。
- 2. 分析、寻找资源瓶颈。
- 3. 分析系统架构、代码,发现资源利用关键所在,思考优化策略。
- 4. 代码、架构、基础设施调优,优化、平衡资源利用。
- 5. 性能测试,观察系统性能特性,是否达到优化目的,以及寻找下一个瓶颈点。
希望通过这几个案例,可以帮助你更好地理解 Spark 的原理,以及性能优化如何实践落地。
案例 1:Spark 任务文件初始化调优
首先进行性能测试,发现这个视频图谱 N 度级联关系应用分为 5 个 job,最后一个 job 为保存结果到 HDFS,其余 job 为同样计算过程的反复迭代。但是发现第一个 job 比其他 job 又
多了个计算阶段 stage,如图中红圈所示。
通过阅读程序代码,发现第一个 job 需要初始化一个空数组,从而产生了一个 stage,但是这个 stage 在性能测试结果上显示,花费了 14 秒的时间,远远超出合理的预期范围。同
时,发现这段时间网络通信也有一定开销,事实上只是内存数据初始化,代码上看不出需要进行网络通信的地方。下图是其中一台计算节点的通信开销,发现在第一个 stage,写通信
操作几乎没有,读通信操作大约每秒几十 MB 的传输速率。
分析 Spark 运行日志,发现这个 stage 主要花费时间并不是处理应用的计算逻辑,而是在从 Driver 进程下载应用执行代码。前面说过,Spark 和 MapReduce 都是通过移动计算程序
到数据所在的服务器节点,从而节省数据传输的网络通信开销,并进行分布式计算,即移动计算比移动数据更划算,而移动计算程序就是在这个阶段进行。
这个视频关系图谱计算程序因为依赖一个第三方的程序包,整个计算程序打包后大小超过 17MB,这个 17MB 的 JAR 包需要部署到所有计算服务器上,即 Worker 节点上。但是只传
输17MB的数据不可能花费这么多时间啊?
进一步分析 Spark 日志和代码后发现,每个计算节点上会启动多个 Executor 进程进行计算,而 Spark 的策略是每个 Executor 进程自己去下载应用程序 JAR 包,当时每台机器启动了
30 个 Executor 进程,这样就是 4×30=120 个进程下载,而 Driver 进程所在机器是一块千兆网卡,导致将这些数据传输完成花费了 14 秒的时间。
发现问题以后,解决办法就显而易见了。同一台服务器上的多个 Executor 进程不必每个都通过网络下载应用程序,只需要一个进程下载到本地后,其他进程将这个文件 copy 到自己
的工作路径就可以了。
这段代码有个技术实现细节需要关注,就是多个进程同时去下载程序包的时候,如何保证只有一个进程去下载,而其他进程阻塞等待,也就是进程间的同步问题。
解决办法是使用了一个本地文件作为进程间同步的锁,只有获得文件锁的进程才去下载,其他进程得不到文件锁,就阻塞等待,阻塞结束后,检查本地程序文件是否已经生成。
这个优化实测效果良好,第一个 stage 从 14 秒下降到不足 1 秒,效果显著。
这个案例的具体代码你可以参考:
https://github.com/apache/spark/pull/1616
案例 2:Spark 任务调度优化
继续前面的性能测试,看看有没有新的性能瓶颈以及性能指标不合理的地方。将 4 台 Worker 机器的 CPU 使用率进行对比分析,发现 CPU 使用率有些蹊跷的地方。
从图中看到,在第一个 job 的第二个阶段,第三台机器的 CPU 使用率和其他机器明显不同,也就是说计算资源利用不均衡,这种有忙有闲的资源分配方式通常会引起性能问题。
分析 Spark 运行日志和 Spark 源代码,发现当有空闲计算资源的 Worker 节点向 Driver 注册的时候,就会触发Spark的任务分配,分配的时候使用轮询方式,每个Worker都会轮流
分配任务,保证任务分配均衡,每个服务器都能领到一部分任务。但是为什么实测的结果却是在第二个 stage,只有一个 Worker 服务器领了任务,而其他服务器没有任何任务可以执
行?
进一步分析日志,发现 Worker 节点向 Driver 注册有先有后,先注册的 Worker 开始领取任务,如果需要执行的任务数小于 Worker 提供的计算单元数,就会出现一个 Worker 领走所
有任务的情况。
而第一个 job 的第二个 stage 刚好是这样的情况,demo 数据量不大,按照 HDFS 默认的 Block 大小,只有 17 个 Block,第二个 stage 就是加载这 17 个 Block 进行初始迭代计算,只
需要 17 个计算任务就能完成,所以当第三台服务器先于其他三台服务器向 Driver 注册的时候,触发 Driver 的任务分配,领走了所有 17 个任务。
同时,为了避免这种一个 Worker 先注册先领走全部任务的情况,我们考虑的一个优化策略是增加一个配置项,只有注册的计算资源数达到一定比例才开始分配任务,默认值是 0.8。
spark.scheduler.minRegisteredResourcesRatio = 0.8
为了避免注册计算资源达不到期望资源比例而无法开始分配任务,在启动任务执行时,又增加了一个配置项,也就是最小等待时间,超过最小等待时间(秒),不管是否达到注册比
例,都开始分配任务。
spark.scheduler.maxRegisteredResourcesWaitingTime = 3
启用这两个配置项后,第二个 stage 的任务被均匀分配到 4 个 Worker 服务器上,执行时间缩短了 1.32 倍。而 4 台 Worker 服务器的 CPU 利用率也变得很均衡了。
这个案例的具体代码你可以参考:https://github.com/apache/spark/pull/900
https://github.com/apache/spark/pull/1525
案例 3:Spark 应用配置优化
看案例 2 的几张 CPU 利用率的图,我们还发现所有 4 个 Worker 服务器的 CPU 利用率最大只能达到 60% 多一点。例如下图,绿色部分就是 CPU 空闲。
这种资源利用瓶颈的分析无需分析 Spark 日志和源代码,根据 Spark 的工作原理,稍加思考就可以发现,当时使用的这些服务器的 CPU 的核心数是 48 核,而应用配置的最大
Executor 数目是120,每台服务器30个任务,虽然30个任务在每个 CPU 核上都 100% 运行,但是总的 CPU 使用率仍只有 60% 多。
具体优化也很简单,设置应用启动参数的 Executor 数为 48×4=192 即可。
案例 4:操作系统配置优化
在性能测试过程中发现,当使用不同服务器的时候,CPU 资源利用情况也不同,某些服务器的 CPU 处于 sys 态,即系统态运行的占比非常高,如下图所示。
图中紫色为CPU处于sys态,某些时候sys态占了CPU总使用率的近 80%,这个比例显然是不合理的,表示虽然CPU很忙,但是没有执行用户计算,而是在执行操作系统的计算。
那么,操作系统究竟在忙什么,占用了这么多 CPU 时间?通过跟踪 Linux 内核执行指令,发现这些 sys 态的执行指令和 Linux 的配置参数 transparent huge pages 有关。
当 transparent huge pages 打开的时候,sys 态 CPU 消耗就会增加,而不同 Linux 版本的 transparent huge pages 默认是否打开是不同的,对于默认打开 transparent huge pages 的
Linux 执行下面的指令,关闭 transparent huge pages。
1
|
echo never > /sys/kernel/mm/transparent_hugepage/enabled
|
2
|
echo never > /sys/kernel/mm/ transparent_hugepage/defrag
|
3
|
关闭以后,对比前面的 CPU 消耗,sys 占比明显下降,总的应用耗时也有明显下降。
案例 5:硬件优化
分析网卡的资源消耗,发现网络通信是性能的瓶颈,对整个应用的影响非常明显。比如在第二个、第三个 job,网络通信消耗长达 50 秒的时间,网络读写通信都达到了网卡的最大吞吐能力,整个集群都在等待网络传输。
我们知道千兆网卡的最大传输速率是每秒125MB,这样的速率和 CPU 内存固然没法比,而虽然比单个磁盘快一些,但是服务器磁盘是 8 块磁盘组成的阵列,总的磁盘吞吐量依然碾压
千兆网卡,因此网卡传输速率的瓶颈就成为整个系统的性能瓶颈。
而优化手段其实很简单粗暴,就是是升级网卡使用万兆网卡。
硬件优化的效果非常明显,以前需要 50 多秒的网络通信时间,缩短为 10 秒左右。从性能曲线上看,网络通信在刚刚触及网卡最大传输速率的时候,就完成了传输,总的计算时间缩
短了近 100 秒。
小结
一般说来,大数据软件性能优化会涉及硬件、操作系统、大数据产品及其配置、应用程序开发和部署几个方面。当性能不能满足需求的时候,先看看各项性能指标是否合理,如果资源
没有全面利用,那么可能是配置不合理或者大数据应用程序(包括 SQL 语句)需要优化;如果某项资源利用已经达到极限,那么就要具体来分析,是集群资源不足,需要增加新的硬
件服务器,还是需要对某项硬件、操作系统或是 JVM,甚至是对大数据产品源代码进行调优。