当出现数据倾斜时如何应对---倾斜key单独处理/MapJoin/星环SkewJoin的原理及使用方法

友情链接

前言

前面的章节分别为读者介绍了为什么会出现数据倾斜、该现象的原理、诱因以及星环计算引擎对此的保护机制。 本篇文章为本系列最终篇,将为您介绍在计算过程中出现数据倾斜的问题时应该如何处理应对,不同手段的使用方式,如果您还有其他想了解的可以多多留言反馈,后续进行补充描述。

常见的3类处理方式

① Key分布不均匀 --- 倾斜的key单独处理
② 大表与小表进行关联 --- Map Join
③ 大表与大表进行关联 --- Skew Join

方法一  倾斜的key单独处理

当存在大量倾斜key的时候,可以通过手动拆分,将倾斜与未倾斜的部分分别做处理,再将结果合并。

用法示例:不包括倾斜key的查询union all包括倾斜key的查询

select
  *
from
  FACT f
  left join DIMENSION d
    on f.CODE_ID = d.CODE_ID
where
  f.CODE_ID <> 250
union all
select
  *
from
  FACT f
  left join DIMENSION d
    on f.CODE_ID = d.CODE_ID
where
  f.CODE_ID = 250
  and d.CODE_ID = 250

还有一种情况比较简单,如果业务上不需要一些key的参与(比如空字符串等无效或无价值的key),可以考虑将倾斜的key直接过滤掉。

用法示例

select a.col1,null as col2 from test1 a 
where a.id is null
union all
select a.col1,b.col2 from test1 a 
left join test2 b on a.id=b.id
where a.id is not null

但是这种方法适用的场景是导致倾斜的key只有少数几个,并且不影响最终结果。所以如果倾斜的key比较重要,不适合进行过滤的操作,也可以采用下面的方式进一步解决。

方法二  改用MapJoin的形式,广播小表出去,避免shuffle

CommonJoin劣势

CommonJoin(也被称为Shuffle Join/Reduce side Join/Sort Merge Join..)主要是在 Shuffle 阶段(Reduce 端)执行。

Common Join 的一个主要问题是在数据整理排序的过程上耗费了大量的资源,它会启动一个Task,Mapper会去读取两张表中的数据,然后处理数据后会对对他们进行排序、合并等操作,然后相同key的所有行数据都会分发到同一个节点上。比如key是age=20,task会读取该行剩下的所有数据,id=1,name=张三,地址=xxx。然后在reduce阶段,reducer获取排序后的数据执行join操作。不可避免地会涉及到shuffle,而shuffle通常是IO重灾区,大量的 IO 都会集中在shuffle过程中。但是。一旦触发Shuffle,所有相同key的值就会被拉到一个或几个Reducer节点上,容易发生单点计算问题,非常消耗资源

image.png

MapJoin介绍

为了优化作业缩短大量数据传输时间,提升系统资源利用率,我们可以改用mapjoin的方式在Map阶段执行表连接,而非等到shuffle以及reduce阶段才执行,避免shuffle。下面我们来看一下MapJoin的运行方式进一步了解。

使用MapJoin时需区分大小表,MapJoin的核心是将小表的数据广播给每个executor,executor读取大表的数据probe小表,逐行按序扫描做关联。

image.png

MapJoin原理

Map Join 的方式是先提前把小表的数据写入HDFS,在大表做计算的时候会先把小表从HDFS读出来复制到每个计算节点上,然后构建小表的哈希表。接下来Executor会读取大表的数据块,根据join key和小表的hashtable做关联,生成结果输出,给到下一个计算阶段(比如groupby)。

由于较小的表已经被提前写入HDFS中,整个计算过程仅涉及Map 阶段,减少了shuffle中排序和合并过程以及网络传输所需的时间。同时,该方式不需要reducer并且跳过了reduce阶段,由executor直接输出结果文件进入下一阶段,所以他与CommonJoin方式相比查询性能更好,任务的执行效率更高。

MapJoin使用方式&限制

但是MapJoin只适用于大表小表Join的情况,因为MapJoin会将指定表的数据全部加载在内存,表在被加载到内存后,数据大小会急剧膨胀,因此指定的表只能是小表。

我们推荐小表被加载到内存后占用的总内存不超过5M(压缩后),如果超过可以根据业务以及集群的情况适当调整。

MapJoin中针对Join操作的限制

  • left outer join的左表必须是大表。
  • right outer join的右表必须是大表。
  • 不支持full outer join。
  • inner join的左表或右表均可以是大表。

启用MapJoin的方式

当大小表关联时星环计算引擎默认自动开启MapJoin,但是如果存在如某些query 或者表,过滤后比较小的情况,这个情况引擎无法自动判断大小,所以需要手动指定,手动指定的方式如下:

通过 HINT 实现

HINT是写在语句中用于指示计算底层实现某个操作的执行方式的一条说明,按照优化目的与一定的格式书写。MapJoin的HINT形式为“/*+ mapjoin(<table_name>) */”,其中table_name指明被广播的表,可以为多个,一般都是小表或过滤率高的表。HINT的实现语法如下:

SELECT /*+MAPJOIN(table_B)*/
...
FROM table_A JOIN table_B
ON a.key = b.key;

其中 table_B 为小表。

比如对于store_sales(大表)和date_dim(小表)两表的JOIN操作语法示例:

SELECT /*+MAPJOIN(date_dim)*/
COUNT(*)
FROM store_sales JOIN date_dim
ON ss_sold_date_sk = d_date_sk;

方法三  skew join

MapJoin主要适用于大小表关联的情况,那如果是大表与大表之间进行关联可以考虑TDH新版本发布的skew join功能,可以一定程度缓解大表关联场景下的数据倾斜问题。

该功能已上线最新版本社区版,感兴趣的小伙伴可以点击该链接获取相关资源:社区版相关资源指路

Skew Join的核心是针对倾斜key(MapJoin)以及常规key(CommonJoin)分别采用不同的Join方式,然后合并他们的结果产生输出

SKEWJOIN原理

SKEWJOIN在运行时,对于倾斜数据较少的CommonTable,会先将它的倾斜key对应的数据写入HDFS中等待后续的MapJoin操作。接下来运行一组计算节点读取倾斜表的数据,执行下述步骤:

① 将HDFS中存储的数据复制到计算节点上,然后构建哈希表。倾斜表中的倾斜数据与哈希表文件关联执行MapJoin操作;
② 其他的常规数据执行CommonJoin的操作;
③ 最后合并MapJoin与CommonJoin的结果进行输出;

SKEWJOIN使用限制

  • 只支持连接中单张表存在数据倾斜的情况;
  • 只支持等值连接的情况。等值连接指的是SQL语句中join的on 条件中join key为keyA=keyB或类似条件;
  • 支持Windrunner和Linac引擎;
  • 对于子查询倾斜的场景,目前只能通过设置hint来解决;
注意:不要随意添加该 hint,该 hint 会在 reduce 和 join 阶段增加行级的判断,在不存在显著数据倾斜的场景中添加该 hint 会影响性能。

当对JOIN语句使用 SKEWJOIN时的限制:

  • INNER JOIN:可以在INNER JOIN语句中为左表或右表指定SKEWJOIN HINT;
  • LEFT JOIN:只能为左表指定 SKEWJOIN HINT;
  • RIGHT JOIN:只能为右表指定 SKEWJOIN HINT;
  • FULL JOIN:不支持 SKEWJOIN HINT;

SKEWJOIN语法

您需要在select语句中使用Hint提示

"/*+SKEWJOIN(table_alias (column_name) [(skew_value)],table_alias (column_name) [(skew_value)]...)*/"

才会执行skewjoin,整体 hint 语法大体上分为:

  • table_alias:存在倾斜的表别名;
  • column_name:倾斜表中连接用的一个或多个列名,要求必须包含所有用作 join 条件的列,即 SQL 中 join 语句的 on 条件中匹配的列名;多个列名用逗号 ',' 分开,整体用括号 '( )' 包裹,如 (col1, col2);
  • skew_value:倾斜表中的连接用列的一个或多个倾斜值,每个倾斜值用括号 '()' 包裹;倾斜值内部不同列的值用逗号 ',' 分割,多个倾斜值整体用方括号 '[ ]' 包裹,如 [('name1', 100), ('name2', 200)];
  • 每个倾斜值,即括号内的多个列值,其必须与 column_name 中倾斜列按顺序对应,skew_value不允许出现null;

语法示例

select /*+ SKEWJOIN(a(id, name)[(1,'qwh'),(2,'ly')]) */
jt_1.id, jt_1.name, des, data
from jt_1 a inner join jt_3 b
on a.id = b.id
and a.name = b.name;

如上述样例,计算引擎会对表 jt_1 中,所有 id=1,name='qwh' 以及 id=2,name='ly' 的所有列做均匀处理,避免倾斜。

本系列文章分享结束,如果对您有帮助多多留言点赞吧~如果您有想要进一步了解的内容,也欢迎您多多留言反馈~

我们下期见~

<think>我们正在处理HiveSQL中两个大表进行JOIN数据倾斜问题。根据引用内容,我们可以采用以下优化方法: 1. **处理无效值(如-99、空字符串、null等)导致的倾斜**:将空值的key转换成一个字符串加上随机数,这样可以将倾斜数据分散到不同的reduce上。具体做法是在JOIN条件中使用`case when`语句,对空值进行随机化处理。例如: ```sql SELECT a.user_id, a.order_id, b.user_id FROM table_a a JOIN table_b b ON (CASE WHEN a.user_id IS NULL THEN concat('hive', rand()) ELSE a.user_id END) = b.user_id ``` 这种方法可以避免空值集中到一个reduce任务上(引用[1]和引用[2])。 2. **使用Map Join**:如果其中一个表相对较小(可以通过配置`hive.auto.convert.join`开启自动转换),可以将其加载到内存中,避免shuffle过程。但注意,此方法适用于小表,对于大表可能不适用。 3. **分桶表(Bucketed Table)**:如果两个表都是分桶表,且按照JOIN的key进行了分桶,并且桶的数量相同,那么可以执行桶的Map Join,这样JOIN操作可以在Map阶段完成,避免数据倾斜。 4. **增加Reduce数量**:通过调整`hive.exec.reducers.bytes.per.reducer`参数(每个reduce任务处理数据量)或者直接设置`mapreduce.job.reduces`参数来增加reduce任务的数量,从而分散负载。 5. **对倾斜Key单独处理(两阶段聚合)**:如果某个Key数据量特别大(如引用[3]中提到的2亿多条数据),可以先将该Key对应的数据单独拿出来处理,再与其他数据合并。 6. **使用Skew Join优化**:Hive提供了数据倾斜的优化参数,如设置`hive.optimize.skewjoin=true`,并指定倾斜key倾斜的程度(`hive.skewjoin.key`和`hive.skewjoin.mapjoin.map.tasks`等)。这样Hive会在运行检测倾斜key,并将倾斜key分拆成多个任务处理。 7. **调整JOIN顺序**:在多个表JOIN,将大表放在后面,小表放在前面,这样Hive会尝试将小表放入内存(Map Join),但这种方法对两个大表JOIN的情况帮助有限。 8. **使用随机数打散大Key**:对于非空的大Key,也可以采用类似处理空值的方法,将大Key分成多个子Key。例如,在其中一个表中,对JOIN的key添加一个随机数后缀(范围从1到N),在另一个表中,将key复制N份(每份加上1到N的后缀),然后进行JOIN。这样可以将原来一个Key数据分散到N个Key上。但这种方法需要复制数据,可能会增加计算量。 **总结**:针对两个大表JOIN的数据倾斜问题,最常用的方法处理无效值(如空值)的随机化,以及使用Skew Join优化参数。如果倾斜是由某个具体的大Key引起的,可以考虑单独处理Key使用随机数打散。 §§相关问题§§ 1. 如何判断Hive查询是否出现了数据倾斜? 2. 除了JOIN操作,Hive中还有哪些操作容易导致数据倾斜? 3. Hive的Skew Join优化参数具体如何配置? 4. 在处理大表JOIN,如何评估使用随机数打散方法的性能影响?</think>### HiveSQL 大表 JOIN 数据倾斜优化方法 在 HiveSQL处理两个大表 JOIN 的数据倾斜问题,核心思路是将倾斜Key 分散到多个 Reduce 任务处理。以下是已验证的优化方案: #### 1. **空值/无效值随机化(推荐方案)** 当数据倾斜由无效值(如 `NULL`、`-99`、空字符串等)引起,通过添加随机后缀分散数据: ```sql SELECT /*+ MAPJOIN(b) */ a.user_id, a.order_id, b.user_name FROM table_a a JOIN table_b b ON (CASE WHEN a.user_id IS NULL THEN CONCAT('hive_', RAND()) ELSE a.user_id END) = b.user_id ``` **优化原理**: - 将空值 Key 转换为随机字符串(如 `hive_0.23`),使原集中在一个 Reduce 的空值分散到多个 Reduce - 减少单个 Reduce 的数据负载(引用[1][^1], 引用[2][^2]) --- #### 2. **热点 Key 分桶打散** 对非空但高度倾斜Key(如引用[3][^3]中单 Key 2.36亿条),采用分桶策略: ```sql -- 步骤1: 对表A的倾斜Key添加随机桶号 CREATE VIEW tmp_a AS SELECT user_id, CASE WHEN user_id = 'high_freq_key' THEN CONCAT(user_id, '_', CAST(RAND()*10 AS INT)) ELSE user_id END AS bucket_key FROM table_a; -- 步骤2: 对表B复制多份倾斜Key CREATE VIEW tmp_b AS SELECT user_id, user_name FROM table_b WHERE user_id <> 'high_freq_key' UNION ALL SELECT b.user_id, b.user_name FROM table_b b LATERAL VIEW EXPLODE(ARRAY(0,1,2,3,4,5,6,7,8,9)) t AS bucket_id WHERE b.user_id = 'high_freq_key'; -- 步骤3: 按桶号JOIN SELECT a.user_id, b.user_name FROM tmp_a a JOIN tmp_b b ON a.bucket_key = b.user_id; ``` --- #### 3. **参数调优组合** 在查询前启用 Hive 倾斜优化参数: ```sql SET hive.optimize.skewjoin=true; -- 开启倾斜JOIN优化 SET hive.skewjoin.key=100000; -- 超过10万条的Key视为倾斜 SET hive.optimize.skewjoin.compile=true; -- 运行动态优化 SET hive.auto.convert.join=true; -- 自动转MapJoin ``` --- #### 4. **MapJoin 强制广播** 若有一个表可过滤到较小规模(<25MB): ```sql SELECT /*+ MAPJOIN(small_table) */ large_table.* FROM large_table JOIN small_table ON large_table.key = small_table.key; ``` > **方案选择建议**: > - 空值倾斜 → 方案1(IO和作业数最少) > - 热点Key倾斜 → 方案2 > - 未知倾斜类型 → 方案3+方案1组合使用 --- ### 性能验证结论 1. 空值随机化方案相比未优化: - Reduce 任务数从 1 个增加到 N 个 - 作业执行间缩短 40%-70%(引用[1][^1]) 2. 分桶打散方案: - 适用于单 Key 超亿级数据场景(引用[3][^3]) - 需权衡数据复制带来的存储开销
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值