土法炼钢兴趣小组的算法知识备份

【系统架构设计百科】数据密集型架构:批流一体与 Lakehouse

文章导航

分类入口
architecture
标签入口
#data-intensive#Lakehouse#Flink#Spark#Delta-Lake#Iceberg#Lambda-architecture

目录

当一个电商平台的订单数据从每天 500 万条增长到每天 5 亿条,当业务部门要求从”T+1 看报表”变成”实时看大盘”,当数据团队同时维护一套 Hadoop 批处理管线和一套 Kafka + Storm 实时管线并为两条链路的数据不一致焦头烂额时——数据密集型架构(Data-Intensive Architecture)的核心矛盾便暴露无遗。过去十年,行业经历了从 MapReduce 到 Lambda 架构(Lambda Architecture),再到 Kappa 架构(Kappa Architecture),最终走向批流一体(Unified Batch and Streaming)与湖仓一体(Lakehouse)的完整演进路径。本文将沿着这条主线,拆解每一步演进背后的技术动因与工程取舍。

一、数据架构的演进:从批处理到批流一体

1.1 批处理时代:MapReduce 与 Hive

2004 年 Google 发表 MapReduce 论文,Hadoop 生态随后在工业界普及。这一阶段的数据处理模式非常直接:

  1. 数据落入 HDFS(Hadoop Distributed File System);
  2. 定时调度 MapReduce / Hive 任务做 ETL(Extract-Transform-Load);
  3. 结果写入数据仓库(Data Warehouse),供 BI 工具查询。

这套架构的优点是简单、可靠、吞吐量大,但致命弱点也很明显——延迟高。一次完整的批处理管线从数据采集到结果可用,通常需要数小时甚至隔天(T+1)。对于实时风控、实时推荐、实时监控等场景,T+1 完全不可接受。

1.2 实时处理的兴起:Storm 与 Kafka

为了填补延迟空白,Storm、Samza 等流处理(Stream Processing)框架应运而生。它们的核心思想是:数据到达即处理,延迟可降至秒级甚至毫秒级。

然而早期流处理框架面临两个根本问题:

于是,一个折中方案诞生了——Lambda 架构。

1.3 演进时间线

下面用一个简化的时间线梳理关键节点:

年份 里程碑 核心特征
2004 Google MapReduce 论文 大规模批处理
2011 Apache Storm 开源 实时流处理
2012 Nathan Marz 提出 Lambda 架构 批处理 + 流处理双轨制
2014 Jay Kreps 提出 Kappa 架构 以流为本的统一模型
2016 Apache Flink 1.0 发布 事件驱动(Event-Driven)、精确一次语义
2018 Spark Structured Streaming GA 微批 + 连续处理模式
2019 Delta Lake 开源、Apache Iceberg 进入孵化 数据湖上的 ACID
2020 Databricks 提出 Lakehouse 概念 湖仓一体
2023 Apache Paimon 进入孵化 面向流的 Table Format

二、Lambda 架构:双轨制的设计与困境

2.1 架构概览

Lambda 架构由 Nathan Marz 在其著作《Big Data: Principles and Best Practices of Scalable Realtime Data Systems》中系统阐述。其核心思想是将数据处理分为三层:

  1. 批处理层(Batch Layer):以 HDFS 存储不可变的全量数据(Master Dataset),定期运行 MapReduce / Spark 任务生成批视图(Batch View);
  2. 速度层(Speed Layer):以 Storm / Kafka Streams 处理最新到达的增量数据,生成实时视图(Real-time View);
  3. 服务层(Serving Layer):将批视图与实时视图合并,对外提供统一查询接口。
graph TD
    A[数据源] -->|全量写入| B[批处理层<br/>Hadoop / Spark]
    A -->|实时流入| C[速度层<br/>Storm / Flink]
    B -->|定期生成| D[批视图<br/>Batch View]
    C -->|持续更新| E[实时视图<br/>Real-time View]
    D --> F[服务层<br/>Serving Layer]
    E --> F
    F --> G[查询接口]

2.2 Lambda 架构的优点

2.3 Lambda 架构的困境

在大规模工程实践中,Lambda 架构暴露出三个致命问题:

问题一:双倍开发与维护成本。 同一套业务逻辑需要用两种完全不同的编程模型实现——批处理通常用 SQL 或 Spark,流处理用 Java / Scala 的流式 API。两套代码的同步维护是噩梦。

// 批处理:Spark SQL 统计每小时订单量
spark.sql("""
    SELECT hour(order_time) AS hr, COUNT(*) AS cnt
    FROM orders
    WHERE dt = '2026-04-12'
    GROUP BY hour(order_time)
""");

// 流处理:Storm Bolt 实现相同逻辑
public class OrderCountBolt extends BaseRichBolt {
    private Map<Integer, Long> hourlyCount = new HashMap<>();

    @Override
    public void execute(Tuple tuple) {
        int hour = tuple.getIntegerByField("hour");
        hourlyCount.merge(hour, 1L, Long::sum);
        // 需要手动处理窗口、状态、容错...
    }
}

两段代码实现的是完全相同的业务逻辑,但编程模型、测试方式、部署运维流程完全不同。

问题二:数据一致性难以保证。 批视图与实时视图在合并时存在时间窗口的重叠或空白,可能导致数据重复计算或漏算。服务层的 merge 逻辑往往需要大量 ad-hoc 的修补代码。

问题三:运维复杂度高。 需要同时运维 HDFS 集群、批处理调度器(Oozie / Airflow)、流处理集群、服务层数据库,任何一个环节故障都可能导致数据链路断裂。

正是这些痛点,催生了 Kappa 架构。

三、Kappa 架构:以流为本的统一模型

3.1 核心思想

2014 年,LinkedIn 的 Jay Kreps(Apache Kafka 的作者)在一篇博客文章中提出了 Kappa 架构。其核心思想极其简洁:

既然所有数据都可以看作事件流(Event Stream),为什么不用一套流处理引擎同时处理实时数据和历史数据?

Kappa 架构去掉了 Lambda 的批处理层,只保留一条流处理管线:

  1. 所有数据写入 Kafka 这样的持久化消息队列(Durable Message Queue);
  2. 流处理引擎(Flink / Kafka Streams)从 Kafka 消费数据,实时计算并输出结果;
  3. 如果需要重算历史数据,只需将 Kafka 的消费偏移量(Offset)回拨到过去某个时间点,重新消费即可。
graph LR
    A[数据源] --> B[Kafka<br/>持久化消息队列]
    B --> C[流处理引擎<br/>Flink / Kafka Streams]
    C --> D[结果存储<br/>实时视图]
    D --> E[查询接口]
    B -->|回拨 Offset| C

3.2 Kappa 架构的前提条件

Kappa 架构并非万能。它能成立依赖于两个技术前提:

  1. 消息队列能保存足够长的历史数据:Kafka 默认只保留 7 天数据,但可以通过配置延长保留期,或使用 Kafka 的分层存储(Tiered Storage)将冷数据迁移到对象存储(Object Storage);
  2. 流处理引擎能提供精确一次语义:这正是 Flink 等现代流处理引擎的核心突破。

3.3 从 Kappa 到批流一体

Kappa 架构虽然简化了架构,但在实践中仍存在局限:

因此,现代数据架构的最终方向是批流一体——不是”只用流”或”只用批”,而是用同一套引擎、同一套代码、同一套语义来处理批数据和流数据。Flink 和 Spark 在这个方向上走出了两条截然不同的技术路线。

这是数据密集型架构中最核心的技术选型问题之一。两者都宣称支持批流一体,但底层架构理念截然不同。

4.1 架构理念的本质差异

Flink:流优先(Stream-First)。 Flink 将一切数据视为无界流(Unbounded Stream),批处理只是流处理的特例——有界流(Bounded Stream)。其核心执行引擎是基于事件驱动的流式引擎,天然支持低延迟处理。

Spark Structured Streaming:批优先(Batch-First)。 Spark 的核心引擎是批处理引擎,Structured Streaming 在其上构建了一个微批处理(Micro-Batch)模型——将流数据切分为一个个小的批次(通常 100ms ~ 数秒),然后用 Spark 的批处理引擎逐批执行。

4.2 精确一次语义的实现

精确一次(Exactly-Once)语义是数据一致性的基石。两者的实现路径不同:

Flink 的检查点机制(Checkpointing):

Flink 使用 Chandy-Lamport 分布式快照算法的变体。其工作流程如下:

  1. JobManager 定期向数据流注入屏障(Barrier);
  2. 当算子(Operator)收到屏障时,将当前状态异步写入外部存储(如 HDFS、S3);
  3. 一旦所有算子完成快照,该检查点被标记为完成;
  4. 故障恢复时,从最近一个完成的检查点恢复状态,并从该位置重新消费上游数据。
// Flink 检查点配置
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(60000); // 每 60 秒做一次检查点
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(30000);
env.getCheckpointConfig().setCheckpointTimeout(120000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);

// 配合 Kafka Source 的精确一次消费
KafkaSource<String> source = KafkaSource.<String>builder()
    .setBootstrapServers("kafka:9092")
    .setTopics("orders")
    .setGroupId("order-processor")
    .setStartingOffsets(OffsetsInitializer.committedOffsets())
    .setValueOnlyDeserializer(new SimpleStringSchema())
    .build();

DataStream<String> stream = env.fromSource(
    source,
    WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5)),
    "Kafka Source"
);

Spark Structured Streaming 的 WAL 与幂等写入:

Spark 通过预写日志(Write-Ahead Log, WAL)记录每个微批次消费的 Offset 范围,结合幂等写入(Idempotent Write)或两阶段提交(Two-Phase Commit)实现端到端精确一次。

# Spark Structured Streaming 精确一次配置
df = spark \
    .readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "kafka:9092") \
    .option("subscribe", "orders") \
    .option("startingOffsets", "earliest") \
    .load()

query = df \
    .selectExpr("CAST(value AS STRING)") \
    .writeStream \
    .outputMode("append") \
    .format("delta") \
    .option("checkpointLocation", "/checkpoint/orders") \
    .trigger(processingTime="10 seconds") \
    .start("/data/orders")

4.3 状态管理

状态管理(State Management)是流处理中最具挑战性的问题。窗口聚合、去重、模式匹配等操作都需要维护跨事件的状态。

Flink 的状态后端(State Backend):

Flink 提供三种状态后端:

状态后端 存储位置 适用场景 状态大小上限
HashMapStateBackend JVM 堆内存 状态较小、低延迟 受 JVM 堆大小限制
EmbeddedRocksDBStateBackend 本地磁盘(RocksDB) 状态极大 受磁盘大小限制
ForStStateBackend 本地磁盘(ForSt) 状态极大、云原生优化 受磁盘大小限制

RocksDB 状态后端是生产环境的主流选择。它将状态存储在本地 SSD 上的 RocksDB 实例中,支持增量检查点(Incremental Checkpoint),只上传自上次检查点以来发生变化的 SST 文件,大幅降低检查点开销。

// 配置 RocksDB 状态后端与增量检查点
env.setStateBackend(new EmbeddedRocksDBStateBackend(true)); // true 表示启用增量检查点
env.getCheckpointConfig().setCheckpointStorage("s3://my-bucket/flink-checkpoints");

Spark 的状态管理:

Spark Structured Streaming 的状态管理相对简单,状态存储在 HDFS/S3 上的检查点目录中。每个微批次结束时,状态快照写入检查点。这种方式的优点是简单可靠,但缺点是状态更新的粒度较粗——如果某个微批次处理时间过长,状态写入会成为瓶颈。

4.4 全面对比

维度 Apache Flink Spark Structured Streaming
架构理念 流优先,批是有界流 批优先,流是连续微批
延迟 毫秒级(真正的事件驱动) 秒级至分钟级(微批间隔)
精确一次 分布式快照(Chandy-Lamport) WAL + 幂等写入
状态管理 RocksDB / 堆内存,支持增量检查点 HDFS 检查点,全量快照
事件时间处理 原生 Watermark 机制,灵活度高 支持 Watermark,但配置灵活度较低
SQL 支持 Flink SQL,持续查询(Continuous Query) Spark SQL,微批查询
生态成熟度 CDC(Change Data Capture)生态完善 与 MLlib、GraphX 等批处理生态集成好
运维复杂度 较高(TaskManager、JobManager 管理) 中等(复用 Spark 集群)
社区活跃度 阿里巴巴主导,中国社区活跃 Databricks 主导,全球社区广泛
典型用户 阿里巴巴、字节跳动、Uber Netflix、Apple、Databricks 客户

4.5 选型建议

五、Lakehouse:数据湖与数据仓库的融合

5.1 数据湖与数据仓库的矛盾

在 Lakehouse 概念出现之前,企业的数据存储通常分为两个系统:

数据湖(Data Lake): 以 HDFS 或对象存储(S3、OSS)为基础,存储原始的、未经结构化处理的数据(Parquet、ORC、JSON、日志等)。优点是成本低、灵活,缺点是缺乏事务支持、数据质量差、查询性能低。

数据仓库(Data Warehouse): 以 Snowflake、Redshift、BigQuery 等为代表,提供高性能的 SQL 查询、ACID 事务、Schema 管理。优点是查询快、数据质量高,缺点是存储成本高、数据需要从数据湖 ETL 过来(引入额外延迟和复杂度)。

这种”两套系统”的架构带来了与 Lambda 架构类似的问题——数据冗余、一致性难保证、运维复杂。

5.2 Lakehouse 的定义

2020 年,Databricks 在论文《Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics》中正式提出 Lakehouse 概念。其核心思想是:

在数据湖的低成本存储之上,叠加数据仓库的管理能力(ACID 事务、Schema 管理、索引优化),实现一套系统同时满足 BI 分析(Business Intelligence)和机器学习(Machine Learning)的需求。

graph TB
    subgraph Lakehouse 架构
        A[对象存储<br/>S3 / HDFS / OSS] --> B[Table Format<br/>Delta Lake / Iceberg / Hudi]
        B --> C[计算引擎<br/>Spark / Flink / Trino / Presto]
        C --> D1[BI 分析]
        C --> D2[机器学习]
        C --> D3[实时数仓]
    end

    subgraph 传统双系统架构
        E[数据湖<br/>S3 / HDFS] -->|ETL| F[数据仓库<br/>Redshift / Snowflake]
        E --> G[ML 引擎<br/>TensorFlow / PyTorch]
        F --> H[BI 工具]
    end

5.3 Lakehouse 的关键技术层

Lakehouse 之所以能在数据湖上实现数据仓库级别的管理能力,核心在于 Table Format 层。Table Format 在原始数据文件(Parquet/ORC)之上引入了一层元数据管理,提供以下关键能力:

  1. ACID 事务:支持并发读写,写操作原子性提交;
  2. 时间旅行(Time Travel):可以查询任意历史版本的数据;
  3. Schema 演化(Schema Evolution):支持在不重写数据的前提下添加、删除、重命名列;
  4. 分区演化(Partition Evolution):支持在不重写数据的前提下修改分区策略;
  5. 数据跳过(Data Skipping):通过列统计信息(min/max/count)跳过不相关的数据文件,加速查询。

六、Table Format 之争:Delta Lake vs Iceberg vs Hudi

Table Format 是 Lakehouse 架构的核心支撑层。当前行业存在三个主要的开源 Table Format,各有侧重。

6.1 Delta Lake

Delta Lake 由 Databricks 开源,与 Spark 生态深度绑定。

核心机制: Delta Lake 使用事务日志(Transaction Log)——_delta_log 目录下的 JSON 文件——记录每次对表的操作(添加文件、删除文件、修改元数据等)。每次写操作生成一个新的 JSON 文件(如 000001.json),每 10 个 JSON 文件自动合并为一个 Checkpoint 文件(Parquet 格式)以加速元数据读取。

-- Delta Lake 时间旅行:查询 1 小时前的数据
SELECT * FROM orders VERSION AS OF 42;

-- 或者按时间戳查询
SELECT * FROM orders TIMESTAMP AS OF '2026-04-12 14:00:00';

-- Delta Lake MERGE(Upsert)操作
MERGE INTO target_table AS t
USING source_table AS s
ON t.order_id = s.order_id
WHEN MATCHED THEN
    UPDATE SET t.status = s.status, t.update_time = s.update_time
WHEN NOT MATCHED THEN
    INSERT (order_id, user_id, amount, status, update_time)
    VALUES (s.order_id, s.user_id, s.amount, s.status, s.update_time);

6.2 Apache Iceberg

Apache Iceberg 由 Netflix 开源,设计目标是引擎无关(Engine-Agnostic)。

核心机制: Iceberg 使用分层的元数据结构:

  1. Catalog:指向当前元数据文件的指针(如 Hive Metastore、Nessie、REST Catalog);
  2. Metadata File(JSON):记录表的 Schema、分区规范、快照列表;
  3. Manifest List(Avro):每个快照对应一个 Manifest List,列出该快照包含的所有 Manifest 文件;
  4. Manifest File(Avro):列出数据文件及其列统计信息(min/max/null count);
  5. Data File(Parquet/ORC/Avro):实际的数据文件。

这种分层设计的关键优势是分区演化。传统 Hive 分区方案一旦确定便无法更改(如从按天分区改为按小时分区需要重写所有数据),而 Iceberg 的分区信息存储在 Manifest 级别,可以在不重写数据的前提下修改分区策略。

-- Iceberg 分区演化:从按天分区改为按小时分区
ALTER TABLE orders WRITE ORDERED BY (order_time);
ALTER TABLE orders SET PARTITION SPEC (
    hours(order_time)
);
-- 后续写入的数据使用新的分区策略,旧数据保持不变

-- Iceberg 快照查询
SELECT * FROM orders FOR SYSTEM_TIME AS OF TIMESTAMP '2026-04-12 14:00:00';

-- Iceberg 增量读取(用于构建流式管线)
SELECT * FROM orders
WHERE snapshot_id BETWEEN 1001 AND 1005;

6.3 Apache Hudi

Apache Hudi(Hadoop Upserts Deletes and Incrementals)由 Uber 开源,最初的设计目标是解决数据湖上的 Upsert 问题。Hudi 提供两种存储类型:

6.4 三者对比

维度 Delta Lake Apache Iceberg Apache Hudi
开源方 Databricks Netflix / Apple Uber
引擎绑定 与 Spark 深度绑定 引擎无关 主要支持 Spark
元数据管理 事务日志(JSON + Parquet Checkpoint) 分层元数据(Catalog → Manifest) 时间线(Timeline)机制
分区演化 不支持(需重写数据) 原生支持 部分支持
Schema 演化 支持(添加列、重命名列) 完整支持(基于 ID 的 Schema) 支持(添加列)
时间旅行 支持 支持 支持
流式读写 支持(Spark Structured Streaming) 支持(Flink Iceberg Connector) 原生支持(核心设计目标)
并发控制 乐观并发(Optimistic Concurrency) 乐观并发 + 序列化隔离 基于时间线的 MVCC
社区与生态 Databricks 主导,商业化成熟 Apache 基金会,厂商中立 Uber 主导,Onehouse 商业化

6.5 选型建议

七、实时数仓的落地架构

7.1 实时数仓的分层模型

传统离线数仓遵循经典的分层模型:ODS → DWD → DWS → ADS。实时数仓的分层模型与之对应,但每一层都需要实时更新:

层次 离线数仓 实时数仓 技术选型
ODS(Operational Data Store) HDFS 原始文件 Kafka Topic Kafka / Pulsar
DWD(Data Warehouse Detail) Hive 明细表 Flink 清洗后写入 Kafka/Iceberg Flink SQL
DWS(Data Warehouse Summary) Hive 汇总表 Flink 聚合后写入 OLAP 引擎 Flink SQL + StarRocks
ADS(Application Data Store) MySQL 结果表 OLAP 引擎直接服务查询 StarRocks / ClickHouse

7.2 典型落地架构

下面给出一个生产级实时数仓架构的完整技术栈:

数据源(MySQL / MongoDB / 日志)
    │
    ▼ CDC(Debezium / Flink CDC)
    │
Kafka(ODS 层:原始事件流)
    │
    ▼ Flink SQL(数据清洗、维表关联)
    │
Iceberg / Kafka(DWD 层:明细事实表)
    │
    ▼ Flink SQL(窗口聚合、指标计算)
    │
StarRocks / ClickHouse(DWS + ADS 层:汇总与服务)
    │
    ▼
BI 工具 / 数据 API / 实时大屏

Flink CDC(Change Data Capture)是打通实时数仓全链路的关键组件。它可以直接读取 MySQL 的 Binlog、PostgreSQL 的 WAL、MongoDB 的 Oplog,将数据变更事件转化为 Flink 的数据流。

-- Flink SQL:创建 MySQL CDC Source 表
CREATE TABLE orders_source (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    amount DECIMAL(10, 2),
    status STRING,
    order_time TIMESTAMP(3),
    update_time TIMESTAMP(3),
    PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = 'mysql-host',
    'port' = '3306',
    'username' = 'flink_reader',
    'password' = '${secret}',
    'database-name' = 'ecommerce',
    'table-name' = 'orders',
    'scan.startup.mode' = 'initial'
);

-- Flink SQL:创建 Iceberg Sink 表(DWD 层)
CREATE TABLE dwd_orders (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    amount DECIMAL(10, 2),
    status STRING,
    order_time TIMESTAMP(3),
    update_time TIMESTAMP(3),
    user_name STRING,
    user_level STRING,
    PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
    'connector' = 'iceberg',
    'catalog-name' = 'iceberg_catalog',
    'catalog-type' = 'hive',
    'warehouse' = 's3://data-lake/iceberg',
    'format-version' = '2',
    'write.upsert.enabled' = 'true'
);

-- Flink SQL:维表关联,将订单与用户信息打宽
INSERT INTO dwd_orders
SELECT
    o.order_id,
    o.user_id,
    o.product_id,
    o.amount,
    o.status,
    o.order_time,
    o.update_time,
    u.user_name,
    u.user_level
FROM orders_source AS o
LEFT JOIN users_dim FOR SYSTEM_TIME AS OF o.proctime AS u
ON o.user_id = u.user_id;

7.4 物化视图与增量计算

实时数仓的核心技术挑战之一是增量计算(Incremental Computation)。传统批处理每次全量重算所有数据,而实时数仓需要在新数据到达时只计算增量部分。

物化视图(Materialized View) 是增量计算的典型载体。以 StarRocks 为例:

-- StarRocks:创建异步物化视图实现增量聚合
CREATE MATERIALIZED VIEW mv_hourly_revenue
REFRESH ASYNC EVERY (INTERVAL 1 MINUTE)
AS
SELECT
    DATE_TRUNC('hour', order_time) AS hour_key,
    product_category,
    COUNT(*) AS order_count,
    SUM(amount) AS total_revenue,
    AVG(amount) AS avg_order_amount
FROM dwd_orders
WHERE status = 'completed'
GROUP BY
    DATE_TRUNC('hour', order_time),
    product_category;

-- 查询时自动路由到物化视图
SELECT * FROM mv_hourly_revenue
WHERE hour_key >= '2026-04-12 10:00:00'
ORDER BY total_revenue DESC;

Flink 的持续查询(Continuous Query)也是增量计算的一种实现。Flink SQL 中的 GROUP BY 聚合本质上维护了一个持续更新的物化视图:

-- Flink SQL:持续计算每分钟每品类的订单量和 GMV
CREATE TABLE dws_realtime_metrics (
    window_start TIMESTAMP(3),
    product_category STRING,
    order_count BIGINT,
    gmv DECIMAL(18, 2),
    PRIMARY KEY (window_start, product_category) NOT ENFORCED
) WITH (
    'connector' = 'starrocks',
    'jdbc-url' = 'jdbc:mysql://starrocks:9030',
    'load-url' = 'starrocks:8030',
    'database-name' = 'realtime_dw',
    'table-name' = 'dws_realtime_metrics',
    'sink.properties.format' = 'json',
    'sink.buffer-flush.interval-ms' = '5000'
);

INSERT INTO dws_realtime_metrics
SELECT
    TUMBLE_START(order_time, INTERVAL '1' MINUTE) AS window_start,
    product_category,
    COUNT(*) AS order_count,
    SUM(amount) AS gmv
FROM dwd_orders
GROUP BY
    TUMBLE(order_time, INTERVAL '1' MINUTE),
    product_category;

7.5 工程案例:某电商平台实时数仓建设

背景: 某头部电商平台日均订单量 3 亿笔,峰值 QPS(Queries Per Second)达 50 万。原有架构为经典 Lambda 架构:离线链路使用 Spark + Hive(T+1),实时链路使用 Storm + HBase。两条链路的数据不一致率约 3%~5%,且实时链路在大促期间经常出现反压(Backpressure)导致数据延迟飙升至分钟级。

改造目标:

  1. 统一批流处理链路,消除数据不一致;
  2. 端到端延迟从分钟级降至秒级;
  3. 存储成本降低 40%。

技术方案:

组件 改造前 改造后
消息队列 Kafka 0.10 Kafka 3.x
流处理引擎 Storm(实时) + Spark(离线) Flink 1.18(统一)
数据湖存储 HDFS + Hive S3 + Apache Iceberg
OLAP 引擎 Druid StarRocks
CDC 工具 自研 Binlog 解析 Flink CDC 3.0
调度系统 Oozie 取消(Flink 常驻任务)

改造效果:

指标 改造前 改造后 改善幅度
端到端延迟 1~5 分钟 3~8 秒 降低 97%
批流数据一致率 95%~97% 99.99% 显著提升
存储成本(月均) 280 万元 168 万元 降低 40%
ETL 代码行数 12 万行(两套) 4.5 万行(一套) 减少 62%
故障恢复时间(RTO) 30~60 分钟 2~5 分钟 降低 90%

关键技术决策:

  1. 选择 Iceberg 而非 Delta Lake,因为需要同时支持 Flink 写入和 Trino 查询,Iceberg 的引擎无关特性是关键;
  2. Flink CDC 替代自研 Binlog 解析器,减少约 2 万行自研代码的维护成本;
  3. StarRocks 替代 Druid,因为 StarRocks 的 SQL 兼容性更好,且支持物化视图自动刷新。

7.6 实时数仓的挑战与应对

实时数仓虽然在延迟和一致性上有巨大优势,但也引入了新的挑战:

挑战一:数据回溯(Data Backfill)。 当业务逻辑变更需要重算历史数据时,纯流式架构需要从 Kafka 回拨 Offset 重消费,但 Kafka 通常只保留有限天数的数据。解决方案是使用 Iceberg 的快照读取能力从数据湖回溯:

-- 使用 Flink 读取 Iceberg 历史快照进行回溯计算
CREATE TABLE orders_backfill (
    order_id BIGINT,
    user_id BIGINT,
    amount DECIMAL(10, 2),
    order_time TIMESTAMP(3)
) WITH (
    'connector' = 'iceberg',
    'catalog-name' = 'iceberg_catalog',
    'catalog-type' = 'hive',
    'warehouse' = 's3://data-lake/iceberg',
    'streaming' = 'false',
    'snapshot-id' = '3821550127947089987'
);

挑战二:小文件问题(Small File Problem)。 流式写入会产生大量小文件,严重影响读取性能和存储效率。Iceberg 提供了自动合并(Auto-Compaction)功能:

-- Iceberg 小文件合并
CALL iceberg_catalog.system.rewrite_data_files(
    table => 'db.dwd_orders',
    strategy => 'sort',
    sort_order => 'order_time ASC',
    options => map(
        'target-file-size-bytes', '268435456',
        'min-file-size-bytes', '67108864',
        'max-file-size-bytes', '536870912'
    )
);

挑战三:精确一次的端到端保障。 从 Source 到 Sink 的精确一次语义需要整条链路的配合。以 Flink + Kafka + Iceberg 链路为例,需要同时满足:

  1. Kafka Source 使用 Committed Offset 追踪消费进度;
  2. Flink 启用 Exactly-Once 检查点模式;
  3. Iceberg Sink 使用两阶段提交协议(Two-Phase Commit),在检查点完成时才将数据文件提交为可见。

7.7 批流一体的数据质量保障

数据质量(Data Quality)在实时场景下更难保障,因为数据一旦流过便不可回头。常见的实时数据质量检查手段包括:

-- Flink SQL:实时数据质量监控
-- 检测订单金额异常(金额为负或超过阈值)
SELECT
    TUMBLE_START(order_time, INTERVAL '5' MINUTE) AS check_window,
    COUNT(*) AS total_orders,
    SUM(CASE WHEN amount <= 0 THEN 1 ELSE 0 END) AS negative_amount_count,
    SUM(CASE WHEN amount > 100000 THEN 1 ELSE 0 END) AS high_amount_count,
    SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) AS null_user_count
FROM orders_source
GROUP BY TUMBLE(order_time, INTERVAL '5' MINUTE)
HAVING
    SUM(CASE WHEN amount <= 0 THEN 1 ELSE 0 END) > 10
    OR SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) > 100;
-- 异常数据写入告警队列

八、总结

数据密集型架构的演进历程可以用一句话概括:从分裂走向统一

演进阶段 核心思想 解决的问题 引入的新问题
纯批处理 全量定期计算 大规模数据处理 延迟高(T+1)
Lambda 架构 批流双轨制 兼顾延迟和准确性 双倍开发成本、数据不一致
Kappa 架构 以流为本 统一处理链路 历史回溯困难、复杂查询低效
批流一体 同一引擎、同一代码 消除批流割裂 引擎选型复杂
Lakehouse 湖仓融合 消除数据湖与仓库割裂 Table Format 选型、生态碎片化

选择哪条路线,取决于业务的具体约束:

  1. 延迟要求:亚秒级选 Flink,秒级到分钟级 Spark 足矣;
  2. 数据规模:PB 级数据选 Lakehouse + Iceberg,TB 级以下用传统数仓可能更简单;
  3. 团队能力:Flink 的运维门槛高于 Spark,需评估团队的技术储备;
  4. 生态兼容:多引擎场景选 Iceberg,纯 Spark 场景选 Delta Lake;
  5. 成本预算:Lakehouse 的存储成本远低于传统数仓,但计算成本需根据查询模式评估。

没有万能的架构,只有在具体约束下的最优解。理解每一步演进背后的技术动因和工程取舍,比记住某个框架的 API 更为重要。


上一篇:【系统架构设计百科】WebAssembly 架构

下一篇:【系统架构设计百科】多租户架构

参考资料

  1. Marz N, Warren J. Big Data: Principles and Best Practices of Scalable Realtime Data Systems. Manning Publications, 2015.
  2. Kreps J. Questioning the Lambda Architecture. O’Reilly Radar, 2014.
  3. Armbrust M, et al. Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics. CIDR, 2021.
  4. Carbone P, et al. Apache Flink: Stream and Batch Processing in a Single Engine. IEEE Data Engineering Bulletin, 2015.
  5. Zaharia M, et al. Apache Spark: A Unified Engine for Big Data Processing. Communications of the ACM, 2016.
  6. Armbrust M, et al. Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores. PVLDB, 2020.
  7. Apache Iceberg Documentation. https://iceberg.apache.org/docs/latest/
  8. Apache Flink Documentation. https://nightlies.apache.org/flink/flink-docs-stable/
  9. Databricks. The Rise of the Lakehouse. https://www.databricks.com/research/lakehouse
  10. StarRocks Documentation. https://docs.starrocks.io/

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-13 · architecture

【系统架构设计百科】架构质量属性:不只是"高可用高性能"

需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。

2026-04-13 · architecture

【系统架构设计百科】告警策略:如何避免"狼来了"

大多数团队的告警系统都在制造噪声而不是传递信号。阈值告警看似直观,实则产生大量误报和漏报,值班工程师在凌晨三点被叫醒,却发现只是一次无害的毛刺。本文从告警疲劳的工业数据出发,拆解基于 SLO 的多窗口燃烧率告警算法,深入 Alertmanager 的路由、抑制与分组机制,结合 PagerDuty 的告警疲劳研究和真实工程案例,给出一套可落地的告警策略设计方法。


By .