3. 数据架构设计:实时数据流架构选型与设计要点
说到实时数据流架构,我见过不少团队一上来就追新框架。结果呢?架构搞得特别重,业务却只需要几秒刷新一次。说白了,选型不是选最火的,而是选最合适的。
这一章,我就把 Kafka、Flink、ClickHouse 这三兄弟怎么搭、怎么配、怎么避坑,一次性讲清楚。
3.1 实时链路的核心逻辑
先看一张整体架构图。你想想看,主数据从业务系统产生,到最终在看板上展示,中间经历了什么?
核心链路: 业务数据库(MySQL/Oracle) → 数据采集(Canal/Debezium) → 消息队列(Kafka) → 流计算(Flink) → 实时存储(ClickHouse) → 看板查询
3.2 Kafka:实时数据的「高速公路」
Kafka 在这条链路里扮演什么角色?说白了,就是缓冲层。我见过最惨的案例,是业务高峰期数据库直接被采集程序打挂。有了 Kafka,上游随便写,下游慢慢读,谁也别拖累谁。
3.2.1 Topic 与分区设计
我个人习惯,主数据按实体类型分 Topic。比如:
- topic_customer:客户主数据变更
- topic_product:产品主数据变更
- topic_org:组织架构变更
分区数怎么定?我曾经踩过一个坑——分区设得太多,导致文件句柄耗尽。后来总结出一个经验公式:
分区数 ≈ max(消费者线程数 × 2, 目标吞吐量 / 单分区吞吐量)
一般建议 6~12 个分区,既能保证并发,又不会浪费资源。
3.2.2 关键配置参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
replication.factor |
3 | 保证高可用,允许一台 broker 宕机 |
min.insync.replicas |
2 | 配合 acks=all,确保数据不丢 |
retention.ms |
604800000(7天) | 给下游重跑留足时间窗口 |
compression.type |
snappy | 减少网络带宽和存储开销 |
注意: 主数据场景下,消息顺序很重要。同一个客户 ID 的变更必须发到同一个分区。设置 key 为业务主键,就能保证有序性。
3.3 Flink:流计算的「大脑」
Flink 负责把原始变更流变成指标结果。我记得刚开始用 Flink 时,最头疼的就是状态管理和 exactly-once 语义。这里分享几个实战要点。
3.3.1 处理流程设计
一个典型的 Flink 作业,大概长这样:
// 伪代码示例
DataStream<String> source = env.addSource(kafkaConsumer);
// 1. 解析 JSON 消息
DataStream<CustomerEvent> events = source
.map(json -> parseCustomerEvent(json));
// 2. 按客户 ID 分组
DataStream<CustomerEvent> keyed = events
.keyBy(CustomerEvent::getCustomerId);
// 3. 滚动聚合:统计变更次数
DataStream<CustomerStats> stats = keyed
.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.aggregate(new CustomerAggregator());
// 4. 写入 ClickHouse
stats.addSink(clickhouseSink);
3.3.2 状态后端选型
| 状态后端 | 适用场景 | 我踩过的坑 |
|---|---|---|
| RocksDB | 大状态(>1GB) | 磁盘 I/O 容易成为瓶颈,建议用 SSD |
| HashMap | 小状态(<1GB) | GC 压力大,状态大了容易 OOM |
我的建议: 主数据场景状态一般不大,用 HashMap 就够了。但如果要做 7 天窗口聚合,还是上 RocksDB 稳妥。
3.3.3 检查点配置
检查点是 Flink 容错的核心。我曾经因为检查点间隔设得太短,导致作业频繁重启。后来调整为:
- 间隔: 30 秒一次
- 超时: 10 分钟
- 最小暂停: 500 毫秒
- 并发检查点: 1(避免资源竞争)
3.4 ClickHouse:实时查询的「加速器」
ClickHouse 为什么适合做实时看板?因为它快。列式存储、向量化执行、稀疏索引,这些特性让它在聚合查询上秒杀传统数据库。
3.4.1 表引擎选择
| 引擎 | 特点 | 推荐场景 |
|---|---|---|
| MergeTree | 基础引擎,支持分区和排序 | 通用场景 |
| ReplacingMergeTree | 去重合并,适合主数据 | 客户、产品等实体表 |
| SummingMergeTree | 预聚合,适合指标 | 变更次数、累计值 |
实战经验: 主数据看板我一般用 ReplacingMergeTree 存最新状态,用 SummingMergeTree 存聚合指标。这样查询时不用做额外计算,直接出结果。
3.4.2 建表语句示例
CREATE TABLE customer_stats (
customer_id UInt64,
change_count UInt32,
last_change_time DateTime,
change_type String,
version UInt32
) ENGINE = ReplacingMergeTree(version)
PARTITION BY toYYYYMMDD(last_change_time)
ORDER BY (customer_id, last_change_time);
3.4.3 写入优化
ClickHouse 不适合单条写入。我建议 Flink 端做批量攒批:
- 批次大小: 1000~5000 条
- 攒批时间: 1~3 秒
- 写入方式: 使用 ClickHouse JDBC 的
PreparedStatement批量提交
避坑指南: 我曾经把批次设到 10000 条,结果 ClickHouse 内存直接打满。后来发现,批次大小要根据数据行宽来调整。行宽越大,批次越小。
3.5 整体链路延迟分析
一个完整的实时链路,延迟主要来自这几个环节:
- 采集延迟: Canal 监听 binlog,毫秒级
- Kafka 延迟: 消息从生产到消费,毫秒级
- Flink 计算延迟: 取决于窗口大小,秒级
- ClickHouse 写入延迟: 批量写入,秒级
整体来看,从数据产生到看板展示,延迟通常在 3~10 秒。这个范围对主数据看板来说,完全够用。
小技巧: 如果业务要求秒级刷新,可以把 Flink 窗口设小一点,比如 5 秒。但要注意,窗口越小,写入 ClickHouse 的频率越高,需要评估集群压力。
3.6 本章小结
嗯,到这里,实时数据流架构的核心选型就讲完了。记住三个关键点:
- Kafka 负责解耦和缓冲,分区和副本数要合理
- Flink 负责计算和状态管理,检查点和状态后端要配好
- ClickHouse 负责存储和查询,表引擎和写入策略要选对