实时数仓架构设计:Kafka + Flink + ClickHouse/Doris 全链路生产实践

一、文章导语

字节跳动的实时数仓支撑着抖音、今日头条等核心产品,每日处理数据量超 2.5 万亿条,峰值 QPS 达到 8000 万/秒。这不是一个技术炫技的数字------背后是推荐系统对数据时效性的极致要求:用户刚看完一个视频,推荐系统需要在秒级根据这次观看行为更新用户画像。

实时数仓从"锦上添花"变成"核心基建",背后有两股推力:

  1. 业务实时化:实时大屏、实时风控、实时推荐,T+1 的离线报表已经无法满足决策需求
  2. 技术成熟度: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 中回放。但这个方案有两个致命弱点:

  1. Kafka 不适合长期存储 TB/PB 级历史数据(成本高、查询难)
  2. 大规模数据回放耗时太长

流批一体架构(当前主流):

复制代码
实时数据 → 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 已经是非常成熟的标准方案),而在于工程化落地:数据质量保障、任务运维管理、容量规划、成本控制。

三个关键决策点:

  1. 架构选型:流批一体 > Kappa > Lambda
  2. 存储选型:Doris(多表 JOIN + 高并发)> ClickHouse(单表极致性能)
  3. 分区规划:提前规划 Kafka 分区数,预留 2 倍余量

六、行业技术展望

  1. Flink 2.0:原生支持存算分离,流批一体能力进一步增强
  2. Doris 2.1:支持 Iceberg 外部表的物化视图,湖仓一体的查询体验进一步提升
  3. Serverless 实时数仓:阿里云 Hologres、AWS Redshift Serverless 正在降低实时数仓的运维门槛

参考文献

  1. Apache Flink. Streaming Analytics. https://nightlies.apache.org/flink/
  2. Apache Kafka. Documentation. https://kafka.apache.org/documentation/
  3. Apache Doris. Official Documentation. https://doris.apache.org/docs/
  4. ClickHouse. Performance Optimization Guide. https://clickhouse.com/docs/en/guides/improving-performance/
  5. 字节跳动技术团队. Flink + Kafka + Doris 千亿级实时数仓架构实践. 2024.
  6. Jay Kreps. Questioning the Lambda Architecture. 2014.
相关推荐
我命由我1234519 小时前
Windows 操作系统 - 符号链接
linux·运维·windows·系统架构·操作系统·运维开发·系统
在水一缸21 小时前
当 AI 编码助手遇上 Rust:深入解析 Zerostack 的极简哲学与实战应用
rust·系统架构·开源项目·轻量化·ai编码助手·zerostack
慧一居士1 天前
Linux 发行版深度对比:RHEL/CentOS vs Debian/Ubuntu
系统架构
AI产品测评官1 天前
企业级 HR 系统架构深度选型:一体化传统 ATS 与前置 AI招聘 获客智能体的边界与协同
人工智能·系统架构
hsw8157739431 天前
第10章 软件架构的演化和维护 — 系统架构设计师
unity·系统架构·游戏引擎
hsw8157739431 天前
第11章 未来信息综合技术 — 系统架构设计师
系统架构
画中有画1 天前
架构评估方法(ATAM)在系统架构设计中的应用
架构·系统架构
ASKED_20192 天前
一文阐述国内网络环境下使用NIM方法
人工智能·系统架构
youngerwang2 天前
【无标题】架构设计案例每日一深耕 Day 1:数据流风格在ETL系统中的应用
系统架构