spark sql优化2

SparkSQL性能调整
SparkSQL 优化
1.广播JOIN表
spark.sql.autoBroadcastJoinThreshold,默认10485760(10M)
在内存够用的情况下提高其大小,可以将join中的较小的表广播出去,而不用进行网络数据传输.
2.合理配置spark.sql.shuffle.partition设置shuffle并行度;
3. 缓存表
对于一条SQL语句中可能多次使用到的表,可以对其进行缓存,使用SQLContext.cacheTable(tableName)或者DataFrame.cache即可。Spark SQL会用内存 列存储的格式进行表的缓存。然后SparkSQL就可以仅仅扫描需要使用的列,并且自动优化压缩,来最小化内存使用和GC开销。可以通过spark.sql.inMemoryColumnarStorage.batchSize这个参数,默认10000,配置列存储单位.
你还可以使用SQLContext.setConf 或在SQL语句中运行SET key=value命令,来配置内存中的缓存。
spark.sql.inMemoryColumnarStorage.compressed true 如果设置为true,Spark SQL将会根据数据统计信息,自动为每一列选择单独的压缩编码方式。
spark.sql.inMemoryColumnarStorage.batchSize 10000 控制列式缓存批量的大小。增大批量大小可以提高内存利用率和压缩率,但同时也会带来OOM(Out Of Memory)的风险。
其他配置选项
以下选项同样也可以用来给查询任务调性能。不过这些选项在未来可能被放弃,因为spark将支持越来越多的自动优化。
spark.sql.files.maxPartitionBytes 134217728 (128 MB) 读取文件时要打包到单个分区中的最大字节数。
spark.sql.files.openCostInBytes 4194304 (4 MB) 估计打开一个文件的成本,测量的字节数可以扫描在同一时间。这是在将多个文件放入分区时使用的。最好是过高估计,那么具有小文件的分区将比具有大文件的分区(这是首先计划的)更快。
spark.sql.broadcastTimeout 300广播连接中的广播等待时间超时(以秒为单位)广播连接中的广播等待时间超时(以秒为单位)
spark.sql.autoBroadcastJoinThreshold 10485760 (10 MB) 配置join操作时,能够作为广播变量的最大table的大小。设置为-1,表示禁用广播。注意,目前的元数据统计仅支持Hive metastore中的表,并且需要运行这个命令:ANALYSE TABLE COMPUTE STATISTICS noscan
spark.sql.shuffle.partitions 200 配置数据混洗(shuffle)时(join或者聚合操作),使用的分区数
4. sparkSql参数调优
A. Spark.sql.codegen 默认false 若设置为true,Spark SQL会将每个查询都编译为Java字节码。当查询量较大时,这样的设置能够改进查询性能,但不适用于查询量小的情形.
B. spark.sql.inMemoryColumnarStorage.compressed 默认true置为true时,会根据统计信息自动为每列选择压缩存储.
c.spark.sql.inMemoryColumnarStorage.batchSize 默认10000 需要视存储数据量而定,若此值设置过大,可能导致内存溢出。
D.spark.sql.shuffle.partitions 默认200,配置连接或聚合数据移动数据时要使用的分区数
E.spark.sql.parquet.compression.codec 默认snappy 可以设置多种编码方式,除了snappy外,还可以设置为uncompressed,gzip和lzo
*注意:sparkSql默认是snappy的压缩方式。这些选项可以放在配置文件中,也可以以编程方式设置SQLContext。例如:
val sqlContext = new SQLContext(sc)
sqlContext.setConf(“spark.sql.codegen”, true)
sqlContext.setConf(“spark.sql.inMemoryColumnarStorage.batchSize”, “10000”)
当把spark.sql.codegen设置为true时,由于需要初始化编译器,在第一次执行查询时,可能会影响查询性能。
5.基于SparlSql提供的优化器cataLyst进行优化
这是基于Scala的函数式编程元素。它提供的语法解析功能通过创建表达式树对语法进行解析,每种数据类型和操作都可以视为表达式树的一种节点。这些节点皆继承自TreeNode抽象类。
6.spark.scheduler.mode 为 FAIR 模式
首先spark.scheduler.mode有FIFO,FAIR两种模式,FIFO是说提交的job,都是顺序执行的,后提交的 job 一定要等之前提交的 job 完全执行结束后才可以执行;FAIR是之前提交的 job 没有用完集群资源的话后提交的job可以即刻开始运行。
7.Hive数据仓库建设的时候,合理设置数据类型,比如你设置成INT的就不要设置成BIGINT,减少数据类型不必要的内存开销
并行性
SparkSQL在集群中运行,将一个查询任务分解成大量的Task分配给集群中的各个节点来运行。通常情况下,Task的数量是大于集群的并行度。比如前面第六章和第七章查询数据时,shuffle的时候使用了缺省的spark.sql.shuffle.partitions,即200个partition,也就是200个Task:

而实验的集群环境却只能并行3个Task,也就是说同时只能有3个Task保持Running:

这时大家就应该明白了,要跑完这200个Task就要跑200/3=67批次。如何减少运行的批次呢?那就要尽量提高查询任务的并行度。查询任务的并行度由两方面决定:集群的处理能力和集群的有效处理能力。
l对于Spark Standalone集群来说,集群的处理能力是由conf/spark-env中的SPARK_WORKER_INSTANCES参数、SPARK_WORKER_CORES参数决定的;而SPARK_WORKER_INSTANCES*SPARK_WORKER_CORES不能超过物理机器的实际CPU core;
l集群的有效处理能力是指集群中空闲的集群资源,一般是指使用spark-submit或spark-shell时指定的–total-executor-cores,一般情况下,我们不需要指定,这时候,Spark Standalone集群会将所有空闲的core分配给查询,并且在Task轮询运行过程中,Standalone集群会将其他spark应用程序运行完后空闲出来的core也分配给正在运行中的查询。
综上所述,SparkSQL的查询并行度主要和集群的core数量相关,合理配置每个节点的core可以提高集群的并行度,提高查询的效率。
高效的数据格式
高效的数据格式,一方面是加快了数据的读入速度,另一方面可以减少内存的消耗。高效的数据格式包括多个方面:
(1).数据本地性
分布式计算系统的精粹在于移动计算而非移动数据,但是在实际的计算过程中,总存在着移动数据的情况,除非是在集群的所有节点上都保存数据的副本。移动数据,将数据从一个节点移动到另一个节点进行计算,不但消耗了网络IO,也消耗了磁盘IO,降低了整个计算的效率。为了提高数据的本地性,除了优化算法(也就是修改spark内存,难度有点高),就是合理设置数据的副本。设置数据的副本,这需要通过配置参数并长期观察运行状态才能获取的一个经验值。
下面是Spark webUI监控Stage的一个图:
lPROCESS_LOCAL是指读取缓存在本地节点的数据
lNODE_LOCAL是指读取本地节点硬盘数据
lANY是指读取非本地节点数据
l通常读取数据PROCESS_LOCAL>NODE_LOCAL>ANY,尽量使数据以PROCESS_LOCAL或NODE_LOCAL方式读取。其中PROCESS_LOCAL还和cache有关。

(2).合适的数据类型
对于要查询的数据,定义合适的数据类型也是非常有必要。对于一个tinyint可以使用的数据列,不需要为了方便定义成int类型,一个tinyint的数据占用了1个byte,而int占用了4个byte。也就是说,一旦将这数据进行缓存的话,内存的消耗将增加数倍。在SparkSQL里,定义合适的数据类型可以节省有限的内存资源。
(3).合适的数据列
对于要查询的数据,在写SQL语句的时候,尽量写出要查询的列名,如Select a,b from tbl,而不是使用Select * from tbl;这样不但可以减少磁盘IO,也减少缓存时消耗的内存。
(4).更优的数据存储格式
在查询的时候,最终还是要读取存储在文件系统中的文件。采用更优的数据存储格式,将有利于数据的读取速度。查看SparkSQL的Stage,可以发现,很多时候,数据读取消耗占有很大的比重。对于sqlContext来说,支持 textFiile、SequenceFile、ParquetFile、jsonFile;对于hiveContext来说,支持AvroFile、ORCFile、Parquet File,以及各种压缩。根据自己的业务需求,测试并选择合适的数据存储格式将有利于提高SparkSQL的查询效率。
内存的使用
spark应用程序最纠结的地方就是内存的使用了,也是最能体现“细节是魔鬼”的地方。Spark的内存配置项有不少,其中比较重要的几个是:
lSPARK_WORKER_MEMORY,在conf/spark-env.sh中配置SPARK_WORKER_MEMORY 和SPARK_WORKER_INSTANCES,可以充分的利用节点的内存资源,SPARK_WORKER_INSTANCES*SPARK_WORKER_MEMORY不要超过节点本身具备的内存容量;
lexecutor-memory,在spark-shell或spark-submit提交spark应用程序时申请使用的内存数量;不要超过节点的SPARK_WORKER_MEMORY;
lspark.storage.memoryFraction spark应用程序在所申请的内存资源中可用于cache的比例
lspark.shuffle.memoryFraction spark应用程序在所申请的内存资源中可用于shuffle的比例
在实际使用上,对于后两个参数,可以根据常用查询的内存消耗情况做适当的变更。另外,在SparkSQL使用上,有几点建议:
l对于频繁使用的表或查询才进行缓存,对于只使用一次的表不需要缓存;
l对于join操作,优先缓存较小的表;
l要多注意Stage的监控,多思考如何才能更多的Task使用PROCESS_LOCAL;
l要多注意Storage的监控,多思考如何才能Fraction cached的比例更多

合适的Task
对于SparkSQL,还有一个比较重要的参数,就是shuffle时候的Task数量,通过spark.sql.shuffle.partitions来调节。调节的基础是spark集群的处理能力和要处理的数据量,spark的默认值是200。Task过多,会产生很多的任务启动开销,Task多少,每个Task的处理时间过长,容易straggle。
其他的一些建议
优化的方面的内容很多,但大部分都是细节性的内容,下面就简单地提提:
l  想要获取更好的表达式查询速度,可以将spark.sql.codegen设置为Ture;
l  对于大数据集的计算结果,不要使用collect() ,collect()就结果返回给driver,很容易撑爆driver的内存;一般直接输出到分布式文件系统中;
l  对于Worker倾斜,设置spark.speculation=true 将持续不给力的节点去掉;
l  对于数据倾斜,采用加入部分中间步骤,如聚合后cache,具体情况具体分析;
l  适当的使用序化方案以及压缩方案;
l  善于利用集群监控系统,将集群的运行状况维持在一个合理的、平稳的状态;
l  善于解决重点矛盾,多观察Stage中的Task,查看最耗时的Task,查找原因并改善;

Spark调优系列之序列化方式调优
由于大多数的spark计算是基于内存的的天性,spark应用的瓶颈一般受制于集群的CPU,网络带宽,内存。大部分情况下,如果内存适合当前数据量的计算,那么瓶颈往往就是带宽,但是有时候我们也需要进行一些调优比如序列化,来减少内存的使用。调优系列目前主要会更新两个主题:数据序列化,这点对于网络带宽调优和减少内存是至关重要的;另一种是内存调优。当然,也会简单介绍一些其他的调优点。本文只讲数据的序列化。
在任何分布式应用中序列化都扮演者一个重要的角色。序列化过程非常慢的或者消耗大量字节的序列化格式,都是会巨大的减缓计算速度。通常这是优化spark应用程序的第一件事情。Spark目标是在你的操作中直接便利的使用java类型和性能找到一个平衡点。目前,spark提供两种序列化的库:
1.Java serialization:默认情况下,spark使用Java的 ObjectOutputStream框架,序列化对象。可以应用于任何继承了java.io.Serializable的自创建类。你也可以通过更密切的继承java.io.Externalizable,来控制你自己的序列化方式的性能。JAVA的序列化虽然灵活,但是通常是非常慢的,同时针对很多类会导致大的序列化格式。
2.Kryo serialization:Spark也可以用 Kryo library (version 2) 来加速序列化。Kryo比java序列化更快,更紧凑(往往搞出10倍),但是并不支持所有的序列化类型,为了达到最佳的性能需要提前注册你在你的程序中使用的类。
你可以通过使用SparkConf更改spark的序列化方式。这个设置不仅影响到worker间传输的Shuffle数据也会序列化准备写到磁盘的RDD。Kryo不是默认序列化方式的主要原因是需要自定义注册。我们建议使用它在任何网络密集型应用程序中。
Spark会自动的包括Kryo,针对大多数通用的scala类。
向Kryo注册你的类,可以通过registerKryoClasses 方法 
val conf = new SparkConf().setMaster(…).setAppName(…)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf) 
    https://github.com/EsotericSoftware/kryo 链接文档描述了更先进的kryo注册选项,如添加自定义序列化代码。 
如果你的对象非常大,你需要增加spark.kryoserializer.buffer。这个值要大于你要序列化的最大的对象。 
最后,如果不向Kyro注册你的自定义类型,Kyro也会继续工作,但是他会保存你每个对象的类全名,这非常浪费。

spark调优系列之高层通用调优
1.并行度
集群不会被充分利用,除非您将每个操作的并行级别设置得足够高。Spark自动会根据文件的大小,是否可分割等因素来设置map的数目(后面会详细讲解输入格式,同时详细讲解各种输入的map数的决定)。对于分布式reduce操作,例如groupbykey和reducebykey,默认它使用的是分区数最大的父RDD的分区数决定reduce的数目。你也可以通过设置spark.default.parallelism来改变默认值,建议值是每个CPU执行2-3个tasks。
2.Reduce任务的内存使用
有时候内存溢出并不是由于你的RDD不适合放在内存里面,而是由于你的某个task的工作集太大了,比如使用groupbykey的时候reduce任务数据集太大了。Spark的shuffle操作(sortByKey, groupByKey, reduceByKey, join, etc)会构建一个hash表,每个task执行一个分组的数据,单个往往会很大。最简单的改善方法是增加并行度,让每个task的输入变得更小。Spark可以高效的支持短达200ms的任务,因为复用了Executor的JVM,这可以降低启动成本,所以你可以很安全的增加并行度,使其超过你的集群core数目。
3.广播变量
使用spark的广播功能可以大幅度减少每个序列化后的task的大小,也可以减少在集群中执行一个job的代价。如果你的任务中使用了大的对象,比如静态表,可以考虑将它声明成广播变量。在driver节点,spark会打印出每个task序列化后的大小,所以你可以通过查看task的大小判断你的task是否过大,通常task的大小超过20KB就值得调优了。
4.数据本地性
数据的本地性可能会对Spark jobs产生重大影响。如果数据和在其上操作的代码在一起,则计算往往是快速的。但如果代码和数据分开,则必须要有一方进行移动。典型的情况是将序列化后的代码移动到数据所在的地方,因为数据往往比代码大很多。Spark构建调度计划的原则就是数据本地性。
数据本地性就是数据离处理他的代码有多远。根据数据和代码当前的位置,数据本地性等级。从最近到最远的顺序列出如下:
(1)PROCESS_LOCAL
数据和代码在同一个JVM中,这是最佳的数据本地性。
(2)NODE_LOCAL
数据和代码在相同的节点。比如数据在同一节点的HDFS上,或者在统一节点的Executor上。由于数据要在多个进程间移动,所以比PROCESS_LOCAL稍慢。
(3)NO_PREF
数据可以从任何地方快速访问,没有数据本地性。
(4)RACK_LOCAL
数据和代码在相同的机架。数据位于同一机架上的不同服务器上,因此需要通过网络发送,通常通过单个交换机发送
(5)ANY
数据在网络上的其他地方,而不在同一个机架中。
Spark倾向于调度任务依据最高的数据本地性,但这往往是不可能的。在任何空闲的Executor上没有未处理数据的情况下,Spark会切换到较低的数据本地性。这种情况下会有两个选择:
1),等待CPU空闲,然后在相同的server上启动task。
2),立即在一个需要迁移数据的较远位置启动一个新的task。
Spark的典型处理策略是等待繁忙CPU释放,时间很短。一旦超时,将移动数据到空闲CPU的地方执行任务。每个级别之间的回退等待超时可以在一个参数中单独配置或全部配置。如果任务较长,且数据本地性较差,可以适当调整Spark.locatity超时时间相关的配置。具体配置如下:
属性 默认值 含义
spark.locality.wait 3s 超时时间,放弃等待在较低数据本地性新启任务。
spark.locality.wait.node spark.locality.wait NODE_LOCAL等待超时时间
spark.locality.wait.process spark.locality.wait PROCESS_LOCAL等待超时时间
spark.locality.wait.rack spark.locality.wait RACK_LOCAL等待超时时间

Spark调优系列之硬件要求
估计所有的spark开发者都很关心spark的硬件要求。恰当的硬件配置需要具体情况具体分析,浪尖在这里给出以下建议。
1.存储系统
因为因为大多数Spark工作可能需要从外部存储系统(例如Hadoop文件系统或HBase)中读取输入数据,所以将其尽可能靠近该系统很重要。所以,有如下建议:
1,如果可能,在与HDFS相同的节点上运行Spark。最简单的方式是将spark 的Standalone集群和hadoop集群安装在相同的节点,同时配置好Spark和hadoop的内存使用,避免相互干扰(对于hadoop,每个task的内存配置参数是mapred.child.java.opts; mapreduce.tasktracker.map.tasks.maximum 和mapreduce.tasktracker.reduce.tasks.maximum 决定了task的数目)。也可以将hadoop和spark运行在共同的集群管理器上,如mesos和 yarn。
2,如果不可能,请在与HDFS相同的局域网中的不同节点上运行Spark。
3,对于低延迟数据存储(如HBase),可能优先在与存储系统不同的节点上运行计算任务以避免干扰。
2.本地磁盘
虽然Spark可以在内存中执行大量的计算,但它仍然使用本地磁盘来存储不适合RAM的数据,以及在stage之间,也即shuffle的中间结果。我们建议每个节点至少有4-8块磁盘,并且不需要RAID,仅仅是独立的磁盘挂在节点。在Linux中,使用noatime选项安装磁盘,以减少不必要的写入。在spark任务中,spark.local.dir配置可以十多个磁盘目录,以逗号分开。如果你运行在hdfs上,与hdfs保持一致就很好。
使用noatime选项安装磁盘,要求当挂载文件系统时,可以指定标准Linux安装选项(noatime),这将禁用该文件系统上的atime更新。磁盘挂在命令:
mount -t gfs BlockDevice MountPoint -o noatime
BlockDevice 指定GFS文件系统驻留的块设备。
MountPoint 指定GFS文件系统应安装的目录。
例子:
mount -t gfs /dev/vg01/lvol0 /gfs1 -o noatime
3.内存
单台机器内存从8GB到数百GB,spark都能运行良好。在所有情况下,我们建议仅为Spark分配最多75%的内存;留下其余的操作系统和缓冲区缓存。
需要多少内存取决于你的应用程序。要确定你的应用的特定数据集需要多大内存,请加载部分数据集到内存,然后在Spark UI的Storage界面去看它的内存占用量。
请注意,内存使用受到存储级别和序列化格式的极大影响 - 有关如何减少内存使用的技巧,请参阅另一篇调优的文章。
最后,请注意,对于超过200GB的内存的机器JAVA VM运行状态并不一直表现良好。如果你买的机器内存超过了200GB,那么可以在一个节点上运行多个worker。Spark Standalone模式下,你可以在配置文件conf/spark-env.sh中设置SPARK_WORKER_INSTANCES的值来设置单节点worker的数目。也可以设置SPARK_WORKER_CORES参数来设置每个Worker的cpu数目。
4.网络
根据以往的经验,假如数据是在内存中,那么spark的应用的瓶颈往往就在网络。用10 Gigabit或者更高的网络,是使spark应用跑的最更快的最佳方式。特别是针对“distributed reduce”应用,如group-bys,reduce-bys和sql joins,就表现的更加明显。在任何给定的应用程序中,你可以通过spark ui查看spark shuffle过程夸网络传输了多少数据。
5.cpu
即使每台机器几十个cpu,spark也可以很好的扩展,因为他在线程之间执行最小的共享cpu。你应该每台机器至少配置8-16个内核。根据cpu负载,可能需要更多的cpu:一旦数据在内存中,大多数应用程序的瓶颈就在CPU和内存。

spark调优系列之内存和GC调优
本文基于spark1.6讲解。
1.基本概述
调优内存的使用主要有三个方面的考虑:对象的内存占用量(你可能希望整个数据集都适合内存),访问这些数据的开销,垃圾回收的负载。
默认情况下,java的对象是可以快速访问的,但是相比于内部的原始数据消耗估计2-5倍的空间。主要归于下面三个原因:
1),每个不同的Java对象都有一个“对象头”,它大约是16个字节,包含一个指向它的类的指针。对于一个数据很少的对象(比如一个Int字段),这可以比数据大。
2),Java字符串在原始字符串数据上具有大约40字节的开销(因为它们将它们存储在一个Chars数组中,并保留额外的数据,例如长度),并且由于String的内部使用UTF-16编码而将每个字符存储为两个字节。因此,一个10个字符的字符串可以容易地消耗60个字节。
3),常用集合类(如HashMap和LinkedList)使用链接的数据结构,其中每个条目都有一个“包装器”对象(例如Map.Entry)。该对象不仅具有头部,还包括指针(通常为8个字节)到列表中的下一个对象。
4),原始类型的集合通常将它们存储为“boxed”对象,如java.lang.Integer。
本节将从Spark的内存管理概述开始,然后讨论用户可以采取的具体策略,以便在他/她的应用程序中更有效地使用内存。具体来说,我们将描述如何确定对象的内存使用情况,以及如何改进数据结构,或通过以序列化的格式存储数据。然后我们将介绍调优Spark的缓存大小和Java垃圾回收器。
2.spark的内存管理概述
Spark中的内存使用大部分属于两类:执行和存储。运行内存指的是用于计算的,shuffle,joins,sorts和aggregations,然后存储内存主要用于缓存和在集群中传播的内部数据。在spark内部,存储器和执行器共享一个统一的区域(M)。当没有使用执行器内存的时候,存储器可以获取所有可用的执行器内存,反之亦然。如果有需要执行器可以驱逐存储占用,但是仅仅当内存小于一个阈值®的时候才会发生。换句话说,R描述了M内部的一个子区域,R中的缓存永远不会被清除。由于实施的复杂性,存储内存不得驱逐执行内存。该设计保证了几个理想的性能。
首先,不使用缓存的应用程序可以将整个空间用于执行,从而避免不必要的磁盘溢写。
其次,使用缓存的应用程序可以保留最小的存储空间(R),其中数据块不受驱逐。
最后,这种方法为各种工作负载提供了合理的开箱即用性能,而不需要用户掌握内部如何分配内存的专业知识。
虽然有两个相关配置,但典型用户不需要调整它们,因为默认值适用于大多数工作负载:
1),spark.memory.fraction将M的大小表示为(JVM堆空间 - 300MB)的一部分(默认为0.75,新版本如spark2.2改为0.6)。剩余的空间(25%,对应的新版本是0.4)用于用户数据结构,Spark中的内部元数据,并且在稀疏和异常大的记录的情况下保护OOM错误。
2),spark.memory.storageFraction表示R的大小作为M的一部分(默认为0.5)。R是M内的存储空间,其中缓存的块免于被执行器驱逐。

3.确定内存的消耗
最好的方式去计算一个数据的的内存消耗,就是创建一个RDD,然后加入cache,这样就可以在web ui中Storage页面看到了。页面会告诉你,这个RDD消耗了多少内存。
要估计特定对象的内存消耗,请使用SizeEstimator的估计方法。这对于尝试使用不同的数据布局来修剪内存使用情况以及确定广播变量在每个执行程序堆中占用的空间量非常有用。
4.调优数据结构
减少内存消耗的第一种方法是避免使用增加负担的java特性,例如基于指针的数据结构和包装对象。下面几种方法可以来避免这个。
1,将数据结构设计为偏好对象数组和原始类型,而不是标准的Java或Scala集合类(例如HashMap)。fastutil库(http://fastutil.di.unimi.it/)为与Java标准库兼容的原始类型提供方便的集合类。
2,尽可能避免使用有很多小对象和指针的嵌套结构。
3,针对关键词,考虑使用数字ID或者枚举对象而不是字符串。
4,如果您的RAM少于32 GB,请设置JVM标志-XX:+ UseCompressedOops使指针为四个字节而不是八个字节。您可以在spark-env.sh中添加这些选项。
5.序列化RDD
尽管进行了调优,当您的对象仍然太大而无法有效存储时,一个简单的方法来减少内存使用是使用RDD持久性API中的序列化StorageLevel(如MEMORY_ONLY_SER)以序列化形式存储它们。Spark将会将每个RDD分区存储为一个大字节数组。以序列化形式存储数据的唯一缺点是数据访问变慢,因为必须对每个对象进行反序列化。如果您想以序列化形式缓存数据,我们强烈建议使用Kryo,因为它会使数据比java序列化后的大小更小(而且肯定比原Java对象更小)。
6.垃圾回收调优
(1).基本介绍
当你程序的RDD频繁的变动的时候,垃圾回收将会是一个问题。RDD的一次读入,然后有很多种基于它的计算,这种情况下垃圾回收没啥问题。当JAVA需要驱逐旧的对象,为新对象腾出空间的时候,需要跟踪所有Java对象并找到无用的对象。要记住的要点是,垃圾收集的成本与Java对象的数量成正比,因此使用较少对象的数据结构(例如,Ints数组,代替LinkedList)将大大降低了成本。一个更好的方法是以序列化形式持久化对象,如上所述:每个RDD分区将只有一个对象(一个字节数组)。在尝试其他技术之前,如果GC是一个问题,首先要尝试的是使用序列化缓存。
由于任务的运行内存和RDD的缓存内存的干扰,GC也会是一个问题。
(2).测量GC的影响
GC调优的第一步是收集关于垃圾收集发生频率和GC花费的时间的统计信息。通过将-verbose:gc -XX:+ PrintGCDetails -XX:+ PrintGCTimeStamps添加到Java选项来完成。下次运行Spark作业时,每当垃圾收集发生时,都会看到在工作日志中打印的消息。请注意,这些日志将在您的群集的Executor节点上(在其工作目录中的stdout文件中),而不是您的driver功能中。
(3).高级GC调优
为了进一步调整垃圾收集,我们首先需要了解一些关于JVM内存管理的基本信息<详细的请看:JVM的垃圾回收算法>:
1),java的堆内存分为两个区域新生代和老年代。新生代保存的是生命周期比较短的对象,老年代保存生命周期比较长的对象。
2),新生代又分为了三个区域(Eden, Survivor1, Survivor2)。
3),垃圾收集过程的简化说明:当Eden已满时,Eden上运行了一个minor GC,并将Eden和Survivor1中存在的对象复制到Survivor2。Survivor 将进行交换。如果一个对象足够老,或者Survivor2已满,则会移动到老年代。最后当老年代接近满的时候,会触发full GC。
Spark应用程序GC调优的目标是,确保生命周期比较长的RDD保存在老年代,新生代有足够的空间保存生命周期比较短的对象。这有助于避免触发Full GC去收集task运行期间产生的临时变量。下面列举几个有用的步骤:
1),通过收集垃圾回收信息,判断是否有太多的垃圾回收过程。假如full gc在一个task完成之前触发了好几次,那说明运行task的内存空间不足,需要加内存。
2),在gc的统计信息中,如果老年代接近满了,减少用于缓存的内存(通过减小spark.memory.storageFraction)。缓存较少的对象比降低运行速度对我们来说更有好处。另外,可以考虑减少年轻代。可以通过减小-Xmn参数设置的值,假如使用的话。假如没有设置可以修改JVM的NewRation参数。大多数JVMs默认值是2,意思是老年代占用了三分之二的总内存。这个值要足够大,相当于扩展了spark.memory.fraction.
3),如果有太多的minor gc,较少的major gc,增加Eden区内存会有帮助。将Eden区内存设置的比task运行估计内存稍微大一些。如果Eden区大小确定为E,那就将新生代的内存设置为-Xmn=4/3E,按比例增加内存是考虑到survivor区所占用的内存。
4),尝试通过设置-XX:+UseG1GC垃圾回收器为G1。在垃圾回收器是瓶颈的一些情况下,它可以提高性能。请注意,对于大的Executor堆,通过使用-XX:G!HeapRegionSize去增大G1的堆大小,显得尤为重要。
5),例如,如果您的任务是从HDFS读取数据,则可以使用从HDFS读取的数据块的大小来估计任务使用的内存量。请注意,解压缩块的大小通常是块大小的2或3倍。所以如果我们希望有3或4个任务的工作空间,HDFS块的大小是64 MB,我们可以估计Eden的大小是4 * 3 * 64MB。
5),监控垃圾收集的频率和时间如何随着新设置的变化而变化。
经验表明,GC调整的效果取决于您的应用程序和可用的内存量。下面的链接里有更多的在线描述的调优的选项,但在高层次上,管理GC的全面发生频率有助于减少开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值