浅谈我对Spark的理解

学习Spark无非出于三点,感兴趣,有需求,他很火。

但对于我来说,从刚开始接触Spark不是因为他很火,而是因为Scala这门语言。正是Scala这门语言的许多特性让我十分爱不释手,我才开始硬着头皮去钻Scala各种独特的用法,也同时硬着头皮去学习Spark,最后沉迷于用各种巧妙地方法写Spark,但是由于缺少数据支持,我也只是停留在写出优美简洁的代码、分析分析Spark源码而已,这根本不算是搞Spark,当时所谓调优,现在看来也只不过是小孩子过家家而已。

后来,我进了某东,接触了真正的大数据,可想而知,之前锻炼一年多的Spark代码风格其实用处不是特别大,因为你即使代码写的天花乱坠,你的老大一句话“你得让大众看得懂”,你就得改回传统风格。那么过后我反思自己,说是搞Spark,你究竟搞懂了什么?

随着Spark经验的不断丰富,我越来越发现,之前的我,其实是钻进了象牙塔里,完全忽略了Spark的本质。现在看来,Spark他是一个分布式计算引擎,其实我们只需要单纯的把它看做一种工具,各个企业只是为了解决某种特定问题才会使用这个工具,这特定问题其实也就是说,我有有限的资源,想快速计算大规模数据,仅此而已。

所以通过这个问题,我们可以直接说,如果想用这个工具,那关注点就无非是两点:占用的资源,计算的速度。当然,资源占用少、计算速度快,那这个任务性能也就越高,但这二者一般来说都是成反比相矛盾的,需要我们根据实际需求来权衡。那么具体到应用开发层面的话,无论是代码的编写,还是之后的参数调整、优化,开发者都应该时刻关注这两个点。

代码编写的话,一段好的代码与一段坏的代码运行起来可能有几倍、几十倍、甚至坏的代码无论怎么调参都无法跑通,这个就需要除了一些开发准则之外,开发者平时积累的经验了。另一方面,对于参数调优这块,它直接影响了资源占用与计算速度,同样,同一个程序,同样的集市配置,不同的参数运行起来性能也可能差个十万八千里,当然也有可能差的不多。以上两点其实在各个博客上都有相应的介绍与解析,在这里不加赘述。

抛开Spark这个名字,揭开各种浮夸、炒作的外表,其实它的本质就是运行在集市上的JVM程序,仅此而已,所以在学习他的时候不得不去探索一下他的内存模型(其实之前所说的两个关注点的内在影响因素就是他),如果对统一内存模型不了解,那么对Spark开发一定也只是一知半解,只知其然而不知其所以然,最典型的例子就是调参的时候只知道–conf xxx.xxx.xxx=xxx,而不知作用在哪里,就好像一个不知道JVM内存模型的人去做内存调优一样。所以越到后来我就越发现,无论是什么技术,千万不要钻进他的象牙塔里,浪费时间,以后还会有更多更新更好的技术等着我们,今天的技术也会没落甚至淘汰,只有掌握其根本原理,身为技术人才会摆脱‘35岁假说’。

最后再跟大家分享一个我近些天解决的一个Spark典型的问题,分析过程请各大佬指点:

需求:hive程序运行慢,运行4小时,每天调度一次,十分影响其子任务的运行,需要优 化,任务的逻辑很简单,表A(几十个字段,joinKey有重复)join 表B(一个字段,只有一个joinKey 无重复),根据null填充0/1

解决过程:

1.直接将原sql用sparksql跑,OOM

2.调参,降低并行度,增加内存,增加数据分片,OOM

3.输入数据其实不是很大,总资源分配的已经足够饱和。

4.观察运行记录,每次只有几个task失败,怎样重提交都失败,其他任务跑的很快,所以考虑data skew

5.后统计两表数据量,左表3千万条,2T,数据倾斜严重,统计joinK数量top1是200w,top5在几十万,半数以上在10以下,其余不超过一千。右表4亿条,1g。

6.最开始考虑有两种方案,一种方案左表切分成n份,右表切分成m份,做n x m次join最后union;另一种通过以每组joinK数量n的对数logn分组,共分成n/logn组,为每条数据打上组号,进行组内局部join,继续取对数、分组、编号,直到k数量达到较小的量,去除编号,进行最终的join;考虑这两种方案解决data skew

7.以上两种方案虽然相当于切分了数据,但涉及更多的join,考虑是否有方法去掉join?发现了以下条件:左表重复k较多,包含的不重复的k远其实小于1000w,而右表不重复几亿数据中其实大部分是无用的。

于是取左表k去重得到集合A,与右表做交集得到集合I,那么左表 join I与原join等价。发现I数据量与A数据量比值超过0.9,于是回头取A与右表差集E,发现E数据量为70w,MB级别数据,则直接进行广播,消除join。

这样一来原join转换成判断左表的k是否在E中,在,则说明k不在右表中,则与原算法join出null等价,那么填充0,若不在,说明k在右表中,与原算法join出非空等价,填充1

9.用Sparksql的udf注册函数,输入k,返回0/1,用到的是contains,Scala中HashSet的contains是常数时间,所以在E collect后进行to[HashSet],并广播出去

10.对于一般性数据,有可能E的量大于I,由于 E∪I=去重的所有k,于是进行判断,E的数量如果小于I,即小于全部的一半,那就用E,反之用I,用I去判断k无非就是与用E填充0、1条件相反而已,即k在I中说明在右表中,则填充1,反之0

11.最后得到结果集不直接写入hive,而是优化为先存hdfs,再load data到hive表

12.这个时候其实已经到了17min

13.最后发现存hdfs时仍有轻微倾斜,则在save前调用dateframe的repartition

14.通过对多次计算的df进行缓存,同时用完则unpersist,再对执行脚本的参数进行适当的调整,最后优化到13min,资源占用3T内存1200core.

总结:其实在做交集或差集的时候底层也会涉及到join操作,但是这个时候的join由于左表是去重后的原左表,所以他不涉及到data skew,那么也就不成问题了。以上分析过程只是针对这个具体问题才可以这么解决。在工作过程中,我们会遇到很多很多问题,那么具体问题具体分析的能力,就是靠我们自己去一点点磨练了。

猜你喜欢

转载自blog.csdn.net/github_37835908/article/details/81740571
今日推荐