订阅更新

业务方要"数据T+0"——离线全量+实时增量的合并策略怎么设计?

数据采集手册 第 20 题 约 21 分钟读完 1 次阅读

本文目录
  1. 先说结论
  2. 数据采集20题通关总结篇
  3. 一、20题速查表
  4. 二、难度分布与优先级
  5. 三、面试三板斧
  6. 四、5条通关建议
  7. 五、下一站

先说结论

T+0的核心技术挑战:历史数据(全量)准但慢、当日数据(增量)快但可能不准,怎么在一个查询里合并看到"截至此刻的完整状态"。

三种主流实现:① UNION合并(离线T-1 + 实时当日);② 实时表+历史基线(实时表只存当日);③ Lakehouse MERGE(同一张表流批共写)。

落地关键:主键幂等+op_ts版本号+边界时刻处理+监控告警。缺一个都可能翻车。

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

详细讲解

"T+1太慢了,要实时!"——业务方这个诉求每个数据团队都听过。"数据T+0"意思是当天数据当天就能看到最新状态,典型场景是实时大盘、当日漏斗分析、实时用户画像。这道题面试官想看的是你能不能设计出"离线准确+实时及时"的融合方案,同时避免Lambda架构的治理痛点。

业务场景:运营大盘的T+0改造

一个GMV大盘最初是T+1,业务每天早上10点看前一天数据。后来运营要求"当日GMV要实时看到",改造成T+0:离线层存T-1及以前的历史全量、实时层只存当日数据、查询时UNION合并。改造后延迟从24小时降到5秒。

-- T+0合并查询的实现思路
 
-- 离线表: dwd_orders_hist (Hive/Iceberg)
-- 包含: 历史所有订单,截止到昨天23:59:59
-- 分区: ds='20240314' 及以前
-- 更新: 每天凌晨3点通过Spark批跑
 
-- 实时表: dwd_orders_rt (Hologres)
-- 包含: 今天00:00到当前时刻的订单
-- 更新: Flink CDC实时写入,秒级延迟
 
-- 合并查询: 给BI/看板使用
CREATE VIEW dwd_orders_unified AS
SELECT order_id, user_id, amount, status, op_ts, 'hist' AS source
FROM dwd_orders_hist
WHERE ds <= DATE_FORMAT(DATE_SUB(CURRENT_DATE, 1), 'yyyyMMdd')
UNION ALL
SELECT order_id, user_id, amount, status, op_ts, 'rt' AS source
FROM dwd_orders_rt
WHERE op_ts >= CAST(CURRENT_DATE AS TIMESTAMP);
 
-- 实时大盘查询(基于视图)
SELECT DATE(op_ts) AS dt, SUM(amount) AS gmv
FROM dwd_orders_unified
WHERE op_ts >= DATE_SUB(CURRENT_DATE, 7)
GROUP BY 1;

方案一:UNION合并(最通用)

离线表保存T-1及以前历史,实时表只保存当日数据,查询时UNION。通用且容易实现,但边界处理需要小心。

-- 关键: 边界时刻的处理(避免重复或遗漏)
 
-- 场景: 凌晨跨日切换时
-- 离线层凌晨0:00之后开始跑T-1全量,大概3:00完成
-- 这3个小时内,查询dwd_orders_hist能看到T-1数据吗?
--   - 如果凌晨0:05查: T-1分区还没跑完,hist里没有T-1数据
--   - 如果凌晨4:00查: T-1分区已经好了,hist有T-1数据
 
-- 解法一: 用current_max(hist_ds) + 1天作为实时层起点
SELECT * FROM dwd_orders_hist
WHERE ds <= (SELECT MAX(ds) FROM dwd_orders_hist)
UNION ALL
SELECT * FROM dwd_orders_rt
WHERE op_ts > (SELECT MAX(ds) || ' 23:59:59' FROM dwd_orders_hist);
-- 这样离线还没跑完时,实时层会多覆盖前一天,保证不漏
 
-- 解法二: 实时层多保留一天
-- Flink CDC同时写两份数据:
-- 1) dwd_orders_rt (当日数据,24小时TTL)  
-- 2) dwd_orders_rt_backup (保留7天,兜底)
-- 离线层凌晨出问题时,从backup补
 
-- 解法三: 用source字段优先级去重
WITH merged AS (
  SELECT *, 1 AS pri FROM dwd_orders_hist WHERE ds <= '${T-1}'
  UNION ALL
  SELECT *, 2 AS pri FROM dwd_orders_rt WHERE op_ts >= '${T-1 00:00:00}'
)
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY pri, op_ts DESC) rn
  FROM merged
) WHERE rn = 1;
-- hist和rt重叠时取hist优先(准确),无重叠时rt独占

适用场景:业务对历史数据准确性要求高;有现成Hive/Iceberg离线层;BI工具支持UNION视图。

方案二:实时表+历史基线快照

思路是把"截至昨天的累计值"存成实时表的一个基线快照,今天的实时数据只需要累加。查询不用UNION,性能好。

-- 实时表同时维护基线和增量
CREATE TABLE rt_gmv_realtime (
  dim_key STRING,         -- 维度(产品/渠道)
  dt       STRING,         -- 日期
  baseline DECIMAL(20,2),  -- T-1 23:59:59的累计值(凌晨刷)
  delta    DECIMAL(20,2)   -- 今天新增(实时累加)
) WITH ('connector' = 'hologres');
 
-- 凌晨1:00: 从离线表加载baseline
INSERT OVERWRITE TABLE rt_gmv_realtime
SELECT dim_key, '${T}' AS dt,
  COALESCE((SELECT SUM(gmv) FROM dws_gmv_hist WHERE ds < '${T}' AND dim_key = ...), 0) AS baseline,
  0 AS delta;
 
-- 白天: Flink实时更新delta
UPDATE rt_gmv_realtime SET delta = delta + ${new_order_amount}
WHERE dim_key = ${dim} AND dt = CURRENT_DATE;
 
-- 查询超简单
SELECT dim_key, baseline + delta AS total_gmv
FROM rt_gmv_realtime WHERE dt = CURRENT_DATE;
 
-- 优势: 查询性能好,无UNION/JOIN
-- 劣势: 只支持累加型指标,不支持明细查询

方案三:Lakehouse批流一体MERGE

用Paimon/Iceberg/Hudi这种支持流批一体的存储,同一张表接受Flink流式upsert和Spark批式覆盖,天然T+0。

-- Paimon主键表,流批一体
CREATE TABLE dwd_orders (
  order_id BIGINT,
  user_id BIGINT,
  amount DECIMAL(10,2),
  status STRING,
  op_ts TIMESTAMP(3),
  PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
  'bucket' = '64',
  'changelog-producer' = 'lookup',
  'merge-engine' = 'partial-update',  -- 支持部分字段更新
  'sequence.field' = 'op_ts'          -- 用op_ts做版本控制
);
 
-- Flink流式写入(实时来的CDC数据)
INSERT INTO dwd_orders
SELECT * FROM mysql_cdc_orders;
 
-- Spark批式修正(T+1跑全量对账,修正实时漏掉的)
MERGE INTO dwd_orders t
USING (
  SELECT * FROM staging_orders_batch WHERE ds = '${T-1}'
) s ON t.order_id = s.order_id
WHEN MATCHED AND s.op_ts > t.op_ts THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
 
-- 下游查询: 永远查最新(实时+离线融合后的结果)
SELECT SUM(amount) FROM dwd_orders
WHERE DATE(op_ts) >= DATE_SUB(CURRENT_DATE, 7);

三方案对比

维度UNION合并基线+增量Lakehouse
查询性能中(UNION成本)高(直接取)高(主键查找)
存储成本中(两份表)低(单表)低(一份)
灵活度高(支持明细)低(只累加)高
实现复杂度低中高(新栈)
边界处理需要UNION逻辑baseline切换时刻MERGE自动处理
典型场景通用报表核心指标大盘新建流批一体

面试高频追问

追问一:凌晨0点切换时怎么避免数据抖动?

凌晨是T+0最容易翻车的时刻——离线T-1全量跑到一半、实时当日数据刚开始、两边合并可能出现"凌晨0:00~3:00的GMV=0"的诡异现象。对策:① 离线跑T-1的时候,实时层不要重置;② 查询层用"实时层优先"——离线有T-1时才用离线覆盖实时;③ 前端BI工具加"数据时间"标签,明确告诉用户当前看到的是几点的快照。生产上最稳的做法是保留实时层的当日+前一天,离线没跑完时都用实时的。

追问二:业务方说实时数据跟离线对不上怎么办?

几乎100%会出现。常见差异来源:① 边界时刻(23:59:59左右的订单归哪天);② 实时漏数据(Flink丢Checkpoint回放窗口内的数据);③ 离线口径和实时口径细微差异(status过滤规则、字段计算公式)。对策:① 建一个"实时vs离线对账表",每天自动跑,差异率>0.5%告警;② 差异必然存在但要可解释,把差异原因写成文档给业务方;③ 核心指标用Lakehouse方案从根上消除差异。

追问三:大盘查询慢怎么优化?

T+0查询性能三个优化点:① 预聚合——DWS层做好分钟/小时级预聚合,大盘查预聚合表不查明细;② 物化视图——Hologres/StarRocks支持MV,基础表变化自动更新MV;③ 物理表存储——用列存+索引(OLAP专项设计),避免全表扫描。生产上大盘查询响应时间目标:p99 < 2秒,超过就必须优化。

实战踩坑:离线跑挂了导致T+0数据停摆

有次周日凌晨Spark集群故障,T-1的离线作业全部失败,早上10点业务方看大盘发现"前7天数据全是0"。排查才发现T+0的查询强依赖dwd_orders_hist表的T-1分区,分区不存在就空了。教训是T+0架构必须有兜底:离线挂了实时能独立支撑一段时间,实时挂了离线的T-1数据能顶。

-- ❌ 反模式: 查询硬依赖T-1分区存在
SELECT * FROM dwd_orders_hist
WHERE ds = DATE_FORMAT(DATE_SUB(CURRENT_DATE, 1), 'yyyyMMdd');
-- 如果T-1分区没跑完,结果为空,大盘空白
 
-- ✓ 兜底方案: 用实际存在的最新分区
WITH latest_hist AS (
  SELECT MAX(ds) AS ds FROM dwd_orders_hist
)
SELECT * FROM dwd_orders_hist
WHERE ds = (SELECT ds FROM latest_hist)
UNION ALL
-- 实时层覆盖(实际最新分区)+1天到现在
SELECT * FROM dwd_orders_rt
WHERE op_ts > (SELECT DATE_ADD(ds, 1) || ' 00:00:00' FROM latest_hist);
 
-- 监控: 离线最新分区 vs current_date - 1
SELECT DATEDIFF(CURRENT_DATE, MAX(ds)) AS lag_days
FROM dwd_orders_hist;
-- lag_days > 1 时告警(离线延迟超过1天)
-- lag_days > 3 时P0(严重影响T+0)

解决方案:查询逻辑用"最新存在的分区"而不是"T-1硬编码",加离线延迟监控,Flink CDC实时层独立部署确保不依赖离线集群。这套兜底让后来再出现离线故障时大盘还能显示截至最新可用时间的数据。

总结

T+0本质是"离线准+实时快"的融合,核心挑战是边界处理和一致性。UNION合并通用、基线+增量性能好、Lakehouse最优雅。生产关键是:主键幂等、op_ts版本号、兜底方案、对账告警。面试时按"诉求→方案对比→边界处理→踩坑"讲,比只说"用Lambda就行"分高得多。数据采集系列20道题到这里就结束了,感谢看到这里的你!

数据采集系列20篇到此结束!下一个系列聊OLAP引擎对比——Doris/StarRocks/ClickHouse/Hologres怎么选?

整个数据采集系列最有感触的是哪一篇?有什么踩过的坑想交流?评论区见~

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

数据采集 面试通关手册

总结篇:20题速查与面试三板斧

Wood聊大数据

9年大数据老兵,聊技术、聊职场、聊成长

数据采集20题通关总结篇

一、20题速查表

题号场景关键词难度
01Canal采集MySQL Binlog延迟暴涨Binlog position / parser线程 / Kafka sink★★★
02DataX/Canal/Flink CDC怎么选全量vs增量 / 侵入性 / 精确一次★★★★
0310亿行MySQL迁到ODS分片 / 并发 / 断点续传 / 校验★★★★
04Schema演进DDL传不传Avro兼容 / 字段新增删除 / Schema Registry★★★★
05CDC打爆MySQL源库Binlog量 / 从库分流 / 限流兜底★★★
06埋点SDK上报可靠性SQLite持久化 / 批量ack / 重试退避★★★
07埋点实时离线双链路Kafka分流 / Lambda vs Kappa★★★
08Filebeat吃满CPUharvester / queue.mem / backoff★★★
09Flume Channel怎么选Memory/File/Kafka Channel三选一★★★
10埋点Schema治理埋点注册 / 版本号 / 前后端校验★★★★
11跨时区采集业务时间UTC毫秒戳 / ZoneId / 夏令时★★★★
12主数据MDM确权One ID / ID Mapping / 数据血缘★★★★★
13Kafka Connect vs Flink CDC算子生态 / 状态管理 / 社区活跃度★★★
14历史数据回溯重算批流一体 / 基线版本 / 幂等写入★★★★
15第三方API采集限流token bucket / 指数退避 / 断点续传★★★
16采集任务三指标监控Latency / Completeness / Accuracy★★★★
17Exactly-Once边界两阶段提交 / 幂等key / 去重窗口★★★★★
18DataWorks数据集成慢channel / splitPk / batchSize / 网络★★★
19Lambda vs Kappa vs Lakehouse双链路 / 单链路 / Paimon/Iceberg★★★★
20T+0离线全量+实时增量合并UNION / 基线+增量 / Lakehouse MERGE★★★★★

二、难度分布与优先级

P0必背(8题)——不会就扣分

这8题是大数据工程师采集岗的基础考察,面试官默认你必须会。答不出就是硬伤。

P0_must_know.txt

01 Canal延迟排查    —— 最常见的线上问题
02 CDC工具选型      —— 架构判断的入场券
05 CDC打爆源库      —— DBA和数据团队的拉扯
06 埋点SDK可靠性    —— C端业务必考
08 Filebeat调优     —— 日志采集的基本功
11 跨时区处理       —— 出海业务必考
16 采集监控三指标   —— 运维视角的成熟度
18 DataWorks调优    —— 阿里系公司必考

P1加分(8题)——会了能拉开差距

这8题开始考架构判断和权衡能力。答得好,面试官会立刻给你加分。

P1_bonus.txt

03 10亿行迁移       —— 考你会不会拆问题
04 Schema演进       —— 考你懂不懂兼容性
07 双链路选择       —— 考你懂不懂Lambda/Kappa
09 Channel选型      —— 考你懂不懂背压
10 埋点Schema治理   —— 考你懂不懂治理
13 Kafka Connect对比 —— 考你懂不懂生态
15 API限流          —— 考你懂不懂退避算法
19 三种架构对比     —— 考你懂不懂Lakehouse

P2拔高(4题)——高阶岗位必问

这4题是高级/资深大数据工程师岗位的分水岭。答不出不扣分(初级岗),但答得好直接往上跳一级。

P2_senior.txt

12 MDM主数据        —— 考你有没有数据治理视角
14 历史数据回溯     —— 考你懂不懂批流一体
17 Exactly-Once     —— 考你懂不懂两阶段提交
20 T+0合并策略      —— 考你有没有端到端设计能力

三、面试三板斧

不是讲技术多深,是讲得让面试官听得爽

第一板斧:场景化回答(STAR结构)

面试官问采集题,最怕听到的开场是:"这个呢,一般有几种方案……"。这种回答听起来就是背八股。

正确打开方式:先讲一个具体场景,把面试官带进来。

STAR_answer_template.txt

S (Situation) 场景:
  『我之前做用户支付实时大盘,上游是MySQL订单表,每秒峰值2000笔……』
 
T (Task) 任务:
  『要求秒级延迟,但某次大促当天延迟从3秒飙到5分钟……』
 
A (Action) 行动:
  『我先看Canal的position是不是在动,发现position没动——
   再去MySQL看show master status,发现Binlog在涨但Canal没追上……』
 
R (Result) 结果:
  『最后定位到Canal parser线程OOM,调大内存+开启并行解析,
   延迟降回200ms内,全程修复花了40分钟……』

1分钟的回答里,场景30秒+行动20秒+结果10秒,面试官会觉得你真的做过

第二板斧:给出数字

没数字的回答等于没说。每一个架构决策,背后都有数字支撑。

反面示例(扣分)正面示例(加分)
我们做了CDC采集用Canal做CDC,Binlog每秒5万条,延迟P99在200ms内
埋点SDK做了可靠性保障SQLite本地缓存+批量上报,丢失率从0.3%降到0.02%
迁移了一批历史数据10亿行MySQL迁到ODS,16并发+splitPk切片,用了8小时
做了Exactly-Once基于Kafka事务+下游幂等key,重复率<0.001%
监控了采集延迟Prometheus三指标:Latency P99<1s / Completeness>99.95% / Accuracy>99.99%

第三板斧:主动引面试官

答完一题,不要干等下一题。主动把话题引到你熟悉的方向上。

lead_the_interview.txt

被问:Canal怎么保证不丢数据?
基础回答:position持久化+ack机制。
 
加分回答(答完后主动引):
  『position持久化+ack机制能保证At-Least-Once。
   如果业务要Exactly-Once,还要配合下游幂等,
   这块我之前在做支付大盘的时候踩过一个坑——
   Kafka事务和MySQL幂等key的边界,您要听吗?』
 
→ 面试官100%会说"你讲讲",这一下你就从被动答题变成主动分享
→ 聊你熟的东西,比被面试官问你不熟的要安全10倍

四、5条通关建议

建议1:简历上的项目细节,必须经得起30分钟追问

不要在简历上写你只了解架构图的项目。面试官会一层一层往下挖:用了什么版本的Canal?为什么选MemoryChannel?Binlog一天多少GB?只要有一层答不出,整个项目的可信度就塌了。

建议2:每个技术选型,必须能说出3种备选和1条决策线

"我们选了Flink CDC" → 扣分。

"对比过DataX/Canal+Kafka/Flink CDC三种,DataX不支持增量、Canal链路长、Flink CDC一体化最省运维,我们选了Flink CDC" → 加分。

建议3:踩坑故事比技术深度更能打动面试官

大家都会讲CDC原理,但踩过坑的人才会讲Binlog格式ROW/STATEMENT/MIXED在主从切换时的坑。踩坑故事是你区别于PPT工程师的最大武器。

建议4:面试前30分钟,重读实战踩坑小节

这本手册每一篇的实战踩坑小节,都是我和同行真实遇到的问题。面试当天早上扫一遍,开场答题时顺手带一两个坑,面试官立刻知道你是真做过的。

建议5:敢说不会,比不懂装懂强10倍

面试官最怕的是半桶水装满桶水的候选人。遇到你确实没接触过的题,大方说:"这块我之前没深入做过,但我了解思路是XXX,如果进公司我会花1-2周把它补起来。" 这个回答比硬编答案拿到offer的概率高得多。

五、下一站

数据采集20篇到这里正式收官。

你在采集面试里遇到过最刁钻的一道题是什么?评论区聊聊,我挑几道加到v2版本里。

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

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