一、前言:数据架构的演进之路
timeline
title 数据架构演进史
2000s : 数据仓库时代 (Teradata/Oracle)
2006 : Hadoop 诞生 (批处理为王)
2011 : Lambda 架构提出 (Nathan Marz)
2014 : Kappa 架构提出 (Jay Kreps)
2020s : 湖仓一体时代 (实时+离线融合)
你可能遇到过这些场景:
graph TD
A[老板的需求] --> B["报表要T+1 ✅"]
A --> C["报表要实时 😰"]
A --> D["既要T+1又要实时 🤯"]
D --> E["你:我太难了"]
本文将回答的核心问题:
如何设计一个既能支持离线分析,又能支持实时查询的数据架构?
二、传统批处理架构的困境
2.1 经典批处理架构
graph LR
subgraph "传统T+1架构"
A[业务数据库] -->|每天凌晨同步| B[数据仓库]
B --> C[报表/BI]
end
subgraph "数据延迟"
D["今天产生的数据
明天才能看到"] end
明天才能看到"] end
2.2 批处理的痛点
graph TB
A[批处理痛点] --> B[高延迟]
A --> C[峰值压力]
A --> D[无法应对实时需求]
B --> B1["T+1 甚至 T+N
业务等不及"] C --> C1["凌晨集中跑批
资源压力大"] D --> D1["实时大屏?
做不到"]
业务等不及"] C --> C1["凌晨集中跑批
资源压力大"] D --> D1["实时大屏?
做不到"]
三、Lambda 架构:双轨并行
3.1 Lambda 架构全景
graph TB
subgraph "数据源"
SOURCE[业务数据]
end
subgraph "Lambda架构"
SOURCE --> BATCH[批处理层
Batch Layer] SOURCE --> SPEED[速度层
Speed Layer] BATCH --> BATCH_VIEW[(批处理视图
Batch View)] SPEED --> RT_VIEW[(实时视图
Real-time View)] BATCH_VIEW --> SERVING[服务层
Serving Layer] RT_VIEW --> SERVING end subgraph "查询" SERVING --> QUERY[查询结果
批处理 + 实时 合并] end style BATCH fill:#4ecdc4 style SPEED fill:#ff6b6b style SERVING fill:#ffeaa7
Batch Layer] SOURCE --> SPEED[速度层
Speed Layer] BATCH --> BATCH_VIEW[(批处理视图
Batch View)] SPEED --> RT_VIEW[(实时视图
Real-time View)] BATCH_VIEW --> SERVING[服务层
Serving Layer] RT_VIEW --> SERVING end subgraph "查询" SERVING --> QUERY[查询结果
批处理 + 实时 合并] end style BATCH fill:#4ecdc4 style SPEED fill:#ff6b6b style SERVING fill:#ffeaa7
3.2 三层架构详解
graph TB
subgraph "批处理层 Batch Layer"
B1["存储:HDFS/S3"]
B2["计算:Spark/Hive"]
B3["特点:全量计算、高延迟、高准确"]
B4["输出:T+1 的完整视图"]
end
subgraph "速度层 Speed Layer"
S1["存储:Kafka/Redis"]
S2["计算:Flink/Spark Streaming"]
S3["特点:增量计算、低延迟、可能不准"]
S4["输出:实时增量视图"]
end
subgraph "服务层 Serving Layer"
V1["批处理视图 + 实时视图"]
V2["合并后对外提供服务"]
V3["存储:HBase/Druid/ClickHouse"]
end
3.3 Lambda 架构实现示例
graph LR
subgraph "数据流"
MYSQL[MySQL] --> KAFKA[Kafka]
KAFKA --> FLINK[Flink
实时计算] KAFKA --> HDFS[HDFS] HDFS --> SPARK[Spark
批处理] end subgraph "存储层" FLINK --> REDIS[(Redis
实时结果)] SPARK --> HIVE[(Hive
批处理结果)] end subgraph "服务层" REDIS --> API[API服务] HIVE --> API API --> APP[应用] end
实时计算] KAFKA --> HDFS[HDFS] HDFS --> SPARK[Spark
批处理] end subgraph "存储层" FLINK --> REDIS[(Redis
实时结果)] SPARK --> HIVE[(Hive
批处理结果)] end subgraph "服务层" REDIS --> API[API服务] HIVE --> API API --> APP[应用] end
代码示例:查询合并逻辑
def get_user_stats(user_id, date):
"""
Lambda架构查询示例
合并批处理结果和实时结果
"""
# 1. 从批处理视图获取截止昨天的统计
batch_result = hive_client.query(f"""
SELECT total_orders, total_amount
FROM user_stats_batch
WHERE user_id = '{user_id}' AND date = '{yesterday}'
""")
# 2. 从实时视图获取今天的增量
realtime_result = redis_client.hgetall(f"user_stats_rt:{user_id}:{today}")
# 3. 合并结果
total_orders = batch_result['total_orders'] + int(realtime_result.get('orders', 0))
total_amount = batch_result['total_amount'] + float(realtime_result.get('amount', 0))
return {
'user_id': user_id,
'total_orders': total_orders,
'total_amount': total_amount,
'data_freshness': 'realtime'
}3.4 Lambda 架构的问题
graph TD
A[Lambda架构的问题] --> B[代码重复]
A --> C[维护成本高]
A --> D[数据一致性]
A --> E[系统复杂]
B --> B1["同一逻辑
批处理写一遍
实时写一遍"] C --> C1["两套系统
两倍维护"] D --> D1["批处理和实时
结果可能不一致"] E --> E1["组件多
出问题难排查"]
批处理写一遍
实时写一遍"] C --> C1["两套系统
两倍维护"] D --> D1["批处理和实时
结果可能不一致"] E --> E1["组件多
出问题难排查"]
痛点案例:
// ========== 批处理代码 (Spark) ==========
val dailyStats = orders
.filter(col("date") === "2024-01-15")
.groupBy("user_id")
.agg(
count("order_id").as("order_count"),
sum("amount").as("total_amount")
)
// ========== 实时代码 (Flink) ==========
DataStream<UserStats> realtimeStats = orders
.keyBy(Order::getUserId)
.window(TumblingEventTimeWindows.of(Time.days(1)))
.aggregate(new OrderAggregator());
// 两套代码逻辑相同,但框架不同,语法不同
// 改一个逻辑,两边都要改,还要保证结果一致
// 🤯 维护噩梦四、Kappa 架构:一切皆流
4.1 Kappa 架构全景
graph TB
subgraph "数据源"
SOURCE[业务数据]
end
subgraph "Kappa架构"
SOURCE --> KAFKA[消息队列
Kafka] KAFKA --> STREAM[流处理引擎
Flink] STREAM --> SERVING[(服务层
OLAP/KV存储)] end subgraph "查询" SERVING --> QUERY[查询结果] end style KAFKA fill:#4ecdc4 style STREAM fill:#ff6b6b style SERVING fill:#ffeaa7
Kafka] KAFKA --> STREAM[流处理引擎
Flink] STREAM --> SERVING[(服务层
OLAP/KV存储)] end subgraph "查询" SERVING --> QUERY[查询结果] end style KAFKA fill:#4ecdc4 style STREAM fill:#ff6b6b style SERVING fill:#ffeaa7
4.2 Kappa 核心理念
graph LR
subgraph "Lambda思维"
A1["批处理 = 处理有界数据集"]
A2["流处理 = 处理无界数据集"]
A3["两者是不同的东西"]
end
graph LR
subgraph "Kappa思维"
B1["批处理 = 流处理的特例"]
B2["有界数据 = 有终点的流"]
B3["统一用流处理搞定一切"]
end
Jay Kreps 的洞见:
"为什么要维护两套系统?如果流处理足够强大,我们只需要一套系统。"
4.3 Kappa 架构实现
graph LR
subgraph "数据采集"
MYSQL[MySQL] -->|CDC| KAFKA[Kafka]
LOG[日志] --> KAFKA
API[API数据] --> KAFKA
end
subgraph "流处理"
KAFKA --> FLINK[Flink]
FLINK -->|实时写入| OLAP[(ClickHouse/Doris)]
FLINK -->|实时写入| KV[(Redis/HBase)]
end
subgraph "查询服务"
OLAP --> SERVICE[API服务]
KV --> SERVICE
end
4.4 重处理机制
sequenceDiagram
participant Kafka
participant Flink_V1 as Flink Job V1
participant Flink_V2 as Flink Job V2
participant Store as 存储
Note over Kafka: 保留历史数据 (如7天)
Flink_V1->>Kafka: 正常消费
Flink_V1->>Store: 写入结果
Note over Flink_V2: 发现Bug,需要重算
Flink_V2->>Kafka: 从头消费历史数据
Flink_V2->>Store: 写入新表/覆盖旧数据
Note over Flink_V1: 下线旧Job
Note over Flink_V2: 新Job接管
Kappa 重处理的关键:
# Kafka 配置:保留足够长的历史数据
log.retention.hours=168 # 保留7天
log.retention.bytes=-1 # 不限制大小
# 或者更长
log.retention.hours=720 # 保留30天4.5 Kappa 的局限性
graph TD
A[Kappa架构的局限] --> B[历史数据处理]
A --> C[复杂分析场景]
A --> D[资源消耗]
A --> E[Kafka存储成本]
B --> B1["重算几年的数据?
Kafka存不了那么久"] C --> C1["复杂SQL/机器学习
流处理不擅长"] D --> D1["实时计算
资源消耗大"] E --> E1["Kafka存储成本
高于HDFS/S3"]
Kafka存不了那么久"] C --> C1["复杂SQL/机器学习
流处理不擅长"] D --> D1["实时计算
资源消耗大"] E --> E1["Kafka存储成本
高于HDFS/S3"]
五、Lambda vs Kappa:深度对比
5.1 架构对比图
graph TB
subgraph "Lambda架构"
L1[数据源] --> L2[批处理层]
L1 --> L3[速度层]
L2 --> L4[服务层]
L3 --> L4
end
graph TB
subgraph "Kappa架构"
K1[数据源] --> K2[消息队列]
K2 --> K3[流处理层]
K3 --> K4[服务层]
end
5.2 维度对比表
| 维度 | Lambda | Kappa |
|---|---|---|
| 复杂度 | 高(两套系统) | 低(一套系统) |
| 代码维护 | 两份代码 | 一份代码 |
| 数据一致性 | 需要额外保证 | 天然一致 |
| 重处理能力 | 强(批处理擅长) | 依赖 Kafka 保留 |
| 历史数据分析 | 强 | 弱 |
| 实时性 | 好 | 很好 |
| 资源利用 | 批处理资源峰值高 | 相对均衡 |
| 技术栈 | Hadoop + Storm/Flink | Kafka + Flink |
5.3 场景选择
flowchart TD
START[选择架构] --> Q1{历史数据需求?}
Q1 -->|需要分析多年历史| A1[Lambda]
Q1 -->|主要关注近期数据| Q2{数据处理复杂度?}
Q2 -->|复杂SQL/ML| A2[Lambda]
Q2 -->|中等复杂度| Q3{团队能力?}
Q3 -->|Flink精通| A3[Kappa]
Q3 -->|Spark为主| A4[Lambda]
Q1 -->|实时性要求极高| A5[Kappa]
style A1 fill:#4ecdc4
style A2 fill:#4ecdc4
style A3 fill:#ff6b6b
style A4 fill:#4ecdc4
style A5 fill:#ff6b6b
六、实战:架构选型案例
6.1 案例一:电商实时大屏
需求:
- 实时 GMV、订单数、UV
- 数据延迟 < 5秒
- 支持小时/天/周对比
graph LR
subgraph "推荐:Kappa架构"
A[订单数据] --> B[Kafka]
B --> C[Flink]
C --> D[ClickHouse]
D --> E[实时大屏]
end
理由:
- 实时性要求高
- 计算逻辑相对简单(聚合)
- 数据时间跨度短
6.2 案例二:用户行为分析平台
需求:
- 用户漏斗分析
- 留存分析(7日/30日)
- 用户路径分析
- 支持历史回溯
graph TB
subgraph "推荐:Lambda架构"
A[行为日志] --> B[Kafka]
B --> C[Flink
实时指标] B --> D[HDFS] D --> E[Spark
复杂分析] C --> F[Druid
实时查询] E --> G[Hive
历史分析] F --> H[分析平台] G --> H end
实时指标] B --> D[HDFS] D --> E[Spark
复杂分析] C --> F[Druid
实时查询] E --> G[Hive
历史分析] F --> H[分析平台] G --> H end
理由:
- 需要历史数据分析
- 计算逻辑复杂(路径分析、漏斗)
- 批处理更擅长这类分析
6.3 案例三:实时风控系统
需求:
- 毫秒级响应
- 实时规则匹配
- 特征实时计算
graph LR
subgraph "推荐:Kappa架构"
A[交易流水] --> B[Kafka]
B --> C[Flink CEP]
C --> D[Redis
特征存储] C --> E[风控决策] end
特征存储] C --> E[风控决策] end
理由:
- 极致实时性
- 流处理天然适合
- CEP 模式匹配
七、现代演进:实时湖仓
7.1 架构融合趋势
graph TB
subgraph "传统割裂"
A1[数据湖
离线分析] --- A2[数据仓库
即席查询] A1 --- A3[实时系统
流计算] end
离线分析] --- A2[数据仓库
即席查询] A1 --- A3[实时系统
流计算] end
graph TB
subgraph "湖仓一体"
B1[统一存储层
Hudi/Iceberg/Delta] B2[统一计算层
Spark/Flink] B3[统一服务层
Trino/Doris] B1 --> B2 --> B3 end
Hudi/Iceberg/Delta] B2[统一计算层
Spark/Flink] B3[统一服务层
Trino/Doris] B1 --> B2 --> B3 end
7.2 实时湖仓架构
graph TB
subgraph "数据源"
S1[业务DB]
S2[日志]
S3[消息队列]
end
subgraph "实时入湖"
S1 -->|Flink CDC| KAFKA[Kafka]
S2 --> KAFKA
S3 --> KAFKA
KAFKA --> FLINK[Flink]
end
subgraph "湖仓存储"
FLINK --> HUDI[Hudi/Iceberg]
HUDI --> S3[(S3/HDFS)]
end
subgraph "统一查询"
S3 --> TRINO[Trino/Spark]
S3 --> DORIS[Doris/StarRocks]
end
subgraph "应用"
TRINO --> BI[BI报表]
DORIS --> REALTIME[实时大屏]
TRINO --> ADHOC[即席查询]
end
7.3 流批一体:超越 Lambda 和 Kappa
graph LR
subgraph "流批一体"
A[同一份代码] --> B[批模式执行]
A --> C[流模式执行]
B --> D[同样的结果]
C --> D
end
Flink 流批一体示例:
-- 同一份 SQL,既能流处理也能批处理
-- Flink 1.14+ 支持
-- 创建 Kafka 源表(流)
CREATE TABLE orders_kafka (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'orders',
'properties.bootstrap.servers' = 'kafka:9092',
'format' = 'json'
);
-- 创建 Hudi 表(湖仓)
CREATE TABLE orders_hudi (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
order_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
'connector' = 'hudi',
'path' = 's3://bucket/orders',
'table.type' = 'MERGE_ON_READ'
);
-- 流模式:实时写入
SET 'execution.runtime-mode' = 'streaming';
INSERT INTO orders_hudi SELECT * FROM orders_kafka;
-- 批模式:全量重算
SET 'execution.runtime-mode' = 'batch';
INSERT OVERWRITE orders_hudi
SELECT * FROM orders_kafka
WHERE order_time >= '2024-01-01';八、架构选型决策框架
8.1 决策矩阵
| 考量因素 | 选 Lambda | 选 Kappa | 选湖仓一体 |
|---|---|---|---|
| 数据延迟要求 | 分钟级可接受 | 秒级/毫秒级 | 分钟级 |
| 历史数据分析 | 多年历史 | 近期为主 | 多年历史 |
| 计算复杂度 | 复杂 ML/图计算 | 聚合/简单计算 | 中等复杂 |
| 团队技术栈 | Spark 为主 | Flink 精通 | 都可以 |
| 维护成本考量 | 可接受两套 | 希望一套 | 希望统一 |
| 是否绑定云厂商 | 不介意 | 不介意 | 希望开放 |
8.2 最终决策流程
flowchart TD
A[开始选型] --> B{实时性要求?}
B -->|T+1足够| C[传统批处理]
B -->|需要实时| D{历史数据需求?}
D -->|多年历史分析| E{计算复杂度?}
D -->|近期数据为主| F[Kappa]
E -->|复杂| G[Lambda]
E -->|中等| H{技术栈成熟度?}
H -->|Flink成熟| I[Kappa + 补数机制]
H -->|都在建设| J[湖仓一体]
style C fill:#90EE90
style F fill:#ff6b6b
style G fill:#4ecdc4
style I fill:#ff6b6b
style J fill:#ffeaa7
九、总结
9.1 三种架构定位
graph LR
subgraph "架构定位"
A[Lambda] --> A1["两条腿走路
批处理 + 流处理"] B[Kappa] --> B1["一条腿跑步
一切皆流"] C[湖仓一体] --> C1["新跑道
流批融合"] end
批处理 + 流处理"] B[Kappa] --> B1["一条腿跑步
一切皆流"] C[湖仓一体] --> C1["新跑道
流批融合"] end
9.2 核心要点
mindmap
root((架构选型))
Lambda
适合复杂分析
历史数据友好
代价是维护两套
Kappa
简单统一
实时性好
依赖Kafka保留
湖仓一体
未来趋势
流批统一
生态在成熟中
9.3 一句话总结
Lambda:稳妥之选,批处理兜底,适合复杂场景
Kappa:极简之选,一套系统搞定,适合实时场景
湖仓一体:未来之选,流批融合,正在成为新标准