Spark调优

来源:互联网 发布:珠海小源科技公司知乎 编辑:程序博客网 时间:2024/06/02 05:54

SPARK方面做的优化

1 SPARK

Spark是一个分布式的计算平台,类同Hadoop的Map/Reduce思想,提出了一个新的数据结构RDD(弹性分布式数据集),在该数据集合上提供了一系列API,更方便开发者使用Spark进行分布式数据处理,同时可高效的使用集群内存。

1.1 集群本身

针对集群自身的配置参数设置的一些优化,主要是Spark运行时的一些参数设置,例如,任务并行度、Driver内存、Executor个数、Executor核数、Executor内存分配比例、Cache内存大小、Shuffule内存大小等。参数很多,根据任务不同,业务不同可进行不同的设置,同时这些参数设置的不合理,对集群任务的性能影响很大。

1.1.1 任务并行度

实际上Spark集群的资源并不一定会被充分利用到,所以要尽量设置合理的并行度,来充分地利用集群的资源,才能充分提高Spark应用程序的性能。Spark没有自己的存储,只是一个计算引擎,需要使用一些常见的分布式文件系统进行数据存储和计算结果结果保存。Spark的Task是以Partition为单位,Partition其实对应Hdfs中的block。Spark会自动设置以文件作为输入源的RDD的并行度,依据其大小,比如HDFS,就会给每一个block创建一个partition,也依据这个设置并行度。对于reduceByKey等会发生shuffle的操作,就使用并行度最大的父RDD的并行度即可。可以手动使用textFile()、parallelize()等方法的第二个参数来设置并行度;也可以使用spark.default.parallelism参数,来设置统一的并行度。给集群中的每个cpu core设置2~3个task。比如说,spark-submit设置了executor数量是10个,每个executor要求分配2个core,那么application总共会有20个core。此时可以设置newSparkConf().set("spark.default.parallelism", "60")来设置合理的并行度,从而充分利用资源。

1.1.2 Driver内存合理使用

使用Join算子时经常会发生一个大表关联一个小表的情形,出于性能的考虑,我们会对于小表数据进行广播,将数据Collect到Driver端,然后Broadcast出去,但是数据从Executor端被拉取到Driver端时经常会发生Driver端的OMM,这时就需要预估数据量的大小合理设置Driver端的内存大小。

1.2.3 Executor 个数

Executor是存在于Worker节点上,一般有多少Worker节点便会有多少Executor。Executor是具体执行Tasks的进程,申请的Executor越多,每个Executor内包含的Cores越多,任务被执行的越快。

1.2 内存使用

Spark是一个内存计算模型,但是不管多好的服务器其内存配置始终很有限,对内存的使用必须合理,否则

1.2.1 合理使用数据结构

内存始终很有限,对于Java提供的一些高级数据结构或者Scala提供的一些高级数据结构,其内部都是按照对象进行存储和实现的,每一个对象都有对象头、引用等额外的信息,因此比较占用内存空间。比如HashMap,String,String还有额外的信息存储字符串长度,结束符等,所以我们应该避免使用这些高级数据结构浪费内存。

使用总结:

1、尽量使用字符串替代对象;

2、使用原始类型(比如Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用,从而降低GC频率,提升性能;

3、如果要便于开发,可以使用一些高级数据结构快速开发系统原型,再后期修改成简单数据结构优化;

4、简单类型不容易维护和使用,要根据集群配置适当选型。

1.2.2 减少new对象

新创建的对象一般都放置在heap内存里,如果在代码中不停的new对象,导致堆内存不够,则会引起Minor GC,如果Minor GC无法回收Eden区的内存,则会引发Full GC,所以应该减少new对象的情况。

1.2.3 压缩数据

我们知道在计算机中,一个整数8是占用4个字节,因为整数是int类型,但是我们可以用一个字节就可以存储一个数字8,这时候便可以使用数据压缩技术,让其尽量少的占用内存。

1.3 算子使用

算子使用调优具体是指对Spark的API函数使用,Spark内核理解,特殊的优化,DAG图优化,业务逻辑优化等。

1.3.1 小文件合并

Spark是一个分布式计算引擎,没有自己的文件系统,需要使用S3,Hdfs等分布式文件系统进行数据存储。以Hdfs为例:Hdfs以Bolck为单位进行Map/Reduce处理,其对应Spark中的Partition的概念,当给Hdfs上写入文件时,会存在很多小文件的情况,即使Hadoop2.0将一个Block设置为128M,也会存在文件系统里小文件很多的情况。然而Spark的Task和Partition是一一对应关系,小文件会导致Task很多,Task一多,核数有限,会导致处理的批次变多,从而性能下降,因此在读取数据时对小文件进行合并是很有必要的。解决办法有:

1、小文件合并:仔细阅读Spark源码会发现其读文件函数textFile方法内部调用的是newHadoopFile进行文件读取的,其可以定义读取的Key,Value数据结构,采取文件流的方式边读取边进行文件合并,设置一个配置参数,不如fileMaxSize控制多少字节组织成一个Partition,从而减少Task的数量;

2、直接使用textFile,在参数里写明Partition个数,但这种方式有局限,数据量多少一般无法获知,设置的Partition多少不知道,另外设置这个参数也是读取完数据调用重分区方法分区的;

3、直接使用textFile,然后调用rePartition方法重分区。

1.3.2 减少RDD的读取

如果对于一份数据进行多次处理,或者说进行多次Action操作时,会引起RDD被从头到尾根据DAG图执行一遍,这个时候我们没有必要对一个RDD读入多次,处理多次,可以使用cache算法将要进入不同处理逻辑的公用RDD进行缓存起来,从而减少RDD计算开销,提高性能。

1.3.3 RDD复用

原因其实同上,上面说的是减少重复读取,这里说的是减少RDD重复转换,如果一个RDD[String]经过处理转换成RDD[K,V],再转换成RDD[V],RDD[K,V]和RDD[V]的V是一样的,那么我们应该使用统一的RD[K,V]进行后续处理。

1.3.4 Cache合理使用

Spark是Lazy操作的,其RDD分为2类算子,一类是Transformation操作,一类是Action操作,只有Action操作才会引起RDD根据DAG图计算,所以这种性能很差,特别是对一个公用的RDD进行多个不同分支Action操作时,都会引发RDD读取到转换的每一步计算,为了避免这种情况发生,可以使用Cache将计算的公用RDD数据缓存起来,下一个Action操作时可以直接利用已经计算出来的公用RDD。Spark提供了7种持久化策略,根据需要合理使用,默认是Cache到内存。在使用Cache到内存这种方式时要注意Cache是在Executor端的内存中划分出60%左右的内存来缓存数据,如果Executor内存很小,容易发生Executor端的内存溢出,在使用时要根据数据量慎用,同时Cache完的数据要记得使用unPersist释放。

1.3.5 flatMap操作代替对RDD先进行filter再进行map

合并对一个RDD的多个map和filter操作,保持一个原则,读取完数据能进行filter先进行filter操作,同时对一个RDD比如有:rdd.filter().map().filter()map()…..,其实可以用flatMap进行替代的,不用对一个RDD来回使用filter和map,对于非常长的filter和map其实可以用flatMap一个算子处理完所有。

1.3.6 广播变量使用

Spark的RDD在计算时,特别是在开发机器学习算法时,经常会有一些外部变量或者配置参数需要传入,或者一个大表关联一个小表,这种直接使用join算子,会导致大量的数据拉取,Spark提供了一种广播变量的机制,可以将小表或者配置参数等进行广播到每个Executor的内存中,这样在Task在进行处理时直接从内存中读取,就不用再远程拉取数据了。

使用场景:

1、大表关联小表,特别是配置表;

2、算法参数或者外部小变量;

3、数据量20M左右的多表关联。

使用局限:

1、广播变量不易过大,数据量大会造成网络传输性能开销;

2、广播变量不易过大,数据量大会造成Executor内存被大量使用,从而在计算Task时要不断的进行GC,消耗时间。

1.3.7 减少Shuffule算子

其实大数据的性能差,主要是指数据Shuffule,大量的数据传输会导致网络IO和带宽压力以及磁盘压力。Shuffule是指将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合或join等操作。比如reduceByKey、join等算子,都会触发shuffle操作。在开发过程中,应该进行使用Shuffule数据量少的算子,能用map之类的算子就尽量使用,能不能reduceByKey、join、distinct、repartition等就尽量不用。这样的话,没有shuffle操作或者仅有较少shuffle操作的Spark作业,可以大大减少性能开销。

使用原则:

1、使用广播变量和map替换join操作,广播可以减少数据传输;

2、使用reduceByKey替代groupByKey操作,reduceByKey是先在本机进行预聚合,其次进行数据传输再在远程进行再聚合,而groupByKey是直接进行数据传输再进行聚合,会导致传输的数据量很多;

3、求平均值Spark未提供,可以使用map+reduceByKey+map进行实现,不用使用groupByKey进行实现,减少数据传输;

4、mapPartition替换groupByKey或者reduceByKey操作。

1.3.8 mapPartition替换map

mapPartition是指在进行处理时,可以按照partition粒度进行数据处理,而不是按照记录粒度,因此其可以减少很多开销,性能比map好。

使用场景:

1、对RDD的每条记录都处理的逻辑都一样,比如获取一个外部文件写数据,则不用使用map,因为每条记录都会打开一个文件句柄,而使用mapPartition则只会打开一个文件句柄,大大减少资源开销;

2、不使用RDD的排序方法,使用mapPartiton,在每个partition内使用二分法进行排序,其次再进行全局排序;

3、不使用RDD的取TOPN算子,使用mapPartiton,在每个partition使用冒泡排序法或者堆排序法取出TOPN,再基于partition取TOPN;

4、需要对数据进行批处理的情况,使用mapPartiton。

使用局限:

1、容易发生OMM,需注意。

1.3.9 filter+coalesce减少Task

一般对于数据收敛比比较大的情况,比如数据使用了filter变成了之前的35%,则可以使用coalesce算子减少partiton个数,从而减少Task数,提高运算效率。如果数据使用filter之后几乎没收敛,建议别使用coalesce,因为其会导致数据shuffule,可能会导致性能下降。

1.3.10 优化DAG图

如果一个业务在开发中耗时比较长,我们可以去查看Spark UI,调出来DAG图进行查看,尽量缩短DAG图的深度,从业务逻辑和实现层面进行代码分析看是否能继续优化DAG的长度。

使用方法:

1、比如对一个RDD按照不同维度进行多次reduceByKey,求不同维度的sum或者count,不对的做法是对RDD进行多次reduceByKey,这样会导致数据多次shuffule,性能特别差,但是如果使用联合key,比如key1+“”,“”+key2,这种方式进行flatMap,则进行一次reduceByKey便可以达到一次性求出所有不同纬度的count和sum;

2、比如多次使用map+filter,改变一次使用flatMap;

3、使用cache减少DAG图的长度和深度;

4、重新设计业务逻辑,找减少DGA图的途径。

1.3.11 序列化使用

Spark默认的是Java的序列化方式,即Serializable序列化,该序列化方式不用注册,但是序列化速度慢性能差,也就是ObjectOutputStream/ObjectInputStreamAPI来进行序列化和反序列化。同时,Spark也支持Kryo序列化,这种序列化速度快,性能比Java高很多,但是使用Kryo序列化时需要进行注册,特别是我们在RDD中传入自定义类型如Calass时必须进行序列化注册,否则会报kryo不能序列化的错误。可以写一个序列化的基类进行注册,其它使用场景进行集成即可,便可以完成注册。

1.4 数据倾斜

数据倾斜是指

2 DSL

领域建模语言,能够让不懂编程的人员只关注其领域信息,用领域语言实现其想要的逻辑,在DSL领域我主要做了2件事:

2.1 DSL+RDD实现报表计算

Spark目前是大数据处理界的佼佼者,但是掌握Spark技能的人很少,而且用Spark实现各种业务处理逻辑的情况也很多,比如各种15分钟,小时任务,天任务,周任务,月任务的计算等,大量的报表统计,业务逻辑基本相似,但是实现的场景很多,如果每个业务都写代码实现,需要的开发量巨大,而且很难维护。

因此,我利用Scala的解析组合字实现了一套DSL语法,可自动解析和计算,具体的实现思路如下:

1、页面可以导入计算指标,原表的列,目的表的列,区分维度列和COUNTER列;

2、算数运算,+,-,*,/,逻辑运算,AND,OR,NOT,>=,>,<=,<,=,==等,以及SUM,MAX,MIN,AVG,IF,CASE WHEN,JOIN,FILTER等算子,在界面可选择;

3、配置公式:原则目的表的某一列比如USER_COUNTER,然后选择=号,再选择一些条件,比如IF(MSISDN)=0) SUM(MSISDN) ELSE 0等,根据需要配置公式;

4、每配置一个公式则插入到页面下面,新生成一行,并支持任意一行公式的编辑修改等;

5、等所有公式都配置完成,然后可点击SUBMIT按钮进行提交下发,然后便会生成公式计算的XML文件,但是XML中<,>等都是特殊符号,因此在生成XML需要进行转义,然后调用Hdfs的FileSystem进行文件的写操作,将文件从Weberver写入到Hdfs上的目录下,目录按照汇聚的目的表表名字进行文件命名;

6、Spark在后台实时运行检测有没有XML生成,调用自己写的公式计算DSL读取XML进行公式的编译,然后读取数据生成RDD,在RDD中调用flatMap,然后循环公式进行每一个数据的条件判断和计算,将变量的值根据字段的列名字组织成枚举传入到环境变量,将环境变量传入编译后的公式中获取到计算结果,生成新的目的表结果,然后调用reduceByKey等操作进行数据汇聚;

DSL:用Scala的解析组合子实现,具体实现方法为:

A:先写出上下文无关文法;

B:继承Java的JavaTokenParsers;

C:使用相应的运算符,~表示顺序组合,opt表示可选项,rep表示重复项等;

D:自定义函数,比如MAX,MIN,AVG,SUM自定义函数实现;

E:^^对编译结果进行转义,转成函数处理,然后实现每一个函数的计算逻辑;

7、基于新生成的结果保存到Hdfs上。

2.2 DSL+RDD实现规则可配置计算

规则可配置的方案其实和2.1是一样的,只不过其是另外一套配置页面,就是多了一些函数而已,比如dynThreshold等,是取动态阈值等,只要是自定义函数,均可以继承已有的函数接口,便可以按照新的方法注入进去。

3 SPARK思考

Spark其实功能很强大,是一个分布式计算平台,目前的机器学习算法因数据量越来越大,在单机情况下已经无法完成计算,同时模型计算的时效性也很低,所以亟待需要一种新的技术去实现这些算法,Spark正好是内存计算适合这种迭代式处理,所以很多机器学习算法或者其它算法都可以被分布式实现,可以用Spark做很多分布式算法计算和实现,很多模型都可以建立在Spark之上。

同时,社交分析中的图计算也是如此,这些都是基于Spark内核RDD上进行了一系列更高层的处理。

原创粉丝点击