数据倾斜
一 数据倾斜的具体表现
1.1 hadoop
1.1.1 hadoop产生数据倾斜的现象
当我们看任务进度长时间维持在99%,这里如果详细的看日志或者和监控界面的话会发现:
- 有一个多几个reduce卡住,一直不结束
- 各种container报错OOM
- 读写的数据量极大,至少远远超过其它正常的reduce
- 伴随着数据倾斜,会出现任务被kill等各种诡异的表现
1.1.2 定位数据倾斜
可以直接同Yarn的8088监控页面查看
1.2 hive
1.2.1 hive产生数据倾斜的情况
出现数据倾斜的情况:
-
join时候使用不同数据类型连接
- hive中过多的null进行join会产生数据倾斜
1.2.2 定位数据倾斜
跟hadoop的MR相同
1.3 spark
1.3.1 当发现如下现象时,十有八九是发生数据倾斜了:
- 绝大多数 task 执行得都非常快,但个别 task 执行极慢,整体任务卡在某个阶段不能结束。
- 原本能够正常执行的 Spark 作业,某天突然报出 OOM(内存溢出)异常,观察异常栈,是我们写的业务代码造成的。这种情况比较少见。
TIPS
在 Spark streaming 程序中,数据倾斜更容易出现,特别是在程序中包含一些类似 sql 的 join、group 这种操作的时候。因为 Spark Streaming 程序在运行的时候,我们一般不会分配特别多的内存,因此一旦在这个过程中出现一些数据倾斜,就十分容易造成 OOM。
1.3.2 如何定位数据倾斜
-
通过Spark Web UI
通过 Spark Web UI 来查看当前运行的 stage 各个 task 分配的数据量(Shuffle Read Size/Records),从而进一步确定是不是 task 分配的数据不均匀导致了数据倾斜。
知道数据倾斜发生在哪一个 stage 之后,接着我们就需要根据 stage 划分原理,推算出来发生倾斜的那个 stage 对应代码中的哪一部分,这部分代码中肯定会有一个 shuffle 类算子。可以通过 countByKey 查看各个 key 的分布。
TIPS
数据倾斜只会发生在 shuffle 过程中。这里给大家罗列一些常用的并且可能会触发 shuffle 操作的算子: distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition 等。出现数据倾斜时,可能就是你的代码中使用了这些算子中的某一个所导致的。
-
通过key抽样统计
也可以通过抽样统计 key 的出现次数验证。
由于数据量巨大,可以采用抽样的方式,对数据进行抽样,统计出现的次数,根据出现次数大小排序取出前几个:
df.select("key").sample(false, 0.1) // 数据采样 .(k => (k, 1)).reduceBykey(_ + _) // 统计 key 出现的次数 .map(k => (k._2, k._1)).sortByKey(false) // 根据 key 出现次数进行排序 .take(10) // 取前 10 个。
如果发现多数数据分布都较为平均,而个别数据比其他数据大上若干个数量级,则说明发生了数据倾斜。
1.4 flink
1.4.1 数据倾斜对flink的影响
(1)单点问题
数据集中在某些分区上(Subtask),导致数据严重不平衡。
(2)GC 频繁
过多的数据集中在某些 JVM(TaskManager),使得JVM 的内存资源短缺,导致频繁 GC。
(3)吞吐下降、延迟增大
数据单点和频繁 GC 导致吞吐下降、延迟增大。
(4)系统崩溃
严重情况下,过长的 GC 导致 TaskManager 失联,系统崩溃。
1.4.2 flink如何定位数据倾斜
- 通过反压判断,Flink Web UI 自带的反压监控(直接方式),一旦哪个地方出现反压就可以定位到哪里产生了数据倾斜
- Flink Web UI 自带Subtask 接收和发送的数据量。当 Subtasks 之间处理的数据量有较大的差距,则该 Subtask 出现数据倾斜。
二 数据倾斜的解决办法
2.1 解决数据倾斜的思路
- 业务逻辑, 比如出现城市热点数据可以单独拿出来处理,另外平均的一起处理
- 程序层面, 在shuffle过程或者算子过程中
- 调参方面, Hadoop和Spark都自带了很多的参数和机制来调节数据倾斜,合理利用它们就能解决大部分问题。
2.2 hadoop解决数据倾斜的方法
-
大小表join的时候,将reducejoin换位mapjoin,在map阶段将小表存到内存中,可以避免数据倾斜(程序层面)
适用场景: MR处理多个结构化数据join的时候产生的数据结构
-
对于数据量过大的数据,单独MR(在不影响结果前提下)(业务逻辑)
适用场景: 针对出现热点问题统计的情况(不适合计算粘性太强的计算)
-
在map阶段结束后先进行combine,这个只能减缓网络传输,IO的压力(程序层面)
适用场景: 同样适用于计算粘性不强的计算
-
局部聚合加上全局聚合:
第一次mr阶段对那些导致了数据倾斜的key加上随机的前缀,然后第二次的MR讲这些随机的key再次聚合(效率比较低,受业务限制)(程序方面)
适用场景: 大部分场景,尤其是复杂计算 缺点: 两个MR浪费时间和资源
-
实现自定义的分区,避免数据倾斜
适用场景: 根据业务自定义分区可以解决大部分问题
-
在不影响的业务的前提下,增加分区的数量,也就是增加reduce个数(参数方面)
适用场景: 适用计算结果不依赖分区的结果,适用默认分区HashPartitioner
2.3 hive解决数据倾斜的方法
-
hive支持针对数据倾斜的处理的参数配置(参数方面)
hive.groupby.skewindata=true
这种方法类似于MR的局部聚合加上全局聚合
适用场景: 大部分场景 缺点: 两个MR,浪费时间和资源
-
不使用left join改用left semi jioin的使用,前者会把所有的数据遍历,后者会根据匹配不上的值跳过 (程序层面)
适用场景: sparkSQL需要join的时候
-
设置多个reduce个数(参数方面)
适用场景: 适用计算结果不依赖分区的结果,适用默认分区HashPartitioner
-
针对hive产生数据倾斜的情况作出优化:
- 针对数据类型不一致时先检验数据结构或者全部转换为字符串比较
- 对于null值较多的情况,可以对null进行添加随机后缀的操作来避免null分区过多
2.4 spark解决数据倾斜的方法
-
对于异常数据的清理
-
提高shuffle并行度(只能解决hash冲突带来的数据倾斜,因为spark默认的分区器是HashPartitioner)
适用场景: hash冲突将不同的key传入一个task
-
自定义分区器
适用场景: 大部分场景
-
使用mapjoin(通过广播变量实现)
适用场景: sparkSQL大小表
-
针对sparkSQL有一个分开join然后union(一表有倾斜,一表没有,将倾斜部分key加前缀然后再join,最后除掉前缀之后union)
适用场景: 一表有倾斜,一表没有,两表都不小
-
大表加盐,小表扩大N倍
适用场景: 大小表
-
加盐局部聚合+去盐全局聚合(两次MR)
适用场景: 大部分场景 缺点: 两次MR,浪费时间和资源
2.5 flink解决数据倾斜的方法
-
调整并行度
适用场景: 数据源 source 消费不均匀
-
key加盐
适用场景: key 分布不均匀的无统计场景
-
两阶段聚合:加盐局部聚合,去盐全局聚合
适用场景: key 分布不均匀的统计场景