实时数仓也要分层吗?跟离线分层完全不一样——实时分层怎么做?
本文目录
先说结论
实时数仓也要分层,但层数要少、链路要短。一般是ODS→DWD→DWS三层就够了,不需要ADS层——实时的ADS就是OLAP引擎上的查询。
跟离线最大的区别:离线每一层是'表'(存储在Hive/MaxCompute),实时每一层是'流'(Kafka Topic或Flink State)。
核心原则:实时分层尽量少做多级JOIN,因为流上的JOIN复杂度和状态开销远大于批处理。能在一层做完的不要拆两层。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
很多人知道离线数仓要分层,但一说到实时数仓就懵了——实时也要分层?数据不是来一条处理一条吗?确实,实时数仓的分层跟离线完全不是一个思路。离线分层存的是'表',实时分层流的是'流'。我在某元宇宙社交应用从0搭建Flink+Doris实时数仓时,专门设计了实时分层架构,今天把这套方案和踩坑经验分享出来。
实时分层架构:Kafka分层 + Flink处理
我在某元宇宙社交应用搭建实时数仓的架构如下:
| 层级 | 存储介质 | 处理引擎 | 典型内容 |
|---|---|---|---|
| ODS | Kafka Topic: ods_xxx | 无(直接消费原始数据) | 原始埋点日志、Binlog变更流 |
| DWD | Kafka Topic: dwd_xxx | Flink(清洗+标准化) | 清洗后的明细事件流 |
| DWS | Doris / Hologres | Flink(窗口聚合) | 分钟级/小时级聚合指标 |
注意看存储介质的变化——ODS和DWD都在Kafka里(流),到了DWS才落到OLAP引擎(表)。这就是实时分层跟离线最大的不同。
ODS层:Kafka原始Topic
实时的ODS层就是Kafka里的原始Topic。数据来源通常有两种:
① 埋点日志:客户端/服务端埋点通过SDK打到Kafka,一个事件类型一个Topic,比如topic: ods_user_gift_event(用户送礼事件)、ods_user_login_event(登录事件)。
② Binlog变更流:MySQL的Binlog通过Canal/Debezium采集到Kafka,比如topic: ods_binlog_order_table(订单表变更)。
// ODS层Kafka消息示例:用户送礼事件
{
"event_id": "evt_20231015_abc123",
"user_id": "u_998877",
"event_type": "send_gift",
"gift_id": "gift_rose",
"gift_amount": 10,
"receiver_id": "u_112233",
"timestamp": 1697356800000,
"device_os": "iOS",
"app_version": "3.2.1"
}
DWD层:Flink做流上清洗
实时DWD层是一个Flink作业,消费ODS的Kafka Topic,做清洗和标准化后写入新的Kafka Topic。跟离线DWD做的事情类似,但有几个关键区别:
-- Flink SQL实现实时DWD层:用户送礼事件清洗
INSERT INTO dwd_user_gift_event -- 写入DWD层的Kafka Topic
SELECT
event_id,
user_id,
gift_id,
gift_amount,
receiver_id,
-- 时间戳转为标准格式
TO_TIMESTAMP(FROM_UNIXTIME(`timestamp`/1000)) AS event_time,
device_os,
app_version,
-- 维度关联:通过异步IO查Redis获取礼物名称和价格
-- 这里用Flink的Temporal Join关联维度表
dim_gift.gift_name,
dim_gift.gift_price_coins
FROM ods_user_gift_event
LEFT JOIN dim_gift FOR SYSTEM_TIME AS OF ods_user_gift_event.proc_time
ON ods_user_gift_event.gift_id = dim_gift.gift_id
WHERE event_id IS NOT NULL
AND user_id IS NOT NULL;
注意这里的维度关联方式:离线DWD用的是普通的LEFT JOIN(维度表是Hive表),实时DWD用的是Flink的Temporal Join(维度表在MySQL/Redis中)。这是面试经常被追问的一个点——流上怎么做维度关联。
DWS层:Flink窗口聚合 → 写入OLAP
实时DWS层是另一个Flink作业,消费DWD层的Kafka Topic,做窗口聚合后写入Doris或Hologres:
-- Flink SQL实现实时DWS层:每分钟送礼汇总
INSERT INTO dws_gift_summary_1min -- 写入Doris/Hologres
SELECT
TUMBLE_START(event_time, INTERVAL '1' MINUTE) AS window_start,
gift_id,
gift_name,
COUNT(*) AS gift_cnt,
COUNT(DISTINCT user_id) AS send_uv,
COUNT(DISTINCT receiver_id) AS receive_uv,
SUM(gift_price_coins) AS total_coins
FROM dwd_user_gift_event
GROUP BY
TUMBLE(event_time, INTERVAL '1' MINUTE),
gift_id,
gift_name;
聚合结果直接写入Doris,前端大屏查Doris就行。不需要再建ADS层——Doris上的SQL查询就是'实时ADS'。
离线分层 vs 实时分层对比
| 维度 | 离线分层 | 实时分层 |
|---|---|---|
| 层数 | 4层(ODS/DWD/DWS/ADS) | 3层(ODS/DWD/DWS),不需要ADS |
| 存储介质 | 每层都是表(Hive/MaxCompute) | ODS/DWD是Kafka Topic,DWS是OLAP表 |
| 处理引擎 | Spark SQL / Hive | Flink SQL / Flink DataStream |
| 维度关联 | 普通LEFT JOIN | Temporal Join / 异步IO + Redis |
| 更新频率 | T+1 / 小时级 | 秒级 / 分钟级 |
| 数据修正 | 重跑即可 | 需要额外的修正机制(离线兜底) |
| 调度方式 | DAG调度(DataWorks/Airflow) | 常驻作业,7×24小时运行 |
| 开发复杂度 | 中等 | 高(状态管理、Exactly-Once、反压) |
面试高频追问
追问一:实时DWD写回Kafka,那Kafka岂不是存了两份数据?成本怎么控制?
没错,Kafka分层确实会增加存储成本。但Kafka的存储成本远低于Hive/HDFS,而且Kafka Topic可以设置较短的保留时间(比如DWD只保留3天)。权衡下来,存储成本增加不大,但获得的好处是DWD可以被多个下游Flink作业复用——比如DWS_gift和DWS_revenue两个聚合作业都可以消费同一个DWD Topic,避免重复清洗。如果不分层,每个下游作业都要各自清洗一遍,计算资源浪费更大。
追问二:实时分层链路挂了怎么办?数据丢了怎么修?
这是实时数仓最大的痛点——数据修正。离线数仓重跑一遍就行,实时数仓的Kafka数据过了保留期就没了。我们的做法是'实时出数 + 离线兜底':实时链路负责快速出数,离线链路每天凌晨跑一遍相同口径的汇总,覆盖写入同一张OLAP表。这样即使实时链路有数据质量问题,第二天离线也会修正过来。这就是Lambda架构的核心价值——实时和离线互相兜底。
追问三:为什么不把所有指标都做成实时?
成本和必要性。实时链路的开发成本是离线的3-5倍(要处理乱序、状态、Checkpoint、反压),运维成本更高(7×24小时常驻作业)。大部分业务指标T+1就够了,只有核心指标(比如广告ROI、实时大屏、风控告警)才需要实时。我们的原则是:只有'业务决策时效性 < 1小时'的指标才走实时链路。
实战踩坑
我在某元宇宙社交应用搭建实时数仓时,一开始没有做Kafka分层——所有Flink作业都直接消费ODS层的原始Topic。一开始只有3个实时作业,问题不大。后来增长到10+个,每个作业都在各自清洗同样的原始数据,不仅浪费了大量Flink资源,而且不同作业的清洗逻辑开始出现不一致——同一个'活跃用户'的定义,有的作业过滤了机器人账号,有的没过滤。
后来我们花了两周时间,把所有原始Topic的清洗逻辑抽出来,建了一组DWD层的Kafka Topic,统一定义了清洗标准。所有下游作业都消费DWD Topic。清洗逻辑只维护一份,口径一致性问题彻底解决了。这也是实时分层最核心的价值——不是为了'规范',而是为了'统一'和'复用'。
总结
实时数仓分层的核心是:ODS/DWD在Kafka里流转,DWS落地到OLAP引擎。跟离线分层最大的区别是存储介质不同、维度关联方式不同、数据修正机制不同。面试时讲实时分层,要重点说清楚'为什么要分层'(复用+口径统一)、'Kafka怎么分层'、'维度关联怎么做'三个点。如果能再说出'实时出数+离线兜底'的修正机制,那就很有深度了。
离线分层和实时分层都讲清楚了,那两个链路怎么统一?下一篇聊Lambda架构和Kappa架构的选型——我为什么最终选了Lambda。
你们的实时数仓分了几层?DWD是存在Kafka里还是直接写OLAP?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长