订阅更新

百GB和百TB的倾斜,处理方案有什么不同?数据规模对选型的影响

数据倾斜手册 第 17 题 约 12 分钟读完

本文目录
  1. 先说结论
  2. 百GB量级:Broadcast能解决大部分问题
  3. TB量级:Broadcast失效,精细化分桶是关键
  4. 十TB量级及以上:预聚合是核心,明细表能不碰就不碰
  5. 面试高频追问
  6. 实战:数据量从百GB到TB的切身体感
  7. 总结

先说结论

数据量级决定方案选择:百GB级,倾斜处理优先考虑Broadcast(内存能放下)、简单的加盐;TB级,Broadcast往往失效(表太大),加盐后单分片还是很大,需要精细化的分桶设计;十TB级及以上,更多依赖预聚合和分层存储来从根源减少单次处理的数据量。

规模越大,治本越重要:数据量小时,治标(每次查询时处理倾斜)还能接受;数据量大时,每次查询处理倾斜的代价极高,必须从存储设计、分层架构层面根治,让查询尽量少接触原始明细数据。

「数据量」不只是行数:要关注的是单次Shuffle的数据量(和并行度、Executor内存有关),而不只是总数据量。同样10TB的数据,并行度1000时每个Task处理10GB,并行度10000时每个Task只处理1GB,体感完全不同。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

同样是数据倾斜,百GB规模和百TB规模的处理方案有很大差异。百GB的倾斜可能Broadcast就解决了;百TB的倾斜,即使打散成100份,单份还是TB级,一样吃力。面试时如果面试官问「你们数据量多大」,这不只是闲聊——他在判断你的倾斜处理经验是否适配他们公司的数据规模。我在不同规模的公司都待过,从早期短视频平台的百GB量级,到某出海硬件品牌的TB量级,再到某短剧出海公司的十TB量级,每个阶段对倾斜的感知和处理方式都不一样。

百GB量级:Broadcast能解决大部分问题

百GB量级的大表,通常意味着单个JOIN的两侧总数据量不超过几百GB。这个规模下,很多维度表(产品表、用户表)可能只有几十MB到几GB,Broadcast是最优解:

-- 百GB量级的典型场景(早期短视频平台,我的第一份工作)
-- 事实表:用户行为日志,每天约50GB(5亿行)
-- 维度表:用户画像表,约800MB(800万用户)
-- 维度表:内容表,约200MB(200万内容)
 
-- 分析:800MB的用户画像表能广播吗?
-- 集群:20个Executor,每个8GB内存
-- 800MB × 20个Executor = 16GB集群内存占用
-- Executor内存8GB × 20 = 160GB总内存
-- 16GB / 160GB = 10%,安全!
 
SET spark.sql.autoBroadcastJoinThreshold = 838860800;  -- 800MB
 
SELECT /*+ BROADCAST(u), BROADCAST(c) */
  b.user_id,
  u.age_group,
  u.city,
  c.content_type,
  COUNT(*) AS play_cnt
FROM user_behavior b
JOIN user_profile u ON b.user_id = u.user_id  -- 800MB,广播
JOIN content_info c ON b.content_id = c.content_id  -- 200MB,广播
WHERE b.dt = '2018-06-01'
GROUP BY b.user_id, u.age_group, u.city, c.content_type;
-- 百GB量级,Broadcast解决JOIN倾斜,作业10分钟内完成

百GB量级的特点:Executor内存相对充裕(相对于数据量),Broadcast的内存代价可以承受;倾斜的处理代价小,加盐10份后每份只有几GB,单Task完全能处理;因此,百GB量级优先考虑Broadcast,其次加盐打散,通常不需要复杂的预聚合设计。

TB量级:Broadcast失效,精细化分桶是关键

TB量级意味着原来的「小表」可能也有几十GB,Broadcast开始失效;大表动辄数百GB,加盐打散后单份还是几十GB,需要更精细的方案:

-- TB量级的典型场景(某出海硬件品牌,迁移后规模)
-- 事实表:设备行为日志,每天约800GB(80亿行)
-- 维度表:设备型号表,约5GB(500万型号)
-- 维度表:用户账号表,约20GB(2000万用户)
 
-- 问题:5GB的设备型号表,能广播吗?
-- 50个Executor × 5GB = 250GB集群内存占用
-- 已经超过单个Executor 8GB内存的60%!不能广播
 
-- TB量级的处理策略:
-- 1. 维度表提前聚合压缩,只保留需要的列
SELECT /*+ BROADCAST(m_slim) */
  e.device_id,
  m_slim.model_name,
  COUNT(*) AS event_cnt
FROM device_events e
JOIN (
  -- 只选需要的2列,从5GB压缩到约200MB,可以广播!
  SELECT device_model_id, model_name
  FROM device_model_info
) m_slim ON e.device_model_id = m_slim.device_model_id
WHERE e.dt = '2023-06-01'
GROUP BY e.device_id, m_slim.model_name;
 
-- 2. 用户账号表(20GB):裁剪列后约2GB,可广播
-- 或者:两张大表改用分桶表,利用Bucket Join避免Shuffle
-- TB量级:分桶设计避免大表Shuffle Join
-- 提前建好分桶表(ETL写入时就分桶):
CREATE TABLE device_events_bucketed
USING DELTA
CLUSTERED BY (device_id) INTO 500 BUCKETS;  -- 500桶,每桶约1.6GB
 
CREATE TABLE device_profile_bucketed
USING DELTA
CLUSTERED BY (device_id) INTO 500 BUCKETS;  -- 必须和上面桶数相同
 
-- 查询时Bucket Join,无Shuffle:
SET spark.sql.sources.bucketing.enabled = true;
SELECT e.device_id, p.country, COUNT(*) AS event_cnt
FROM device_events_bucketed e
JOIN device_profile_bucketed p ON e.device_id = p.device_id
WHERE e.dt = '2023-06-01'
GROUP BY e.device_id, p.country;
-- TB量级Bucket Join:比Shuffle Join快3-5倍

十TB量级及以上:预聚合是核心,明细表能不碰就不碰

十TB及以上量级,从架构层面就要减少对原始明细数据的直接查询。每次查询对原始明细做大规模Shuffle,哪怕倾斜处理得再好,也架不住数据量本身带来的IO和网络开销:

数据量级典型规模主要倾斜挑战优先解法架构重点
百GB1-500GB/天热点维度JOINBroadcastExecutor内存调优
TB0.5-10TB/天维度表过大无法广播,大表Shuffle分桶设计,列裁剪后广播分桶表设计
十TB10-100TB/天明细表查询IO开销极大,任何Shuffle代价高DWS预聚合,减少明细查询分层数仓,DWS设计
百TB以上>100TB/天单次任务规模超出集群能力分批处理,数据分治分区裁剪,时间切片
-- 十TB量级的正确姿势:DWS层预聚合,ADS层不碰明细
 
-- ODS/DWD层:原始明细,10TB/天,尽量少直接查询
-- dwd_ad_exposure: 100亿行/天,每行约100字节 = 1TB/天
 
-- DWS层(每天凌晨跑一次,把明细聚合到DWS):
INSERT OVERWRITE dws_ad_daily_stats PARTITION(dt='2024-08-01')
SELECT
  ad_id,
  channel_id,
  country,
  SUM(exposure)    AS total_exposure,
  SUM(click)       AS total_click,
  SUM(revenue)     AS total_revenue,
  COUNT(DISTINCT user_id) AS uv  -- 只在DWS层做一次COUNT(DISTINCT)
FROM dwd_ad_exposure
WHERE dt = '2024-08-01'
GROUP BY ad_id, channel_id, country;
-- DWS表:从100亿行 → 约100万行(广告数×渠道数×国家数),压缩100倍
 
-- ADS层查询直接读DWS(无倾斜风险,数据量小):
SELECT channel_id, SUM(total_revenue) AS channel_revenue
FROM dws_ad_daily_stats
WHERE dt = '2024-08-01'
GROUP BY channel_id;
-- ADS查询:从100万行聚合,秒级完成,完全没有倾斜问题

面试高频追问

追问一:你说TB量级用分桶,分桶数怎么决定?500桶的依据是什么?

分桶数的计算公式:目标是每个桶的数据量在128MB-1GB之间(和HDFS Block大小和Spark Task处理能力匹配)。计算方法:数据总量 ÷ 目标每桶大小 = 桶数。上面的例子:800GB ÷ 1.6GB/桶 ≈ 500桶。更精确的方法:先跑一次全表扫描统计总行数和平均行大小,再计算桶数。分桶数通常选2的幂次(128、256、512)或者质数(499、503),避免哈希冲突导致数据不均匀。另外桶数一旦确定,很难修改(需要重建表),所以宁可多选一点(比如800桶),后续合并桶比分裂桶容易得多。

追问二:十TB量级的DWS预聚合,如果COUNT(DISTINCT)的UV需要精确计算,DWS里能存吗?

精确UV在DWS层有两个方案:1)用Bitmap存储:把user_id写入Bitmap(RoaringBitmap),DWS里存Bitmap对象,查询时用bitmap_count()取精确UV,支持跨时间段的UNION操作(Bitmap OR),非常高效。Doris/StarRocks原生支持BITMAP类型;2)用HyperLogLog(HLL):误差1-5%,但存储极省,查询极快,适合大盘趋势分析(不需要精确到个位数)。实际上精确UV和近似UV两种都存:精确结算场景用Bitmap,趋势分析用HLL。我在某短剧出海公司用Hologres,就是两种都存的方案,按场景选用。

追问三:数据量从TB增长到十TB,除了倾斜问题,还有哪些架构层面要升级?

数据量级跃升时,倾斜只是冰山一角,整个架构要系统性升级:1)存储:HDFS扩容或上云(S3/OSS),单机NameNode升级为HA,Block大小从128MB调到256MB减少NameNode压力;2)计算:Executor内存和CPU配比调整,并行度从数百到数千,shuffle.partitions对应增大;3)调度:任务依赖图变复杂,需要引入精细的优先级控制,防止资源争抢;4)数据质量:数据量大后数据质量问题被放大,需要更完善的校验和监控;5)成本:十TB量级的存储和计算成本显著上升,需要引入存储分层(热温冷)和计算资源弹性伸缩。把这些配套的架构升级说出来,面试官会觉得你有系统性的大规模数据平台建设经验。

实战:数据量从百GB到TB的切身体感

我从早期的短视频平台(百GB级)跳到某出海硬件品牌(TB级),最直接的体感就是:原来能广播的表,突然广播不了了。

# 百GB时代(早期短视频平台):
# 用户画像表 800MB,直接广播,JOIN 5分钟
# 参数:autoBroadcastJoinThreshold = 800MB,完美
 
# TB时代(某出海硬件品牌):
# 设备型号表从500MB增长到5GB(SKU增加了10倍)
# 5GB × 50个Executor = 250GB,Executor内存只有8GB,直接OOM!
 
# 我的处理过程:
# 第一步:降低广播阈值(临时方案)
# 但这样设备型号表就不能广播,退化到Shuffle Join,倾斜问题重新出现
 
# 第二步:分析设备型号表的查询需求
# 发现:查询里只用到 device_model_id, model_name, category 3列(共200列)
# 3列 × 500万行 ≈ 200MB,可以广播!
 
# 第三步:建立精简视图(Slim View),只暴露必要列
# 所有下游作业都JOIN精简视图而不是原始大表
# CREATE VIEW device_model_slim AS
# SELECT device_model_id, model_name, category FROM device_model_info;
 
# 效果:广播表从5GB → 200MB,重新可以广播
# 额外收益:查询计划更干净,后续维护也更清晰
 
# 教训:数据量增长时,要定期审查「维度表的使用方式」
# 随着业务发展,维度表的列数会膨胀,但查询实际用到的列很少变化
# 建立精简视图是一个低成本高收益的优化手段

总结

数据规模决定倾斜处理策略:百GB优先Broadcast;TB量级Broadcast受限,转向分桶设计+列裁剪广播;十TB及以上,从架构层面建立DWS预聚合减少明细查询,让倾斜问题在汇总层自然消失。面试时说清楚「我在某个量级的项目里遇到了什么问题、为什么百GB的方案在TB量级不适用」,比泛泛说「可以用加盐打散」更有说服力。数据量级经验是面试中重要的差异化因素,不同公司的数据规模差异很大,展示自己处理过的最大规模,是加分项。

下一篇聊一个跨系统的综合场景:从Kafka消费到Flink处理再到Doris写入,整条实时链路上哪些环节可能有倾斜?怎么端到端排查?

你们现在的数据量级是多少?最大单表有多大?倾斜在哪个环节最难处理?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器