实时数仓架构设计:Kafka + Flink + ClickHouse/Doris 全链路生产实践
一、文章导语
字节跳动的实时数仓支撑着抖音、今日头条等核心产品,每日处理数据量超 2.5 万亿条,峰值 QPS 达到 8000 万/秒。这不是一个技术炫技的数字------背后是推荐系统对数据时效性的极致要求:用户刚看完一个视频,推荐系统需要在秒级根据这次观看行为更新用户画像。
实时数仓从"锦上添花"变成"核心基建",背后有两股推力:
- 业务实时化:实时大屏、实时风控、实时推荐,T+1 的离线报表已经无法满足决策需求
- 技术成熟度:Flink 的 Exactly-Once 语义、ClickHouse/Doris 的极致查询性能、Kafka 的高吞吐------三个技术拼图都已就位
本文聚焦实时数仓的架构设计,拆解 Lambda/Kappa/流批一体三种架构的取舍,深入 Kafka + Flink + ClickHouse/Doris 的全链路方案,用字节跳动的真实案例展示千亿级实时数仓的落地细节。
二、核心架构技术讲解
2.1 Lambda vs Kappa vs 流批一体:架构演进路线
Lambda 架构(2011 年,Nathan Marz 提出):
实时数据 → 流处理层(Storm/Flink)→ 实时视图
↘ 合并 → 服务层
离线数据 → 批处理层(Spark/Hive)→ 批视图
Lambda 的问题是两套代码、两套逻辑------流处理和批处理用不同的代码实现同样的业务逻辑,维护成本极高。一个口径变更需要同时修改两处,而且容易产生流批结果不一致的 Bug。
Kappa 架构(2014 年,Jay Kreps 提出):
所有数据 → Kafka(消息队列作为唯一数据源)
↓
Flink 流处理
↓
实时存储(ClickHouse/Doris)
Kappa 的思想是"一切皆流"------只有流处理,没有批处理。如果需要重算历史数据,就从 Kafka 中回放。但这个方案有两个致命弱点:
- Kafka 不适合长期存储 TB/PB 级历史数据(成本高、查询难)
- 大规模数据回放耗时太长
流批一体架构(当前主流):
实时数据 → Kafka → Flink 流处理 → ClickHouse/Doris(实时层)
↕
离线数据 → 对象存储 → Flink/Spark 批处理 → Iceberg/Hudi(离线层)
↕
Trino 联邦查询(服务层)
流批一体的核心思路:用同一套 Flink 代码处理实时和离线,用不同的存储引擎分别服务实时查询和历史分析。
2.2 Kafka:实时数仓的"主动脉"
Kafka 在实时数仓中的角色远不止"消息队列"------它是数据的统一入口和缓冲层。
关键设计要点:
1. 分区策略决定数据局部性
分区键的选择直接影响下游处理的数据倾斜程度。以电商订单为例:
java
// 按 user_id 哈希分区:保证同一用户的数据在同一分区
// 下游做用户维度的聚合时不需要 shuffle
producer.send(new ProducerRecord<>("ods_orders",
userId.hashCode() % partitionCount, orderJson));
错误做法是按 order_id 分区------虽然分布均匀,但下游做用户维度聚合时必须跨分区 shuffle,性能急剧下降。
2. 数据保留策略
实时数仓中 Kafka 的数据保留时间建议 3-7 天------既能支撑故障恢复时的数据回放,又不会让存储成本失控。字节跳动的实践是保留 7 天,配合分层存储(热数据 SSD、冷数据 HDD)。
3. 日志 compaction
对于 CDC 场景(数据库变更捕获),Kafka 需要开启日志压缩------保留每个 key 的最新值,而不是所有历史值。这能大幅减少存储量。
2.3 Flink:实时 ETL 的核心引擎
Flink 在实时数仓中承担流式 ETL 的职责------清洗、关联、聚合。
核心场景 1:多流 Join
java
// 订单流关联用户流(双流 JOIN)
DataStream<Order> orderStream = ...;
DataStream<User> userStream = ...;
orderStream.keyBy(Order::getUserId)
.intervalJoin(userStream.keyBy(User::getId))
.between(Time.minutes(-5), Time.minutes(0)) // 5 分钟窗口
.process(new OrderUserJoinFunction());
双流 JOIN 的难点在于乱序数据------订单事件可能早于用户注册事件到达。解决方案:
- 设置合理的时间窗口(如上例中的 5 分钟)
- 使用 Flink 的 Watermark 机制处理乱序
- 对于长期关联需求(如订单关联商品详情),改用维表 JOIN(Flink 的
AsyncIO异步查询外部存储)
核心场景 2:多级聚合
实时数仓的聚合链路通常是多级的:
ODS(原始数据)→ DWD(明细宽表)→ DWS(汇总层)→ ADS(应用层)
每一级都有不同的聚合粒度:
- DWD:数据清洗 + 维表关联,产出明细宽表
- DWS:按时间窗口聚合(1min/5min/1h),产出轻度汇总
- ADS:按业务维度聚合(按商家、按品类),产出业务指标
关键性能优化:
- Flink 的
minibatch优化:聚合前先攒一小批数据,减少状态访问次数 - RocksDB 状态后端:处理大状态(> 内存容量)的唯一选择,但需要调优
state.backend.rocksdb.block.cache-size Local-Global聚合:先本地预聚合,再全局聚合,减少网络传输
2.4 ClickHouse vs Doris:实时存储引擎选型
| 维度 | ClickHouse | Apache Doris |
|---|---|---|
| 查询延迟 | 毫秒级(单表) | 毫秒级 |
| 并发能力 | 弱(<100 QPS) | 强(1000+ QPS) |
| JOIN 能力 | 弱(大表 JOIN 几乎不可用) | 强(Colocate Join) |
| 数据更新 | 异步 Mutation(慢) | Unique Key 模型(实时 Upsert) |
| 运维复杂度 | 高(手动管理副本) | 低(FE/BE 自动管理) |
| 适用场景 | 日志分析、监控 | 实时报表、多维分析 |
选型建议:
- 纯日志分析 / 监控场景 → ClickHouse(极致单表查询性能)
- 需要多表 JOIN / 高并发查询 / 实时 Upsert → Doris
- 混合场景 → 两者共存,Doris 做服务层,ClickHouse 做深度分析
三、实战落地要点:字节跳动千亿级实时数仓
字节跳动的实时数仓是业界最著名的案例之一。核心架构:
业务数据 → Kafka(数千分区)
↓
Flink(数千并行度)
↓
Apache Doris(数百节点)
↓
实时 BI / 实时推荐
关键工程实践:
1. Flink 任务管理:数千个 Flink 任务通过自研的流式平台统一管理,支持任务的自动扩缩容、Savepoint 恢复、灰度发布。
2. 数据质量保障:实时数据最怕"静默错误"------数据写入成功但内容错误。字节的方案是"对账机制":每条实时数据都有一个对应的离线数据(T+1 产出的 Hive 表),每天自动对账,发现差异后告警。
3. 大状态处理:用户画像类任务的窗口状态可能达到 TB 级。字节的优化包括:
- RocksDB 的增量 Checkpoint(只保存变化的部分,而非全量状态)
- 状态 TTL(超过 7 天未更新的状态自动清理)
- 使用 Flink 的
KeyedStateStore配合外部 KV 存储(如 Redis)分担状态压力
四、架构痛点与避坑指南
坑 1:Kafka 分区数规划不当
某团队上线后发现 Flink 消费速度跟不上 Kafka 生产速度。排查发现 Kafka 只有 8 个分区,而 Flink 最大并行度受分区数限制------1 个分区只能被 1 个 Flink SubTask 消费。经验法则:Kafka 分区数 = Flink 目标并行度 × 2(留余量)。
坑 2:Flink 反压雪崩
下游 ClickHouse 写入变慢 → Flink 反压 → Kafka 消费延迟 → 数据积压 → Checkpoint 超时 → 任务重启 → 重启后从上一个 Savepoint 恢复,需要追历史数据 → 压力更大 → 再次反压......这是一个经典的恶性循环。解法:
- 设置 Flink 的反压比例告警
- 启用自适应调度(Adaptive Scheduler),自动调整并行度
- 写入端设置合理的超时和重试策略,避免阻塞整个 DAG
坑 3:Doris 的数据更新冲突
Doris 的 Unique Key 模型虽然支持实时 Upsert,但高频更新同一个 Key 会导致版本合并压力。字节的经验是将更新频率控制在秒级以上,毫秒级的频繁更新场景用 Redis 而不是 Doris。
五、全文总结
实时数仓的建设重点不在于技术选型(Kafka + Flink + ClickHouse/Doris 已经是非常成熟的标准方案),而在于工程化落地:数据质量保障、任务运维管理、容量规划、成本控制。
三个关键决策点:
- 架构选型:流批一体 > Kappa > Lambda
- 存储选型:Doris(多表 JOIN + 高并发)> ClickHouse(单表极致性能)
- 分区规划:提前规划 Kafka 分区数,预留 2 倍余量
六、行业技术展望
- Flink 2.0:原生支持存算分离,流批一体能力进一步增强
- Doris 2.1:支持 Iceberg 外部表的物化视图,湖仓一体的查询体验进一步提升
- Serverless 实时数仓:阿里云 Hologres、AWS Redshift Serverless 正在降低实时数仓的运维门槛
参考文献
- Apache Flink. Streaming Analytics. https://nightlies.apache.org/flink/
- Apache Kafka. Documentation. https://kafka.apache.org/documentation/
- Apache Doris. Official Documentation. https://doris.apache.org/docs/
- ClickHouse. Performance Optimization Guide. https://clickhouse.com/docs/en/guides/improving-performance/
- 字节跳动技术团队. Flink + Kafka + Doris 千亿级实时数仓架构实践. 2024.
- Jay Kreps. Questioning the Lambda Architecture. 2014.