如何解决数据倾斜问题
背景
分布式环境下经常会碰到数据倾斜的问题,因为实际业务中的数据很难均匀分布,有些数据中会存在大量重复的 key ,例如某个 ID 的用户活跃行为特别频繁,某个时间段系统在线的用户人数特别多等等。
在具体任务中,导致数据倾斜的操作主要是发生在 group by、join 等需要数据shuffle的操作中,这些过程需要根据 key 进行数据汇集处理,其中容易卡住的往往是 join 操作。当某个或者某些 key 的数据量远大于其他 key 时,处理这些 key 的任务运行时间也远大于其他任务,从而拖累整个任务时长。
数据倾斜的问题不解决好的话,不仅没办法充分利用分布式带来的数据处理优势,而且还可能导致内存消耗过大,超负荷导致任务延迟或者失败。本文将结合在日常工作中碰到的导致数据倾斜的问题进行拆解,追溯问题出现的源头,更好地预防与解决数据倾斜问题。
1、事前对连接 key 进行预处理
不要着眼于问题出现之后再找应对方案,可以换个思路,在事前先避免数据倾斜问题的发生。
我们一般的思维惯性是碰到问题再找解决方案,但碰多了数据倾斜会发现,大多的数据倾斜都是跟业务无光的数据导致的。我们能在读表之后第一件事就是尽可能的过滤不必要的数据,理清业务含义先过滤脏数据和业务不相关的数据。
过滤的数据具体可以分为以下两种情况:
(1)两个表连接 key 存在大量的 NULL 数据没有过滤,参与进了 join 的执行。其中分为主动产生与非主动产生的,主动产生的空值与无效值是值本来连接字段就存在大量空值与无效值。另一种是非主动产生的空值或无效值,比如之前的逻辑为了合并数据,用了 union all 操作把某些列设置为 null ,而后面的逻辑采用该列作为 join 条件导致的数据倾斜。
(2)存在 “ 脏数据 ” ,两个表连接条件的字段数据类型不一致,不满足原有的数据类型,经过内在的逻辑处理往往会得到与上边 NULL 相同的结果,因此在连接前,需要把连接 key 统一好数据类型。
事前最好的办法是:写 SQL 操作之前,先简单看下表的数据量级,再看 key 的空值与无效值的统计,表与表之间的关联可以,先增加空值与无效值的过滤处理,比如直接去掉,或者在大表关联大表出现的异常值使用 rand 函数进行随机填充。
2、大表关联小表,一般用 mapjoin
如果是两个表 join 的情况,有一个表很大,另一个很小,即大表关联小表,一般考虑用 mapjoin 的方式,将小表缓存在内存中,再广播到大表所在的节点上,避免数据倾斜的出现。
注意:
1、无论是否使用 mapjoin ,小表都需要放入前面,原因是reduce阶段会将第一个表的 key 全部放入内存,当第二个表到来的时候再开始输出。
2、还有一个需要注意的是,如果你使用的是左外连接,即 left out join 的话,基表是不能被广播的,因此左外连接中左表不可以是小表,右外连接中右表不可以是小表。
3、倾斜数据分而治之
例如:表 a 跟表 b 进行 join 操作,有数据倾斜的表是 表a ,表 a 里边的倾斜的头部 key 非常少,只有 key = 1 的数据特别多,可以将 a 的数据分为两部分 a1 跟 a2,a1 中只包含 key = 1 的倾斜数据,a2 不包含数据倾斜的数据。采取 union all + mapjoin 的方式分而治之,把key = a 单独抽出来作为一组用 mapjoin ,剩下的另起炉灶直接用 join,最后再把各自计算得出来的结果用 union all 拼接起来。
具体实现可以参考以下代码:
4、倾斜数据打散处理
这种方法其实很简单,一个字:切!
数据倾斜的表头部 key 都很多,而且都是业务需要的 key ,既不能过滤,也不好单拆开计算,可以考虑在头部 key 添加随机数进行打散,处理后再去掉随机数合并。
具体实现可以参考以下代码:
总结
以上,个人对数据倾斜解决方案就已经梳理总结完成,可能美中不足的就是干巴巴的文字占据大多数篇幅,实际业务的解决思路偏多,没有很好地列举详细的案例一一讲解,个人水平有限,也希望大家见谅。大家可以根据业务情况进行灵活运用,希望以上的数据倾斜解决思路能够有所帮助,毕竟多用多踩坑,印象更深些,最后才知道更为高效的解决方案。