上一篇介绍了一些关于提交Spark任务参数的调优,本片文章来聊聊一个Spark作业中RDD的重构,以及一些复用的RDD持久化的常用策略。
首先给出两个RDD执行过程图形
类型1如上图所示,当第一次对RDD2执行算子获得RDD3的时候,首先会从RDD1开始计算,然后执行RDD2,最后计算得到RDD3。对于RDD4同样会按照RDD3的流程去执行一遍RDD1和RDD2的两个算子,想想如果数据量非常大的话,那将是完全不敢想象的灾难。随后,我们会根据这种情况进行优化,接下来看看另外一种情况。
类型2如上图所示,从一个RDD到几个不同的RDD,算子和计算逻辑其实完全是一样的,由于人为原因导致计算了多次而得到了多个RDD,例如上图中的RDD1到RDD2和RDD4。
针对上面出现的两种情况,我们可以做出如下一些优化。
1.尽量去复用已经产生的RDD,可以抽象成一个共同的RDD,以供后面的RDD计算时反复使用。
2.对于抽象出来的公共的RDD一定要做持久化操作(其实就是做一个缓存操作,后面会详细介绍几种缓存的方式)
下面介绍一下对于公共的RDD如何做持久化操作及缓存(持久化)的方式。
第一个问题:如何做持久化操作?
答案:直接调用cache()或者presist()方法对指定的RDD进行缓存(持久化)操作,同时在方法中指定缓存的策略。
第二个问题:缓存(持久化)的方式有哪些?
答案:MEMORY_ONLY /* 数据全部缓存在内存中 */
MEMORY_ONLY_2 /*数据以双副本的方式缓存在内存中*/
MEMORY_ONLY_SER /* 数据全部以序列化的方式缓存到内存中*/
MEMORY_AND_DISK /* 数据一部分缓存在内存中,一部分持久化到磁盘上*/
MEMORY_AND_DISK_SER /* 数据以序列化的方式一部分缓存在内存中,一部分持久化到磁盘上*/
MEMORY_AND_DISK_2 /*数据以双副本的方式一部分缓存到内存中,一部分持久化到磁盘上*/
DISK_ONLY /* 数据全部持久化到磁盘上*/
那么新的问题又出现了,为什么会出现这么多的缓存数据的策略呢?既然出现了那么肯定有其出现的道理(又讲了一串废话),接下来介绍一下我们在代码中该如何选取合适我们代码的缓存策略。
先说说几个概念,看完这几个概念之后,我相信大家自然就明白了该如何选取缓存策略了。
1.持久化
持久化的意思就是说将RDD的数据缓存到内存中或者持久化到磁盘上,只需要缓存一次,后面对这个RDD做任何计算或者操作,可以直接从缓存中或者磁盘上获得,可以大大加快后续RDD的计算速度。
2.持久化+可序列化
如果按照正常的策略将数据直接缓存到内存中,如果内存不够大的话就会导致内存占用过大,从而导致OOM(内存溢出)情况的出现。针对这种情况,当内存无法支撑公共RDD数据完全存入内存的时候,就可以考虑将RDD数据序列化成一个大的字节数组(一个大的对象),就可以大大降低内存的占用率,由于序列化和反序列化需要消耗一定的性能,所以比直接持久化到内存的方式性能稍微差一些。
3.内存+磁盘+序列化
如果序列化纯内存方式,还是导致OOM,内存溢出;就只能考虑磁盘的方式,内存+磁盘的普通方式(无序列化)。如果还是不行的话,那么就采用内存+磁盘+序列化的方式缓存数据。
4.持久化+双副本机制
为了数据的高可靠性,而且内存充足,可以使用双副本机制进行数据持久化,这种方式可以保证持久化数据的安全性。因为如果只有一个副本,机器宕机缓存数据就会丢失,那么就会导致还得重新计算一次;持久化的每个数据单元,如果有两个副本,另一个副本存储放在其他节点上面;从而进行容错;一个副本丢了,不用重新计算,还可以使用另外一份副本。这种方式,仅仅针对你的内存资源极度充足。、
通过上面的几个概念介绍,我相信大家应该完全明白了该如何选取缓存策略了吧。
本片文章到这里基本就完了,后面会不定期的更新关于spark调优的一些文章,希望可以一块讨论学习。欢迎关注。
如需转载,请注明:
网友评论