深入理解Spark原理,从性能优化入手

努力尽今夕,少年犹可夸。这篇文章主要讲述深入理解Spark原理,从性能优化入手相关的知识,希望能为你提供帮助。
1 Spark任务文件初始化调优
首先进行性能测试,发现这个视频图谱N度级联关系应用分为5个job,最后一个job为保存结果到HDFS,其余job为同样计算过程的反复迭代。但是发现第一个job比其他job又多了个计算阶段stage,如图中红圈所示。

深入理解Spark原理,从性能优化入手

文章图片

通过阅读程序代码,发现第一个job需要初始化一个空数组,从而产生了一个stage,但是这个stage在性能测试结果上显示,花费了14秒的时间,远远超出合理的预期范围。同时,发现这段时间网络通信也有一定开销,事实上只是内存数据初始化,代码上看不出需要进行网络通信的地方。下图是其中一台计算节点的通信开销,发现在第一个stage,写通信操作几乎没有,读通信操作大约每秒几十MB的传输速率。
深入理解Spark原理,从性能优化入手

文章图片

分析Spark运行日志,发现这个stage主要花费时间并不是处理应用的计算逻辑,而是在从Driver进程下载应用执行代码。前面说过,Spark和MapReduce都是通过移动计算程序到数据所在的服务器节点,从而节省数据传输的网络通信开销,并进行分布式计算,即移动计算比移动数据更划算,而移动计算程序就是在这个阶段进行。
深入理解Spark原理,从性能优化入手

文章图片

这个视频关系图谱计算程序因为依赖一个第三方的程序包,整个计算程序打包后大小超过17MB,这个17MB的JAR包需要部署到所有计算服务器上,即Worker节点上。但是只传输17MB的数据不可能花费这么多时间啊?
进一步分析Spark日志和代码后发现,每个计算节点上会启动多个Executor进程进行计算,而Spark的策略是每个Executor进程自己去下载应用程序JAR包,当时每台机器启动了30个Executor进程,这样就是4×30=120个进程下载,而Driver进程所在机器是一块千兆网卡,导致将这些数据传输完成花费了14秒的时间。
发现问题以后,解决办法就显而易见了。同一台服务器上的多个Executor进程不必每个都通过网络下载应用程序,只需要一个进程下载到本地后,其他进程将这个文件copy到自己的工作路径就可以了。
深入理解Spark原理,从性能优化入手

文章图片

这段代码有个技术实现细节需要关注,就是多个进程同时去下载程序包的时候,如何保证只有一个进程去下载,而其他进程阻塞等待,也就是进程间的同步问题。
解决办法是使用了一个本地文件作为进程间同步的锁,只有获得文件锁的进程才去下载,其他进程得不到文件锁,就阻塞等待,阻塞结束后,检查本地程序文件是否已经生成。
这个优化实测效果良好,第一个stage从14秒下降到不足1秒,效果显著。
深入理解Spark原理,从性能优化入手

文章图片

该案例的具体代码:https://github.com/apache/spark/pull/1616
Spark任务调度优化将4台Worker机器CPU使用率进行对比:
深入理解Spark原理,从性能优化入手

文章图片
深入理解Spark原理,从性能优化入手

文章图片
深入理解Spark原理,从性能优化入手

文章图片
深入理解Spark原理,从性能优化入手

文章图片

在第一个job的第二个阶段,第三台机器的CPU使用率和其他机器明显不同,即计算资源利用不均衡:有忙有闲的资源分配方式通常会引发性能问题。
分析Spark日志和源码,当有空闲计算资源的Worker节点向Driver注册时,就会触发Spark任务分配,而分配使用轮询,每个Worker都会轮流分配任务,保证任务分配均衡,每个服务器都能领到一部分任务。
但实测结果是在第二个stage,只有一个Worker服务器领了任务,而其他服务器无任务执行!
深入理解Spark原理,从性能优化入手

文章图片

分析日志,发现Worker节点向Driver注册有先有后,先注册的Worker开始领取任务,若:
$需执行任务数< Worker提供的计算单元数$,就会导致一个Worker领走所有任务。
而第一个job的第二个stage就是该场景,demo数据量小,按HDFS默认的Block大小,只有17个Block,第二个stage就是加载这17个Block进行初始迭代计算,只需要17个计算任务就能完成,所以当第三台服务器先于其他三台服务器向Driver注册的时候,触发Driver的任务分配,领走所有17个任务。
同时,为避免这种一个Worker先注册导致先领走全部任务,考虑增加一个配置项,只有注册的计算资源数达到阈值比例才开始分配任务:
spark.scheduler.minRegisteredResourcesRatio = 0.8

为了避免注册计算资源达不到期望资源比例而无法开始分配任务,在启动任务执行时,又增加了一个配置项,也就是最小等待时间,超过最小等待时间(秒),不管是否达到注册比例,都开始分配任务。
spark.scheduler.maxRegisteredResourcesWaitingTime = 3

启用这两个配置项后,第二个stage的任务被均匀分配到4个Worker服务器上,执行时间缩短了1.32倍。而4台Worker服务器的CPU利用率也变得很均衡了。
深入理解Spark原理,从性能优化入手

文章图片
深入理解Spark原理,从性能优化入手

文章图片
深入理解Spark原理,从性能优化入手

文章图片
深入理解Spark原理,从性能优化入手

文章图片

这个案例的具体代码你可以参考: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日志和源代码,根据Spark原理,稍加思考就发现,当时使用的这些服务器CPU48核,而应用配置最大Executor数目120,每台服务器30个任务,虽然30个任务在每个CPU核上都100%运行,但总CPU使用率仍只有60%。
优化也简单,设置应用启动参数的Executor数为48×4=192。
4 操作系统配置优化性能测试时发现,当使用不同服务器时,CPU资源利用情况也不同,某些服务器CPU处于sys态,即系统态运行的占比高:
深入理解Spark原理,从性能优化入手

文章图片

图中紫色为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占比明显下降,总的应用耗时也有明显下降。
深入理解Spark原理,从性能优化入手

文章图片

5 硬件优化分析网卡资源消耗,发现网络通信是性能瓶颈,对整个应用的影响明显。
比如在第二个、第三个job,网络通信消耗长达50s,网络读写通信都达到了网卡的最大吞吐能力,整个集群都在等待网络传输。
深入理解Spark原理,从性能优化入手

文章图片

千兆网卡最大传输速率是125MB/s,这样的速率和CPU内存肯定没法比,而虽然比单个磁盘快点,但服务器磁盘是8块磁盘组成的阵列,总磁盘吞吐量碾压千兆网卡,因此网卡传输速率瓶颈成为系统性能瓶颈。
优化很简单:使用万兆网卡。
深入理解Spark原理,从性能优化入手

文章图片

以前需要50s网络通信时间,缩短为10s左右。从性能曲线上看,网络通信在刚刚触及网卡最大传输速率的时候,就完成传输,总计算时间缩短近100s。
总结【深入理解Spark原理,从性能优化入手】大数据软件性能优化会涉及硬件、操作系统、大数据产品及其配置、应用程序开发和部署几个方面。当性能不能满足需求的时候,先看看各项性能指标是否合理,如果资源没有全面利用,那么可能是配置不合理或者大数据应用程序(包括SQL语句)需要优化;如果某项资源利用已经达到极限,那么就要具体来分析,是集群资源不足,需要增加新的硬件服务器,还是需要对某项硬件、操作系统或是JVM,甚至是对大数据产品源代码进行调优。

    推荐阅读