3. 数据架构设计:实时数据流架构选型与设计要点

说到实时数据流架构,我见过不少团队一上来就追新框架。结果呢?架构搞得特别重,业务却只需要几秒刷新一次。说白了,选型不是选最火的,而是选最合适的。

这一章,我就把 Kafka、Flink、ClickHouse 这三兄弟怎么搭、怎么配、怎么避坑,一次性讲清楚。

3.1 实时链路的核心逻辑

先看一张整体架构图。你想想看,主数据从业务系统产生,到最终在看板上展示,中间经历了什么?

核心链路: 业务数据库(MySQL/Oracle) → 数据采集(Canal/Debezium) → 消息队列(Kafka) → 流计算(Flink) → 实时存储(ClickHouse) → 看板查询

实时数据流架构总览 业务数据库 Canal/Debezium Kafka Flink ClickHouse 各层职责说明: • 业务数据库:主数据的源头,存储客户、产品、组织等核心实体 • 采集层:监听数据库 binlog,实时捕获变更事件 • 消息队列:削峰填谷,解耦上下游,保证数据不丢 • 流计算:清洗、转换、聚合,产出指标结果 • 实时存储:提供毫秒级查询,支撑看板交互

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 整体链路延迟分析

一个完整的实时链路,延迟主要来自这几个环节:

  1. 采集延迟: Canal 监听 binlog,毫秒级
  2. Kafka 延迟: 消息从生产到消费,毫秒级
  3. Flink 计算延迟: 取决于窗口大小,秒级
  4. ClickHouse 写入延迟: 批量写入,秒级

整体来看,从数据产生到看板展示,延迟通常在 3~10 秒。这个范围对主数据看板来说,完全够用。

小技巧: 如果业务要求秒级刷新,可以把 Flink 窗口设小一点,比如 5 秒。但要注意,窗口越小,写入 ClickHouse 的频率越高,需要评估集群压力。

3.6 本章小结

嗯,到这里,实时数据流架构的核心选型就讲完了。记住三个关键点:

  • Kafka 负责解耦和缓冲,分区和副本数要合理
  • Flink 负责计算和状态管理,检查点和状态后端要配好
  • ClickHouse 负责存储和查询,表引擎和写入策略要选对