gitbook/从0开始学大数据/docs/72638.md
2022-09-03 22:05:03 +08:00

12 KiB
Raw Blame History

20 | Spark的性能优化案例分析

上一期我讲了软件性能优化必须经过进行性能测试并在了解软件架构和技术的基础上进行。今天我们通过几个Spark性能优化的案例看一看所讲的性能优化原则如何落地。如果你忘记了性能优化的原则可以返回上一期复习一下。

基于软件性能优化原则和Spark的特点Spark性能优化可以分解为下面几步。

1.性能测试观察Spark性能特性和资源CPU、Memory、Disk、Net利用情况。

2.分析、寻找资源瓶颈。

3.分析系统架构、代码,发现资源利用关键所在,思考优化策略。

4.代码、架构、基础设施调优,优化、平衡资源利用。

5.性能测试,观察系统性能特性,是否达到优化目的,以及寻找下一个瓶颈点。

下面我们一起进入详细的案例分析希望通过这几个案例可以帮助你更好地理解Spark的原理以及性能优化如何实践落地希望能对你有所启发。

案例1Spark任务文件初始化调优

首先进行性能测试发现这个视频图谱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

案例2Spark任务调度优化

继续前面的性能测试看看有没有新的性能瓶颈以及性能指标不合理的地方。我们将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

案例3Spark应用配置优化

看案例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。

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/ transparent_hugepage/defrag

关闭以后对比前面的CPU消耗sys占比明显下降总的应用耗时也有明显下降。

案例5硬件优化

分析网卡的资源消耗发现网络通信是性能的瓶颈对整个应用的影响非常明显。比如在第二个、第三个job网络通信消耗长达50秒的时间网络读写通信都达到了网卡的最大吞吐能力整个集群都在等待网络传输。

我们知道千兆网卡的最大传输速率是每秒125MB这样的速率和CPU内存固然没法比而虽然比单个磁盘快一些但是服务器磁盘是8块磁盘组成的阵列总的磁盘吞吐量依然碾压千兆网卡因此网卡传输速率的瓶颈就成为整个系统的性能瓶颈。

而优化手段其实很简单粗暴,就是升级网卡使用万兆网卡。

硬件优化的效果非常明显以前需要50多秒的网络通信时间缩短为10秒左右。从性能曲线上看网络通信在刚刚触及网卡最大传输速率的时候就完成了传输总的计算时间缩短了近100秒。

小结

一般说来大数据软件性能优化会涉及硬件、操作系统、大数据产品及其配置、应用程序开发和部署几个方面。当性能不能满足需求的时候先看看各项性能指标是否合理如果资源没有全面利用那么可能是配置不合理或者大数据应用程序包括SQL语句需要优化如果某项资源利用已经达到极限那么就要具体来分析是集群资源不足需要增加新的硬件服务器还是需要对某项硬件、操作系统或是JVM甚至是对大数据产品源代码进行调优。

思考题

关于目前的主要大数据产品你在学习、使用过程中从SQL写法、应用编程、参数配置到大数据产品自身的架构原理与源码实现你有没有发现有哪些可以进行性能优化的地方

欢迎你点击“请朋友读”,把今天的文章分享给好友。也欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。