【Spark深入学习 -14】Spark应用经验与程序调优

3.4.1 设置合适的资源量

3.4.2 设置合理的JVM

3.4.3 启用更高效的序列化方法

3.4.4 增大off head内存

3.4.5 shuffle参数调优

3.4.6 设置reduce task数目

3.4.7 使用spark sql实现

4.Spark 调优案例

5.参考资料


1.遗留问题

1)BAT这样的企业内部是如何开发和运行Spark程序的?

· 开发和测试

intellij进行开发,对scala支持的非常好,java也支持的很好,开发好了在local模式下运行进行测试,intellij是不能远程提交到集群的,itellij没有分发jar包功能。

· spark生产环境运行

将所有依赖的包打成assembly.jar包,这样不再依赖环境里的任何模式,一般用yarn cluster模式运行(driver可以容错),可以用yarn client模式进行测试。每天要跑,可以使用工作流调度器,按照你的设置定时定点跑程序,如果出错了可以报警,工作流调度引擎,可以处理复杂的依赖,用的比较多的开源

airflow:轻量级,很多公司用

oozie:比较复杂

2)编译spark程序非常慢

修改mavn pom仓库,将国外的仓库,改成国内的仓库,如阿里云

3)Spark参数的设置问题

有三种设置方式,  在程序内设置(优先级高),提交参数设置(优先级中),配置文件配置(优先级低),park-default:常用的可以在配置文件中配

2.Spark调优初体验

调优程序,首先得知道程序慢在哪里,要定位问题,找到优化的点。定位问题又涉及到很多层面,机器硬件、网络、操作系统、JVM虚拟机、大数据软件平台层、软件开发层,所以调优是一个综合工程,Spark程序调优可以分为以下几个层面:

1)基础设施层

机器硬件(如磁盘的选择,SATA盘还是SAS盘,磁盘RAID方式等)、网络(千兆网卡还是万兆网卡,网络峰值期间的带宽、吞吐、网络延迟、网络抖动,很多时候网络问题导致各种莫名问题,举个真实的例子,公司网线被老鼠咬了,导致网络时而可以,时而不行,鬼知道是什么问题,让人抓狂)、操作系统(操作系统的稳定性,内核版本的选择,非常重要,还有一些配置策略得和hadoop生态吻合)。这些都非常底层了,就网络、linux操作系统就够花时间学习和了解的了,很多时候需要系统集成部的同事一起配合。

2)大数据平台层:HDFS、YARN、Hive、Spark、JVM等

JVM的调优,还没有什么经验,而且目前也没有非常深入,最多也就是GC策略挑挑,大数据平台调优真是个技术活,需要很多经验。

3)程序开发层

开发工程师程序开发技巧、对业务的理解等

2.1利用WebUI分析Spark程序瓶颈

选择合适的资源,前提是了解集群有多少资源 。1)集群内存总量:单台节点共8G,2G给datanode,6G个spark做计算(yarn共有18G,3个节点);2)集群CPU总合数:  每个机器有8个CPU,共24个core;3)集群总存储:总存储空间有多少,一般保证20%左右的剩余空间,要不然会影响集群整体性能,HDFS剩余空间不足也会导致任务执行很慢,甚至失败。

如何观察和评估程序的效率怎么执行shell,跑spark任务就不讲了,说明一下如何观察Spark任务执行的瓶颈,从哪几个指标观察任务执行的效率。

指标一:观察Spark任务解析和提交消耗的时间

主要涉及到的是jar包上传的优化,这里面分为2种情况:

1)程序依赖的jar,这种通常是spark lib目录下的所有jar包,有好几百兆,spark程序会上传这些,为了提升效率,可以提前上传好,

2)程序自身的jar包,如果程序不经常变动,也可以提前上传到HDFS上。

指标二:观察WebUI产生的系统监控数据

这里面有很多指标,罗列如下

1)观察job监控参数,产生了多少个job,一个action对应一个job,如果action之间没有依赖关系,资源有富余,可以让job并行执行。

2)观察stage监控参数,一个job分解成了结果stage,每个stage执行了多少时间,输入了多少数据量,shuffle read了多少数据,shuffle write 了多少数据。

3)观察executor监控参数,driver在哪里,executor在哪里,每个executor启动了几个CPU核数,起了多少内存,输入多少数据(可以查看数据是否有倾斜),shuffle了多少数据,内存放了多少数据,GC执行了多少时间。还可以查看stderr,查看日志。

4)观察每个task监控参数,task执行的时间,GC执行的时间,读入的数据总大小和记录数,shuffle的大小,task执行时的本地行,举个执行的任务例子,如下所示

可以看出来,一共有2个stage,1个stage包含8个task,一个包含2个task,先跑8个的,再跑2个的,一个14秒,一个0.1秒。再看看executor,发现只有2个executor,一个executor只有1个core,也就是一个executor只能处理一个task,集群也就是最多跑2个,10个task要跑5轮

再点击stage的链接进去,观察每个task跑多长时间

好了,按照前面罗列的监控指标点,可以看出如下监控参数:

集群资源:内存3个节点18G,VCORE:3个节点24VCORE

1)Job监控参数:只有一个Job

2)Stage监控参数:有2个Stage

·  stage1:8个task,这个先跑,执行了14秒

·  stage2:2个task,这个后跑,执行了0.1秒,是collect方法,数据汇聚到driver

3)executor:2个executor,1个executor 1G内存

4)task:本地性node_local,GC毫秒级,shuffle也是不足Kb

分析消耗的资源:

1)从stage的执行时间分析,stage1执行时间长,可以考虑优化stage1

2)executor分析:只有2个executor,内存也只有1G,启动的都是默认参数,和集群资源相比,有很作资源没有利用起来,有点浪费,可以调整executor个数,增大并发度,或者如果数据量大,也有必要,增大内存。

3)task:本地行还可以,node级别,gc也可以,shuffle也少

总结可能优化的点:

优化stage1,Job中的stage1有8个task,2个executor,需要跑4轮才能跑完第一轮的所有task,调整为8个executor(原来同时跑2个task,现在可以同时跑8个task了),那么只需要一轮就而已跑完所有的task。内存1G也够用了,因为输入数据就不足1G,如果输入数据很大的话,也可以增大内存。

2.2 设置合适的资源

设置合适的资源,要明确集群的资源总量,然后观察监控指标,检查是否充分使用了集群中的资源

资源量关联度比较大的一个是内存一个是CPU的使用率,当然还有其他,但这两个指标是比较重点的,这两个指标对应spark程序主要是Executor的内存和core。所以计算资源的设置单位是Executor

增加Executor个数:–num-executors 4

增加每个Executor同时云心的task数目:–executor-cores 2

除了Executor消耗资源,还有driver和shuffle等也都是消耗资源大户,我把几个常用的和资源使用相关的参数含义及参考值总结如下:

num-executors

参数说明:该参数用于设置Spark作业总共要用多少个Executor进程来执行。Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程。这个参数非常之重要,如果不设置的话,默认只会给你启动少量的Executor进程,此时你的Spark作业的运行速度是非常慢的。

参数调优建议:每个Spark作业的运行一般设置50~100个左右的Executor进程比较合适,设置太少或太多的Executor进程都不好。设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源。

executor-memory

参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联。

参数调优建议:每个Executor进程的内存设置4G8G较为合适。但是这只是一个参考值,具体的设置还是得根据不同部门的资源队列来定。可以看看自己团队的资源队列的最大内存限制是多少,num-executors乘以executor-memory,就代表了你的Spark作业申请到的总内存量(也就是所有Executor进程的内存总和),这个量是不能超过队列的最大内存量的。此外,如果你是跟团队里其他人共享这个资源队列,那么申请的总内存量最好不要超过资源队列最大总内存的1/31/2,避免你自己的Spark作业占用了队列所有的资源,导致别的同学的作业无法运行。

executor-cores

参数说明:该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。因为每个CPU core同一时间只能执行一个task线程,因此每个Executor进程的CPU core数量越多,越能够快速地执行完分配给自己的所有task线程。

参数调优建议:Executor的CPU core数量设置为2~4个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU core。同样建议,如果是跟他人共享这个队列,那么num-executors * executor-cores不要超过队列总CPU core的1/3~1/2左右比较合适,也是避免影响其他同学的作业运行。

driver-memory

参数说明:该参数用于设置Driver进程的内存。

参数调优建议:Driver的内存通常来说不设置,或者设置1G左右应该就够了。唯一需要注意的一点是,如果需要使用collect算子将RDD的数据全部拉取到Driver上进行处理,那么必须确保Driver的内存足够大,否则会出现OOM内存溢出的问题。

spark.default.parallelism

参数说明:该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能。

参数调优建议:Spark作业的默认task数量为500~1000个较为合适。很多同学常犯的一个错误就是不去设置这个参数,那么此时就会导致Spark自己根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task。通常来说,Spark默认设置的数量是偏少的(比如就几十个task),如果task数量偏少的话,就会导致你前面设置好的Executor的参数都前功尽弃。试想一下,无论你的Executor进程有多少个,内存和CPU有多大,但是task只有1个或者10个,那么90%的Executor进程可能根本就没有task执行,也就是白白浪费了资源!因此Spark官网建议的设置原则是,设置该参数为num-executors * executor-cores的2~3倍较为合适,比如Executor的总CPU core数量为300个,那么设置1000个task是可以的,此时可以充分地利用Spark集群的资源。

spark.storage.memoryFraction

参数说明:该参数用于设置RDD持久化数据在Executor内存中能占的比例,默认是0.6。也就是说,默认Executor 60%的内存,可以用来保存持久化的RDD数据。根据你选择的不同的持久化策略,如果内存不够时,可能数据就不会持久化,或者数据会写入磁盘。

参数调优建议:如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些,保证持久化的数据能够容纳在内存中。避免内存不够缓存所有的数据,导致数据只能写入磁盘中,降低了性能。但是如果Spark作业中的shuffle类操作比较多,而持久化操作比较少,那么这个参数的值适当降低一些比较合适。此外,如果发现作业由于频繁的gc导致运行缓慢(通过spark web ui可以观察到作业的gc耗时),意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。

spark.shuffle.memoryFraction

参数说明:该参数用于设置shuffle过程中一个task拉取到上个stage的task的输出后,进行聚合操作时能够使用的Executor内存的比例,默认是0.2。也就是说,Executor默认只有20%的内存用来进行该操作。shuffle操作在进行聚合时,如果发现使用的内存超出了这个20%的限制,那么多余的数据就会溢写到磁盘文件中去,此时就会极大地降低性能。

参数调优建议:如果Spark作业中的RDD持久化操作较少,shuffle操作较多时,建议降低持久化操作的内存占比,提高shuffle操作的内存占比比例,避免shuffle过程中数据过多时内存不够用,必须溢写到磁盘上,降低了性能。此外,如果发现作业由于频繁的gc导致运行缓慢,意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。

资源参数的调优,没有一个固定的值,需要同学们根据自己的实际情况(包括Spark作业中的shuffle操作数量、RDD持久化操作数量以及spark web ui中显示的作业gc情况),同时参考本篇文章中给出的原理以及调优建议,合理地设置上述参数。

参数设置demo

/bin/spark-submit \

–master yarn-cluster \

–num-executors 100 \

–executor-memory 6G \

–executor-cores 4 \

–driver-memory 1G \

–conf spark.default.parallelism=1000 \

–conf spark.storage.memoryFraction=0.5 \

–conf spark.shuffle.memoryFraction=0.3 \

优化后的,一共执行了11秒

每个task执行时间

再观察每个task花费时间,观察发现,每个task执行的时间比原来的4秒还长,原因是在资源不变的情况,任务数多了只有2个CPU,CPU变成瓶颈了,要频繁切换如果CPU很多,优化效果会非常好,CPU是瓶颈

2.3 设置合适的并发度

任务的并发度有几个级别:job级别的并发,stage级别的并发,task级别的并发。这里谈的并发优化是task级别的,主要就是map任务并行度和reduce任务的并行度,这方面的调优其实可以参考MapReduce的调优,原理都是一样一样的。Spark的任务数需要注意几点:

1)Map个数默认是和输入文件的blok数是一样的,如hdfs则和 blokc数目一致,hbase则和regio个数一致。

2)rdd之间的map个数如果不修改,后面的和前面个数一样

3)reduce默认个数也是和map个数一样

map设置方法:

单个设置: sc.textFile(“/input/data”,100); //指定100个blokc,那么就100个map

批量设置:将每个map处理数量调大,map数就少了,默认128M

2.4 修改存储格式

很多人并不明白为什么文件存储格式会影响文件的读取效率,我打个最简单的比方。我们知道linux 系统是单机版的操作系统,里面有ext3,ext4这样的文件,ext的职责就是对linux系统文件进行管理,HDFS是分布式的文件系统,也是对文件进行管理。好的文件系统就像一个勤快的媳妇,在你房子面积不变的情况下,勤快的媳妇会将物品放的井井有条,利用到房子里面的每个空间,你要拿什么东西,都能很快找到;而不好的文件系统就像是一个懒媳妇,房间里面堆满了东西,找起来很麻烦,房间利用率也非常糟糕,找东西困难,放东西进去也很难,东西越多越脏越乱。(媳妇没有好坏之分,只有适合不适合,还有看你怎么和媳妇相处了,互相了不了解,性格和脾气对不对路,文件系统也是如此)

文件存储格式和文件系统是一样的原理,文件系统管理的是文件,而文件储存格式管理的是文件内容(管理的是文件中每一行每一列的具体内容)。所以低效率的文件存储格式就像是一个赖媳妇,家里被管的一塌糊涂,东西越多越脏乱差,高效率的文件存储格式就是勤快且聪明的媳妇,一切都管的井然有序,取东西方便,放东西也容易,还会根据不同的物品特征进行摆放,完美,6666!!!

csv,txt,json等等都是懒媳妇,parquet,orc都是勤快媳妇,那为什么文本文件是懒媳妇,parquet是好媳妇,主要有以下几个原因:

文本文件为什么不好?

文本文件行存储,存储占用空间,而且读取数据的时候会读出很多不必要的数据出来,这就好像你叫懒媳妇给你拿一顶帽子,结果她把衣服,鞋子,袜子统统拿出来,然后再从里面挑出你要的帽子。

parquet为什么好,Spark使用parquet文件存储格式意义在哪里?

  1. 如果说HDFS 是大数据时代分布式文件系统首选标准,那么parquet则是整个大数据时代文件存储格式实时首选标准

  2. 速度更快:从使用spark sql操作普通文件CSV和parquet文件速度对比上看,绝大多数情况

会比使用csv等普通文件速度提升10倍左右,在一些普通文件系统无法在spark上成功运行的情况

下,使用parquet很多时候可以成功运行

  1. parquet的压缩技术非常稳定出色,在spark sql中对压缩技术的处理可能无法正常的完成工作

(例如会导致lost task,lost executor)但是此时如果使用parquet就可以正常的完成

  1. 极大的减少磁盘I/o,通常情况下能够减少75%的存储空间,由此可以极大的减少spark sql处理

数据的时候的数据输入内容,尤其是在spark1.6x中有个下推过滤器在一些情况下可以极大的

减少磁盘的IO和内存的占用,(下推过滤器)

  1. spark 1.6x parquet方式极大的提升了扫描的吞吐量,极大提高了数据的查找速度spark1.6和spark1.5x相比而言,提升了大约1倍的速度,在spark1.6X中,操作parquet时候cpu也进行了极大的优化,有效的降低了cpu

  2. 采用parquet可以极大的优化spark的调度和执行。我们测试spark如果用parquet可以有效的减少stage的执行消耗,同时可以优化执行路径

3.Spark调优经验

3.1 Spark原理及调优工具

· Spark Web UI界面

· jstack、jstat、jprofile

· history server:当Spark应用退出后,仍可以获得历史Spark应用的stages和tasks执行信息,便于分析程序不明原因挂掉的情况,Spark的history server依赖mr的history server。

3.2 运行环境优化

3.2.1 防止不必要的分发

每个Application都会上传一个spark-assembly-x.x.x-SNAPSHOT-hadoopx.x.x-cdhx.x.x.jar的jar包,影响HDFS的性能以及占用HDFS的空间.对于用户的jar包,有时候体积也非常庞大,我们同样的方式上传hdfs上,然后直接使用。

  1. 依赖ja包重复上传

执行spark任务有大量jar包上传HDFS,将系统jar包上传到hdfs上,直接使用hdfs上的文件,具体下:

1)修改conf/spark-default.conf添加以下配置

spark.yarn.jar hdfs://master:9000/system/spark/jars/spark-assembly-1.6.0-hadoop2.6.0.jar

2)再次执行SparkPi,提交脚本发生了变化,如下:

bin/spark-submit  --class  org.apache.spark.examples.SparkPi \ --master yarn-cluster \ --num-executors 3 \ --driver-memory 1g \ --executor-memory 1g \ --executor-cores 1 \ lib/spark-examples*.jar  10

  1. 用户jar包重复上传,避免重复分发

bin/spark-submit  --class  org.apache.spark.examples.SparkPi \ --master yarn-cluster \ --num-executors 3 \ --driver-memory 1g \ --executor-memory 1g \ --executor-cores 1 \ hdfs://master:9000/user/spark/jars/spark-examples-1.6.0-hadoop2.6.0.jar 10

3.2.2 提高数据本地性

分布式数据并行环境下,保持数据的本地性是非常重要的内容,事关分布式系统性能高下,涉及到数据本地性的概念有block、partition、worker、rack。

Spark中的数据本地性有三种:

  • PROCESS_LOCAL是指读取缓存在本地节点的数据
  • NODE_LOCAL是指读取本地节点
  • RACK_LOCAL是指读非本机架的节点数据

yarn和hfs尽可能的在一个节点上很多rack local,说明本地性很差,可以通过增加副本数来提升本地新。

3.2.3 存储格式选择

BAT等公司80%都是采用列式存储结构 ,相同的列存储在一起,只读取所需的列,io减少,相同的列存在一起,压缩比会非常高。大概是行存储的1/22个apache顶级项目ORC:源自于hive,建表最好都搞成orc,hive常用parquet,rdd读取效率低,spark sql高效率读取

列式存储和行式存储相比有哪些优势呢?

·可以跳过不符合条件的数据,只读取需要的数据,降低IO数据量。

·压缩编码可以降低磁盘存储空间。由于同一列的数据类型是一样的,可以使用更高效的压缩编码(例如Run Length Encoding和Delta Encoding)进一步节约存储空间。

·只读取需要的列,支持向量运算,能够获取更好的扫描性能。

从上图可以很清楚地看到,行式存储下一张表的数据都是放在一起的,但列式存储下都被分开保存了。所以它们就有了如下这些优缺点:

通过字典表压缩数据。为了方面后面的讲解,这部分也顺带提一下了。

下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化Normalize和Denomalize)

下面这个图,通过一条查询的执行过程说明列式存储(以及数据压缩)的优点:

关键步骤如下:

1.去字典表里找到字符串对应数字(只进行一次字符串比较)。

  1. 用数字去列表里匹配,匹配上的位置设为1。

  2. 把不同列的匹配结果进行位运算得到符合所有条件的记录下标。

  3. 使用这个下标组装出最终的结果集。

3.2.4 选择高配机器

随着硬件的不断发展和企业的需求的不断变化, 大部分企业在集群搭建初期和中期的集群配置都不一样。而Spark是一个非常消耗内存的,因此对于初期一些配置较低,尤其内存较差的机器,是不适合跑spark任务的,更加适合硬件配置高一点的机器上跑。机器配置的参差不齐,应该如何有区别的调度

,yarn提供了很好的解决方案。

yarn支持标签,根据机器的配置,给机器打相应的标签,标签如何打不在讨论范围,目前只有capacity 调度算法支持标签给队列支持打标签,将标签和队列绑定在一起,将应用程序提交到指定标签的队列,执行的时候就会提交到到相应标签节点 。

很多时候是基础平台的修改,运维负责优化 yarn基于标签的调度,haodop从hadoop2.6.0开始提供基于标签的调度策略。

3.3 优化操作符

3.3.1 过滤操作导致多小任务

filter操作使用不当,很容易引发麻烦。假如一个任务有3个parition,经过filger过滤之后,可能导致部分剩下很少,有些剩余很多,剩余很多的在下一步计算量很大,会拖后腿,其他的作业很快就做完了,而剩余很多的要执行很长时间,整个任务都要延误,而其他很快执行完的作业早就释放资源了

造成资源还的浪费

对于这种场景有2种优化策略:

1)coalses:合并已有的partiion,性能非常高,但是很有可能还不是很均与,

大的依旧很大,小的进行了合并

2)repartion:根据数据量灯亮划分,每个partion尽可能均匀,会经过一次shuffle比较均匀

3.3.2 降低单条记录开销

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

就做完了,而剩余很多的要执行很长时间,整个任务都要延误,而其他很快执行完的作业早就释放资源了

造成资源还的浪费

对于这种场景有2种优化策略:

1)coalses:合并已有的partiion,性能非常高,但是很有可能还不是很均与,

大的依旧很大,小的进行了合并

2)repartion:根据数据量灯亮划分,每个partion尽可能均匀,会经过一次shuffle比较均匀

[外链图片转存中…(img-zJvc2Zk9-1714262330819)]

3.3.2 降低单条记录开销

[外链图片转存中…(img-vlvoktNN-1714262330819)]
[外链图片转存中…(img-zAaENF51-1714262330820)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值