【大数据分析】Spark调优方案之开发调优

在使用Spark数据分析框架时,并不能只关注Spark提供的分布式计算能力。熟悉Spark底层,并以此为基础进行不同维度的优化能够很大程度上提高数据分析的效率。开发调优需要数据分析工程师了解一些Spark开发基本原则,包括:RDD的lineage设计、算子的合理使用、特殊操作的优化等。

避免使用重复的RDD

在开发Spark程序时,首先需要基于某个数据源(比如Hive表或HDFS文件)创建一个初始的RDD,接着对这个RDD执行某算子操作,然后得到下一个RDD。以此类推,循环往复,直到计算出最终需要的结果。在这个过程中,多个RDD会通过不同的算子操作(比如map、reduce等)串起来,这个“RDD串”,就是RDD lineage,对于同一份数据,只应该创建一个RDD,不能创建多个RDD来代表同一份数据。

复用一个RDD

如果有一个RDD的数据格式是key-value类型的,另一个是单value类型的,而这两个RDD的value数据是完全一样的,那么此时可以只使用key-value类型的那个RDD,因为其中已经包含另一个的数据了。对于类似这种多个RDD的数据有重叠或者包含的情况,应尽可能复用一个RDD,这样可以尽可能地减少RDD的数量,从而尽可能减少算子执行的次数。

持久化

如果一个程序的整个过程没有一次持久化,其默认的运行方式如下图所示。
在这里插入图片描述
Spark程序在遇到Action算子时,才会被触发。上图Data A在遇到第一个Action算子才会被真正计算,直到计算出Data B。此时当Data B遇到第二个Action时,会重新计算一次Data A,相当于Data A在遇到第一个Action前的计算过程被执行了两次。这种方式在性能上会比较差。
所以建议对多次使用的RDD进行持久化,Spark会根据持久化策略,将RDD中的数据保存到内存或者磁盘中。以后每次对这个RDD进行算子操作时,都会直接从内存或磁盘中提取持久化的RDD数据,然后执行算子,而不会从源头处重新计算一遍这个RDD。
常用的持久化策略:

策略描述
MEMORY_ONLY不序列化数据,将数据保存在内存中。如果内存不够存放所有的数据,则数据就不会进行持久化。使用cache()方法时默认开启此策略
MEMORY_AND_DISK不序列化数据,优先尝试将数据保存在内存中。如果内存不够,会将剩余数据写入磁盘文件中,下次对这个RDD执行算子时,持久化在磁盘文件中的数据会被读取出来。
MEMORY_ONLY_SER与MEMORY_ONLY类似。区别在于它会先将RDD的每个partition序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。
MEMORY_AND_DISK_SER与MEMORY_AND_DISK类似。区别在于会将RDD的每个partition序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。
DISK_ONLY不序列化数据,将数据全部写入磁盘文件中。
MEMORY_ONLY_2, MEMORY_AND_DISK_2, 等等.对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于进行容错。假如某个节点挂掉,节点的内存或磁盘中的持久化数据丢失了,那么后续对RDD计算时还可以使用该数据在其他节点上的副本。如果没有副本的话,就只能将这些数据从源头处重新计算一遍了。

尽可能避免使用shuffle类算子

执行shuffle算子时,各个节点上的相同key都会先写入本地磁盘文件中,然后其他节点需要通过网络传输拉取各个节点上的磁盘文件中的相同key。此时如果进行的是聚合操作,可能会因为一个节点上处理的key过多,导致内存不够存放,进而溢写到磁盘文件中。因此这个过程会出现大量的IO操作,以及数据的网络传输操作。磁盘IO和网络数据传输也是shuffle性能较差的主要原因。
因此应该避免使用reduceByKey、join、distinct、repartition等会进行shuffle的算子,尽量使用map类的非shuffle算子。

使用预聚合的shuffle操作

使用groupbykey时
在这里插入图片描述
使用reduceBykey时
在这里插入图片描述

使用高性能的算子

groupbykey ->reducebykey/aggregatebykey
map->mappartitions
foreach->foreachpartitions
repartition &sort->repartitionandsortwithinpartitions

广播大变量

有时会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如100M以上的大集合),那么此时应该使用Spark的广播(Broadcast)功能来提升性能,因为变量在默认情况下会复制到各个节点上运行,RDD可以通过collect方法转换成需要broadcast的变量。

使用Kryo优化序列化

在Spark中,主要有三个地方涉及到了序列化:
(1)在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输
(2)将自定义的类型作为RDD的泛型类型时(比如JavaRDD,Student是自定义类型),所有自定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类必须实现Serializable接口。
(3)使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个partition都序列化成一个大的字节数组。
对于以上这些情况,可以通过使用Kryo序列化类库。
Spark默认使用的是Java的序列化机制,也就是使用ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高10倍左右。Spark之所以默认没有使用Kryo作为序列化类库,是因为Kryo要求最好注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。

// 创建SparkConf对象。
val conf = new SparkConf().setMaster(...).setAppName(...)
// 设置序列化器为KryoSerializer。
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 注册要序列化的自定义类型。
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))

优化数据结构

避免使用对象,字符串,集合这三种数据类型,因为它们比较消耗内存,但其实 这个很难做到。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值