Spark常见的问题以及解决方案
Spark为什么比Hadoop要快?
Spark比hadoop快的原因,我认为主要是spark的DAG机制优于hadoop太多,spark的DAG机制以及RDD的设计避免了很多落盘的操作,在窄依赖的情况下可以在内存中完成end to end的计算,相比于hadoop的map reduce编程模型来说,少了很多IO的开销。其次还有几个其他方面的助攻
- shuffl机制的不同
虽然2者都会遇到shuffle的场景,但是spark的shuffle更加先进。对于MapReduce,其在Shuffle时需要花费大量时间进行排序,排序在MapReduce的Shuffle中似乎是不可避免的。而spark在shuffle的时候,不一定会用到排序,有可能会使用Hash的形式 - mr是多进程,spark则是多线程
线程相比于进程,少了很多CPU调度下的资源切换。
spark的shfflue机制
上面说到spark比mr优越的一个原因也是shffle机制的不同。具体来说,spark的shffle机制从它诞生到现在一共有这么几种。
shuffle的意思就是打乱数据顺序使得相同的key被统一到同一个分区。

spark中只有2类stage:ShuffleMapStage和ResultStage。但其总的过程包含有map阶段(shuffle write)和reduce阶段(shuffle read),两个阶段位于不同的stage中。
ResultStage基本上对应代码中的action算子, 而ShuffleMapStage 的则伴随着 shuffle IO。spark的mr和hadoop的mapreduce不是一个东西。map端task和reduce端task不在相同的stage中,map task位于ShuffleMapStage,reduce task位于ResultStage。map task会先执行,将上一个stage得到的最后结果写出,后执行的reduce task拉取上一个stage进行合并。
对于一次shuffle,map过程和reduce过程都有若干个task来执行。对于task的个数,map端的task个数和RDD的partition个数相同,reduce 端的 stage 默认取spark.default.parallelism 这个配置项的值作为分区数,如果没有配置,则以 map 端的最后一个 RDD 的分区数作为其分区数,分区数就将决定 reduce 端的 task 的个数。
在Spark的源码中,负责shuffle过程的执行、计算和处理的组件主要就是ShuffleManager。
spark1.2
在Spark 1.2以前,默认的shuffle计算引擎是HashShuffleManager。它的计算模式比较的简单粗暴,详细如下:
- shuffle write阶段
这个阶段将stage中每个task处理的数据根据算子进行“划分”。比如reduceByKey,就是对相同的key执行hash算法,从而将相同都写入同一个磁盘文件中,而每一个磁盘文件都只属于下游stage的一个task。在将数据写入磁盘之前,会先将数据写入内存缓冲中,当内存缓冲填满之后,才会溢写到磁盘文件中去。
- shuffle read阶段
stage的每一个task就需要将上一个stage的计算结果中的所有相同key,从各个节点上通过网络都拉取到自己所在的节点上,然后进行key的聚合或连接等操作。由于shuffle write的过程中,task给下游stage的每个task都创建了一个磁盘文件,因此shuffle read的过程中,每个task只要从上游stage的所有task所在节点上,拉取属于自己的那一个磁盘文件即可。
那么针对这种简单粗暴的HashShuffleManager,有着一个非常严重的弊端:会产生大量的中间磁盘文件,这样大量的磁盘IO操作会很影响性能。磁盘文件的数量由下一个stage的task数量决定,即下一个stage的task有多少个,当前stage的每个task就要创建多少份磁盘文件。比如下一个 stage 总共有 100 个 task,那么当前 stage 的每个 task 都要创建 100 份磁盘文件,如果当前stage有50个 task,那么总共会建立5000个磁盘文件。
优化之后的HashShuffleManager
由于原版的HashShuffleManager,HashShuffleManager后期进行了优化,这里说的优化是指可以设置一个参数,
spark.shuffle.consolidateFiles=true。该参数默认值为false,通常来说如果我们使用HashShuffleManager,那么都建议开启这个选项。开启consolidate机制之后,在shuffle write过程中,task不会为下游stage的每个task创建一个磁盘文件,此时会出现shuffleFileGroup的概念,每个 shuffleFileGroup会对应一批磁盘文件,磁盘文件的数量与下游stage的task数量是相同的。而此时就会根据Executor数,并行执行task。第一批并行执行的每个task都会创建一个shuffleFileGroup,并将数据写入对应的磁盘文件内。当Executor执行完一批task,接着执行下一批task时,下一批task就会复用之前已有的shuffleFileGroup,将数据写入已有的磁盘文件中,而不会写入新的磁盘文件中。即运行在同一个Executor的task会复用之前的磁盘文件。 这样就可以有效将多个task的磁盘文件进行一定程度上的合并,从而大幅度减少磁盘文件的数量,进而提升shuffle write的性能。

当前spark默认使用的SortShuffleManager
在Spark 1.2以后的版本中,默认的ShuffleManager改成了SortShuffleManager。SortShuffleManager的运行机制主要分成两种,一种是普通运行机制,另一种是bypass运行机制。当shuffle read task的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(默认为200),就会启用bypass机制。
普通运行模式
在普通模式下,数据会先写入一个内存数据结构中,此时根据不同的shuffle算子,可以选用不同的数据结构。如果是由聚合操作的shuffle算子,就是用map的数据结构(边聚合边写入内存),如果是join的算子,就使用array的数据结构(直接写入内存)。等到内存容量到了临界值就准备溢写到磁盘。在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序,排序之后,会分批将数据写入磁盘文件,每批次默认1万条数据。此时task往磁盘溢写,会产生多个临时文件,最后会将所有的临时文件都进行合并,合并成一个大文件。最终只剩下两个文件,一个是合并之后的数据文件,一个是索引文件,标识了下游各个task的数据在文件中的start offset与end offset。下游的task根据索引文件读取相应的数据文件。需要注意的是,此处所说的两个文件,是指上游一个task生成两个文件,而非所有的task最终只有两个文件。

bypass机制
触发bypass机制的条件:
- shuffle map task的数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认200)
- 不是聚合类的shuffle算子(比如groupByKey)
排序的时间复杂度最高不能优于O(nlogn),那么如果将排序的时间复杂度省下,那么shuffle性能将会提升很多。bypass机制与普通SortShuffleManager运行机制的不同在于,bypass机制就是利用了hash的O(1)时间复杂度取代了排序的操作开销,提升了这部分的性能。
task会为每个下游task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,将key写入对应的磁盘文件之中。如上,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。

针对shuffle机制的调优策略
- spark.shuffle.file.buffer
该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小(默认是32K)。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数。
- spark.reducer.maxSizeInFlight:
该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。
- spark.shuffle.io.maxRetries & spark.shuffle.io.retryWait:
spark.shuffle.io.retryWait:huffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。(默认是3次)
spark.shuffle.io.retryWait:该参数代表了每次重试拉取数据的等待间隔。(默认为5s)
一般的调优都是将重试次数调高,不调整时间间隔。
- spark.shuffle.memoryFraction:
该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例。
- spark.shuffle.manager
该参数用于设置shufflemanager的类型(默认为sort)。Spark1.5x以后有三个可选项:
Hash:spark1.x版本的默认值,HashShuffleManager
Sort:spark2.x版本的默认值,普通机制,当shuffle read task 的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数,自动开启bypass 机制
tungsten-sort:
复制代码spark.shuffle.sort.bypassMergeThreshold
参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作。
调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些
- spark.shuffle.consolidateFiles:
如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,也就是开启优化后的HashShuffleManager。
如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。
Spark基石--RDD
RDD又叫弹性分布式数据集,表示一个只读的包含多个并行分区的集合。其中
- 弹性意味着:当计算过程中内存不足时可刷写到磁盘等外存上,可与外存做灵活的数据交换
- 分布式:分区使得可以在多个机器上并行计算
- 一组只读的、可分区的分布式数据集合,集合内包含了多个分区
一个最简单的rdd至少要包含以下几个东西,这些都被定义在spark core组件的rdd的抽象类中

- 一组分区
- 定义在这个分区上的操作
- 当前rdd和其他rdd的依赖
可选的有 - Preferred Location
是一个列表,用于存储每个 Partition 的优先位置。对于每个 HDFS 文件来说,这个列表保存的是每个 Partition 所在的块的位置,也就是该文件的「划分点」
- 分区方式
RDD 的分区方式主要包含两种:Hash Partitioner 和 Range Partitioner,这两种分区类型都是针对 Key-Value 类型的数据,如是非 Key-Value 类型则分区函数为 None。Hash 是以 Key 作为分区条件的散列分布,分区数据不连续,极端情况也可能散列到少数几个分区上导致数据不均等;Range 按 Key 的排序平衡分布,分区内数据连续,大小也相对均等。
那么我们把rdd画出来之后它大概是这个样子

图所示是 RDD 的内部结构图,它是一个只读、有属性的数据集。它的属性用来描述当前数据集的状态。
数据集data由数据的分区(partition)组成,并由(block)映射成真实数据。
RDD 的主要属性可以分为 3 类:与上述图中代码对应,细节上来说主要有:
- 与其他 RDD 的关系(parents、dependencies)
- 数据(partitioner、checkpoint、storage level、iterator 等)
- RDD 自身属性(sparkcontext、sparkconf)
RDD 自身属性
从自身属性说起,自身属性包含spark上下文,以及初始化sc时候的配置信息conf
SparkContext 是 Spark job 的入口,由 Driver 创建在 client 端, 并向集群资源管理器如yarn等申请资源这些都是由sc完成
conf就是我们设置的一些job参数
数据集list
RDD 内部的数据集合在逻辑上和物理上被划分成多个小子集合,这样的每一个子集合我们将其称为分区(Partitions),分区的个数会决定并行计算的粒度,有多少分区就有多少的task,而每一个分区数值的计算都是在一个单独的任务中进行的,因此并行任务的个数也是由 RDD分区的个数决定的。但事实上 RDD 只是数据集的抽象,分区内部并不会存储具体的数据。Partition 类内包含一个 index 成员,表示该分区在 RDD 内的编号,通过 RDD 编号+分区编号可以确定该分区对应的唯一块编号,再利用底层数据存储层提供的接口就能从存储介质(如:HDFS、Memory)中提取出分区对应的数据。
RDD 的分区方式主要包含两种:Hash Partitioner 和 Range Partitioner,这两种分区类型都是针对 Key-Value 类型的数据,如是非 Key-Value 类型则分区函数为 None。Hash 是以 Key 作为分区条件的散列分布,分区数据不连续,极端情况也可能散列到少数几个分区上导致数据不均等;Range 按 Key 的排序平衡分布,分区内数据连续,大小也相对均等。
Preferred Location 是一个列表,用于存储每个 Partition 的优先位置。对于每个 HDFS 文件来说,这个列表保存的是每个 Partition 所在的块的位置,也就是该文件的「划分点」。
Storage Level 是 RDD 持久化的存储级别,RDD 持久化可以调用两种方法:cache 和 persist:persist 方法可以自由的设置存储级别,默认是持久化到内存;cache 方法是将 RDD 持久化到内存,cache 的内部实际上是调用了persist 方法,由于没有开放存储级别的参数设置,所以是直接持久化到内存。

checkpoint
Checkpoint 是 Spark 提供的一种缓存机制,当需要计算依赖链非常长又想避免重新计算之前的 RDD 时,可以对 RDD 做 Checkpoint 处理,检查 RDD 是否被物化或计算,并将结果持久化到磁盘或 HDFS 内。Checkpoint 会把当前 RDD 保存到一个目录,要触发 action 操作的时候它才会执行。在 Checkpoint 应该先做持久化(persist 或者 cache)操作,否则就要重新计算一遍。若某个 RDD 成功执行 checkpoint,它前面的所有依赖链会被销毁。
与 Spark 提供的另一种缓存机制 cache 相比:cache 缓存数据由 executor 管理,若 executor 消失,它的数据将被清除,RDD 需要重新计算;而 checkpoint 将数据保存到磁盘或 HDFS 内,job 可以从 checkpoint 点继续计算。Spark 提供了 rdd.persist(StorageLevel.DISK_ONLY) 这样的方法,相当于 cache 到磁盘上,这样可以使 RDD 第一次被计算得到时就存储到磁盘上,它们之间的区别在于:persist 虽然可以将 RDD 的 partition 持久化到磁盘,但一旦作业执行结束,被 cache 到磁盘上的 RDD 会被清空;而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹,如果不被手动 remove 掉,是一直存在的。
spark血统
一个作业从开始到结束的计算过程中产生了多个 RDD,RDD 之间是彼此相互依赖的,我们把这种父子依赖的关系称之为「血统」。RDD 只支持粗颗粒变换,即只记录单个块(分区)上执行的单个操作,然后创建某个 RDD 的变换序列(血统 lineage)存储下来。
变换序列指每个 RDD 都包含了它是如何由其他 RDD 变换过来的以及如何重建某一块数据的信息。因此 RDD 的容错机制又称「血统」容错。 要实现这种「血统」容错机制,最大的难题就是如何表达父 RDD 和子 RDD 之间的依赖关系。
分区机制
RDD 的分区机制有两个关键点:一个是关键参数,即 Spark 的默认并发数 spark.default.parallelism;另一个是关键原则,RDD 分区尽可能使得分区的个数等于集群核心数目。
设置的时候可以在spark conf下设置
- spark.default.parallelism
- 或者在rdd转换的时候做repartition
RDD常用操作/算子
action算子

transform算子

spark 数据倾斜问题的处理方案
数据倾斜也是我们经常遇到的问题之一,那么通常情况下,判断spark数据倾斜的方法有2个
- client模式下,会有“进度条”,会显示当前位于那个stage,以及当前stage的task数量,当进度条突然变得很慢的时候,或者卡在某个stage不动的话,一般都是出现了数据倾斜(很少会出现资源被占)
- spark UI: 从spark ui中可以看出每个task处理的数据量和消耗的时间
数据倾斜发生的原因
数据倾斜的原理很简单:在进行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或join等操作。此时如果某个key对应的数据量特别大的话,就会发生数据倾斜。比如大部分key对应10条数据,但是个别key却对应了100万条数据,那么大部分task可能就只会分配到10条数据,然后1秒钟就运行完了;但是个别task可能分配到了100万数据,要运行一两个小时。因此,整个Spark作业的运行进度是由运行时间最长的那个task决定的。
因此出现数据倾斜的时候,Spark作业看起来会运行得非常缓慢,甚至可能因为某个task处理的数据量过大导致内存溢出。
下图就是一个很清晰的例子:
hello这个key,在三个节点上对应了总共7条数据,这些数据都会被拉取到同一个task中进行处理;而world和you这两个key分别才对应1条数据,所以另外两个task只要分别处理1条数据即可。此时第一个task的运行时间可能是另外两个task的7倍,而整个stage的运行速度也由运行最慢的那个task所决定。
那么面对数据倾斜大概有如下几个方案
map侧代替join侧
小表join大表,如果小表能够被存储在work的内存中的话,通过Spark的Broadcast机制,将Reduce侧Join转化为Map侧Join,避免Shuffle, 整个流程从宽依赖转变为窄依赖,从而完全消除Shuffle带来的数据。

随机前缀后缀key
为数据量特别大的Key增加随机前/后缀,使得原来Key相同的数据变为Key不相同的数据,从而使倾斜的数据集分散到不同的Task中,彻底解决数据倾斜问题。Join另一则的数据中,与倾斜Key对应的部分数据,与随机前缀集作笛卡尔乘积,从而保证无论数据倾斜侧倾斜Key如何加前缀,都能与之正常Join。

增加task的并行度
也可以被称为提高shfflue操作的并行度,在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于Spark SQL中的shuffle类语句,比如group by、join等,需要设置一个参数,即spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是200
增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据。举例来说,如果原本有5个key,每个key对应10条数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。而增加了shuffle read task以后,每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时间都会变短了。具体原理如下图所示。

参考文献或转载
shfflue机制 https://juejin.im/post/6844904099872243719
spark rdd
[数据倾斜1](http://www.jasongj.com/spark/skew/, https://blog.csdn.net/u012501054/article/details/101371050)
Spark常见的问题以及解决方案的更多相关文章
- flume常见异常汇总以及解决方案
flume常见异常汇总以及解决方案 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 实际生产环境中,我用flume将kafka的数据定期的往hdfs集群中上传数据,也遇到过一系列的坑 ...
- IE6中CSS常见BUG全集及解决方案——摘自网友
IE6中CSS常见BUG全集及解决方案 IE6双倍边距bug 当页面内有多个连续浮动时,如本页的图标列表是采用左浮动,此时设置li的左侧margin值时,在最左侧呈现双倍情况.如外边距设置为10px, ...
- MongoDB + Spark: 完整的大数据解决方案
Spark介绍 按照官方的定义,Spark 是一个通用,快速,适用于大规模数据的处理引擎. 通用性:我们可以使用Spark SQL来执行常规分析, Spark Streaming 来流数据处理, 以及 ...
- jquery博客收集的IE6中CSS常见BUG全集及解决方案
今天的样式调的纠结,一会这边一会那么把jquery博客折腾的头大,浏览器兼容性.晚上闲着收集一些常见IE6中的BUG 3像素问题及解决办法 当使用float浮动容器后,在IE6下会产生3px的空隙,有 ...
- css 常见兼容性问题及解决方案
css 兼容问题一直是困扰前端开发人员的大难题,提到兼容性立马想到了万恶的ie6,说多了都是泪,还是整理一些常见的兼容性问题以及解决的方案吧. 一. 浮动元素双边距. ①条件:ie6下,如果给元素设置 ...
- struts2.1.8+hibernate2.5.6+spring3.0(ssh2三大框架)常见异常原因和解决方案
---------------------------------------------------------------------------------------------------- ...
- spark完整的数据倾斜解决方案
1.数据倾斜的原理 2.数据倾斜的现象 3.数据倾斜的产生原因与定位 在执行shuffle操作的时候,大家都知道,我们之前讲解过shuffle的原理. 是按照key,来进行values的数据的输出.拉 ...
- 常见Web攻击及解决方案
DoS和DDoS攻击 DoS(Denial of Service),即拒绝服务,造成远程服务器拒绝服务的行为被称为DoS攻击.其目的是使计算机或网络无法提供正常的服务.最常见的DoS攻击有计算机网络带 ...
- 3. Spark常见数据源
*以下内容由<Spark快速大数据分析>整理所得. 读书笔记的第三部分是讲的是Spark有哪些常见数据源?怎么读取它们的数据并保存. Spark有三类常见的数据源: 文件格式与文件系统:它 ...
- hive on spark:return code 30041 Failed to create Spark client for Spark session原因分析及解决方案探寻
最近在Hive中使用Spark引擎进行执行时(set hive.execution.engine=spark),经常遇到return code 30041的报错,为了深入探究其原因,阅读了官方issu ...
随机推荐
- 四种色彩模式ARGB_8888、ARGB_4444、 RGB_565、 ALPHA_8
A:透明度. R:红色. G:绿色. B:蓝色. Bitmap.Config ARGB_8888:有四个8位组成,A,R,G,B各占八位,也就是各占一个字节.也就是一个像素点占4个字节,32位. Bi ...
- mysql-对应删除 dict 脚本
-- 1. 此 dict 是在不同租户下的数据字典,查询时需要根据 departid 进行分类查询 -- 2. 删除dict, dict分类主表类型与挂载的子表数据 -- 3. 通过查询到的主表的 g ...
- RocketMQ(6) offset管理
这里的offset指的是Consumer的消费进度offset. 消费进度offset是用来记录每个Queue的不同消费组的消费进度的.根据消费进度记录器的不同,可以分为两种模式:本地模式和远程模式. ...
- C#拾遗补漏之goto跳转语句
前言 在我们日常工作中常用的C#跳转语句有break.continue.return,但是还有一个C#跳转语句很多同学可能都比较的陌生就是goto,今天大姚带大家一起来认识一下goto语句及其它的优缺 ...
- Scyther 协议形式化验证翻译 (第二章)
论文概述:$\alpha +\forall (\sum \oint_{3}^{4})$ 第二章: 操作语义 在第二章中我提出了一种新的安全协议的模型,用于定义安全协议以及协议的行为,在明确的模型中执 ...
- DDD笔记
笔记来源于b站视频 1.系统"老化" 需求难:程序员和产品经理沟通困难,更改需求难 开发难:对于上前行代码的类,更改很难,只能用if-else 创新难:对于之前老的技术笔试SSH想 ...
- linux 三剑客命令
Linux 命令集合 目录 Linux 命令集合 基础概念 1 软连接和硬链接 1.1 基础概念 1.2 如何创建软链接 零.正则 01 区别 02 通配符 03 基础正则 04 扩展正则 一 awk ...
- day04-实现SpringBoot底层机制
实现SpringBoot底层机制 Tomcat底层启动分析+Spring容器初始化+Tomcat关联Spring容器 1.任务1-创建Tomcat,并启动 (1)创建一个Maven项目,修改pom.x ...
- ItemsControl和ListView、ListBox的区别
1.ItemsControl用来显示一个数据项的集合,它的底层是一个列表,它可以非常灵活的展示布局和数据 以下是例子 <ItemsControl ItemsSource="{Bindi ...
- 说说如何在Vue项目中应用TypeScript?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.前言 与link类似 在VUE项目中应用typescript,我们需要引入一个库vue-property-decorator, 其是基 ...

