订阅更新

FlinkCDC → Kafka → Flink → Doris,实时数仓这条链路怎么设计最稳?

Kafka手册 第 8 题 约 8 分钟读完

本文目录
  1. 先说结论
  2. 实时数仓的完整架构
  3. FlinkCDC → Kafka这段怎么设计?
  4. Kafka → Flink → Doris这段怎么设计?
  5. 实战踩坑:CDC数据乱序导致的幽灵数据
  6. 两家公司架构对比
  7. 面试高频追问
  8. 总结

先说结论

Kafka在实时数仓里的角色是'数据总线':所有数据源(埋点SDK、FlinkCDC、第三方回传)统一写入Kafka,下游Flink按需消费处理,写入OLAP引擎(Doris/Hologres)。Kafka解耦了上下游,让数据源和消费者可以独立扩缩容。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

这道题不是单纯考Kafka,而是考你对'Kafka在实时数仓中扮演什么角色'的理解。面试官想看的是你能不能把Kafka和Flink的配合讲清楚。我在某元宇宙社交公司从0搭建了Flink+Kafka+Doris的实时数仓,在某短剧出海公司也是类似的实时链路(Kafka+Flink+Hologres),今天把这套架构的设计思路和踩坑经验分享出来。

实时数仓的完整架构

# 我在某元宇宙社交公司搭建的实时数仓架构
#
#  ┌──────────────┐
#  │ 数据源层      │
#  │ 埋点SDK       │──→ Kafka topic: user_event
#  │ FlinkCDC      │──→ Kafka topic: user_sync / gift_txn
#  │ 第三方API     │──→ Kafka topic: third_party
#  └──────────────┘
#          ↓
#  ┌──────────────┐
#  │ Kafka集群     │  3 Broker, 多Topic, 数据缓冲+解耦
#  └──────────────┘
#          ↓
#  ┌──────────────┐
#  │ Flink计算层   │  实时ETL / 聚合 / 维表Join
#  └──────────────┘
#          ↓
#  ┌──────────────┐
#  │ 存储层        │  Doris(实时查询)+ S3(离线备份)
#  └──────────────┘
#          ↓
#  ┌──────────────┐
#  │ 应用层        │  Metabase大盘 / 接口查询 / 报警
#  └──────────────┘

FlinkCDC → Kafka这段怎么设计?

FlinkCDC负责实时捕获MySQL的binlog变更,写入Kafka。这段链路有几个关键设计点:

// FlinkCDC配置(以MySQL为例)
MySqlSource<String> source = MySqlSource.<String>builder()
    .hostname("mysql-host")
    .port(3306)
    .databaseList("app_db")
    .tableList("app_db.users", "app_db.gift_transactions")
    .username("cdc_reader")
    .password("xxx")
    .deserializer(new JsonDebeziumDeserializationSchema())
    .build();
 
// 写入Kafka时的关键配置
// ① 用表的主键作为Kafka的Key → 同一条记录的变更进同一个Partition
// ② 保证同一主键的INSERT/UPDATE/DELETE有序
// ③ Kafka Sink开启Exactly-Once(两阶段提交)

为什么要把CDC数据写入Kafka而不是直接写Doris?三个原因:① 解耦——CDC数据可以被多个下游消费(实时大盘、风控、推荐算法各取所需);② 缓冲——上游MySQL变更速率波动大,Kafka可以削峰填谷;③ 回溯——Kafka保留7天数据,出问题可以重新消费。

Kafka → Flink → Doris这段怎么设计?

Flink消费Kafka数据做实时ETL,然后写入Doris。这段链路的关键:

设计点方案原因
Flink消费模式KafkaSource + Checkpoint保证Exactly-Once语义
数据清洗Flink DataStream中过滤+转换脏数据不写入Doris
维表JoinFlink Async IO查询维度表实时关联用户/设备维度
写入Doris批量Stream Load(攒批5s/1000条)避免逐条INSERT的低吞吐
Checkpoint间隔60秒平衡延迟和恢复开销
// Flink消费Kafka + 写入Doris的核心代码框架
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(60000);  // 60秒Checkpoint
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
 
// 从Kafka消费
KafkaSource<String> kafkaSource = KafkaSource.<String>builder()
    .setBootstrapServers("broker1:9092,broker2:9092")
    .setTopics("gift_txn")
    .setGroupId("flink-gift-etl")
    .setStartingOffsets(OffsetsInitializer.committedOffsets())
    .build();
 
DataStream<String> stream = env.fromSource(kafkaSource, ...);
// ETL处理 → 攒批 → Stream Load写入Doris

实战踩坑:CDC数据乱序导致的幽灵数据

有一次业务反馈:某个用户明明修改了昵称,但Doris里还是老昵称。排查发现是FlinkCDC写Kafka时没有用主键做Key,导致同一个用户的UPDATE和之前的INSERT进了不同Partition。Flink并行消费时,UPDATE先于INSERT被处理了——UPDATE找不到对应记录,被当作无效数据丢弃了。

解决方案:FlinkCDC写Kafka时,用表的主键(user_id)作为Kafka的Message Key。同一个user_id的所有变更都进同一个Partition,保证时序正确。这个改动之后幽灵数据问题彻底解决了。

两家公司架构对比

维度元宇宙社交公司短剧出海公司
数据源埋点SDK + FlinkCDC广告平台API回传 + 埋点
Kafka管理自建(Ambari+Know Streaming)云厂商托管
计算引擎Flink DataStreamFlink + 云厂商调度
OLAP存储Doris(自建)Hologres(云服务)
BI展示Metabase云厂商BI工具
数据时效秒级(实时大盘)5分钟(投放数据)

两套架构的核心思路一样:数据源 → Kafka(缓冲解耦)→ Flink(实时ETL)→ OLAP引擎(查询服务)。区别在于自建 vs 云服务。小公司建议优先用云服务,节省运维成本;数据量大或定制需求多再考虑自建。

面试高频追问

追问一:为什么用Kafka而不是直接Flink读MySQL?

三个原因:解耦(多下游共享)、缓冲(削峰填谷)、回溯(重新消费历史数据)。如果只有一个下游可以考虑直连,但生产环境很少只有一个消费者。

追问二:Flink Checkpoint失败了怎么办?

Checkpoint失败Flink会从上一次成功的Checkpoint恢复。Kafka的offset也回到上次Checkpoint的值,意味着会重复消费一部分数据。这就是为什么Consumer端要做幂等写入——Doris的UNIQUE KEY模型天然支持幂等。

总结

Kafka+Flink实时数仓面试,关键是讲清楚Kafka在架构中的角色(数据总线、解耦缓冲)和与Flink的配合方式(KafkaSource+Checkpoint+Exactly-Once)。CDC数据要用主键做Kafka Key保证顺序,写Doris要用Stream Load批量写入。两个踩坑点:CDC乱序和Doris写入瓶颈,都是实际生产会遇到的高频问题。

下一篇聊一个细节但很重要的问题:Kafka能保证消息有序吗?什么时候有序什么时候无序?

你们的实时数仓架构是什么样的?用了Kafka+Flink吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器