订阅更新

实时项目怎么讲才能展示深度:实时项目表达模板

项目实战表达 13 讲 · 第 3 讲 约 32 分钟读完

本文目录
  1. 开篇:一个只会念技术栈的候选人
  2. 先说结论
  3. 实时项目的 STAR 模板——某元宇宙社交应用示范
  4. 架构图怎么画——面试白板必杀技
  5. 必问追问1:延迟多少?端到端怎么拆?
  6. 必问追问2:怎么保证不丢数据/不重复?Exactly-Once 怎么实现?
  7. 必问追问3:乱序数据怎么处理?Watermark 怎么设?
  8. 必问追问4:遇到过数据倾斜吗?怎么处理的?
  9. 必问追问5:State 管理怎么做的?为什么选 RocksDB?
  10. 必问追问6:为什么选 Doris 不选 ClickHouse?
  11. 其他高频追问
  12. 面试回答分层——60分 vs 75分 vs 90分
  13. 总结
  14. 下一篇

开篇:一个只会念技术栈的候选人

前段时间帮朋友公司面一个3年经验的实时数据开发。简历上写着"主导Flink+Kafka+Doris实时数仓建设"——听起来挺有料。

面试官让他讲讲这个项目。他开口就是:

"我们用Kafka做消息队列,Flink做实时计算,Doris做OLAP查询。Kafka的分区数是32个,Flink的并行度是16,Doris用的是Unique模型。数据从Kafka进来,Flink做处理,结果写到Doris,前端查询走Doris。"

面试官追问:"你们端到端延迟多少?"

"大概几分钟吧。"

"几分钟是多少?具体拆解一下?"

"就是整体比较快。"

"Checkpoint间隔设的多少?为什么?"

"用的默认配置。"

"遇到过反压吗?怎么排查的?"

"没遇到过反压问题。"

面试结束。面试官的评语是——"技术栈名词熟悉,但没有任何实战深度,怀疑不是主导者。"


实时项目是大数据面试里最能展示技术深度的项目类型——因为实时架构比离线复杂得多,涉及的技术点密集:消息队列、流计算、状态管理、容错机制、OLAP查询、反压调优……随便拎一个出来都能深挖半小时。

正因为深度大,实时项目也是最容易翻车的——很多人只会讲"我们用了Flink+Kafka+Doris",一追问 Checkpoint 原理、Watermark 机制、Exactly-Once 实现、反压定位就卡壳。

面试官心里那把尺是很清楚的:"念技术栈"是5分,"讲架构决策+具体实现+踩过的坑"才是90分。


这篇我会讲透实时项目的表达模板——怎么把实时项目讲出技术深度,让面试官觉得你真的是架构设计者而不是代码复制者。我会用某元宇宙社交应用的 Flink+Doris 实时数仓从0搭建项目做完整示范,把延迟拆解、Exactly-Once 实现、数据倾斜处理、State Backend 选型这些高频追问点全部展开。


先说结论

实时项目的表达 = STAR框架 + 三个核心讲清楚:

  1. 为什么需要实时(业务驱动不是技术驱动)——"运营T+1发现刷单已经损失几万元" vs "我们想用Flink玩一下"
  2. 架构怎么设计(数据流向 + 每个组件的选型原因)——不是念技术栈,是讲"为什么选Doris不选CK、为什么Kafka分区32"
  3. 难点怎么解决(至少讲一个你实际解决过的深度难点)—— Exactly-Once / 延迟优化 / 数据倾斜 / State 管理四选一

STAR 框架前面两篇讲过了,这篇重点在后三个。


实时项目的 STAR 模板——某元宇宙社交应用示范

S(背景,30秒)

"我在某元宇宙社交应用负责实时数据平台。业务是虚拟社交——用户在App里送虚拟礼物、打赏主播、参加活动。涉及充值、消费、提现等高频交易。

之前数据是 T+1 的——运营第二天才能看到昨天的交易数据。有一次某个热门礼物被羊毛党刷单,T+1 才发现异常,当时已经损失了几万元。 业务方强烈要求实时监控和实时风控。"

这段 S 的三个亮点:

  1. 业务脱敏到位:"某元宇宙社交应用 + 虚拟礼物/打赏" —— 面试官能立刻理解业务形态
  2. 痛点用故事讲:不是抽象的"T+1 不够用",而是"某个礼物被刷单损失几万元"这种具象痛点
  3. 业务需求明确:"实时监控 + 实时风控"——这是驱动做实时的业务原因,不是"我们想用新技术"

T(任务,20秒)

"我的任务是从0搭建实时数仓,目标三个:把交易数据的时效从 T+1 提升到分钟级、支撑运营的实时大盘、支撑风控的实时规则匹配(可疑交易5分钟内告警)。"

"从0搭建"这个词再次出现——这是实时项目最硬的履历标签。如果不是从0搭建,要诚实说"我接手了实时平台后主导了 XX 模块的重构"。

A(行动,1.5分钟)—— 实时架构的核心部分

"我设计了 Kafka + Flink + Doris 的三层实时架构。

第一层,Kafka 作为实时数据总线。 App 的埋点数据和交易日志通过 Kafka Producer 实时推送,日均 5000 万条消息。Kafka 分区数设 32(和下游 Flink 并行度对齐),acks=all 保证不丢消息。

第二层,Flink 做实时 ETL。 从 Kafka 消费数据,做三件事——清洗(去重、格式校验)、维度关联(和 Doris 的维度表做异步 Lookup Join)、窗口聚合(按 5分钟滚动窗口统计 GMV、订单数、活跃用户)。关键设计上:用 Event Time + Watermark 30秒处理乱序、Checkpoint 间隔 60秒 + RocksDB State Backend保证容错、Flink Sink 到 Doris 用两阶段提交实现 Exactly-Once。

第三层,Doris 做实时 OLAP 查询。 Flink 聚合后的结果通过 Stream Load 实时写入 Doris 的 Unique 模型表,运营在 Metabase 上查看实时大盘。风控规则引擎单独订阅 Flink 的侧输出流,命中规则的交易直接推送告警到飞书风控群。

整个链路的分层:Kafka 是实时 ODS,Flink 输出到 Doris 的是实时 DWD(明细去重后)和实时 DWS(窗口聚合结果)。"

A 的核心亮点:

  • 每个组件都有配置细节:Kafka 分区 32、Flink Checkpoint 60秒、Watermark 30秒——这些数字是"真做过"的硬标签
  • 关键技术点点到为止但不展开:Exactly-Once、RocksDB State Backend、两阶段提交——每一个都是面试官会追问的勾子,你主动抛出来
  • 分层对应离线概念:Kafka=实时ODS、Flink输出=实时DWD/DWS——面试官听了会觉得你有体系化思考

R(结果,45秒)

"端到端延迟从 T+1 降到 5 分钟以内——具体拆解是:Kafka 到 Flink 200ms,Flink 窗口处理 2-3 秒(包含5分钟窗口的触发延迟),写入 Doris 1 秒,Metabase 查询展示 1-2 秒。

实时大盘支撑了运营、产品、风控三个团队的日常监控,DAU 约 200+。

发现了3次刷单异常——之前 T+1 时每次至少损失几万元,实时后 5 分钟内就能发现并封禁账号,损失降到千元级别。

整套架构后来作为公司实时数据的标准范式——后面新增的实时看板都基于这套框架扩展。"

R 的三层结构:

  1. 性能数字:端到端5分钟、每个环节拆解清楚
  2. 业务影响:3次刷单告警、损失从几万到千元
  3. 组织影响:作为公司实时数据标准范式

架构图怎么画——面试白板必杀技

面试中一定会遇到这句话:"你能画一下你们的实时架构图吗?" 很多人一听就懵——不知道画哪些组件、不知道怎么摆、不知道怎么讲。

架构图的"极简白板版"

不要画得像 PPT 那样花哨。面试白板只有 60 秒画图时间,抓住数据流向 + 关键组件就够了:

┌──────────────────────┐
│  App埋点 / 交易日志  │
└──────────┬───────────┘
           │ (实时推送)
           ▼
┌──────────────────────┐
│  Kafka (实时ODS)     │   日均 5000万条
│  32 partitions       │   acks=all
└──────────┬───────────┘
           │ (Flink 消费)
           ▼
┌──────────────────────────────────────────────┐
│           Flink (实时ETL)                    │
│  ┌────────────────────────────────────────┐  │
│  │ Source → 清洗 → 维度Lookup → 窗口聚合 │  │
│  └─────┬──────────┬─────────┬─────────────┘  │
│        │          │         │                │
│  Checkpoint  Watermark   State Backend       │
│  (60s)       (30s)       (RocksDB)           │
└────────┬──────────┬─────────┬────────────────┘
         │          │         │
    DWD去重  DWS聚合    侧输出流(风控)
         ▼          ▼         ▼
┌────────────────────┐  ┌──────────────┐
│  Doris (实时OLAP)  │  │ 风控告警系统 │
│  Unique/Agg 模型   │  │ (飞书机器人) │
└──────────┬─────────┘  └──────────────┘
           │
           ▼
    ┌────────────┐
    │ Metabase   │
    │ 实时大盘   │
    └────────────┘

画完图讲三件事

画完不要沉默等面试官提问。主动讲三件事:

第一:数据流向(15秒)

"数据从左上的 App 开始,通过 Kafka 作为数据总线进入 Flink 做实时 ETL,处理后分两路——聚合结果写入 Doris 支撑实时大盘,命中风控规则的交易通过侧输出流推送告警。"

第二:关键组件的选型原因(30秒)

"三个关键选型—— Kafka 选 32 分区:和 Flink 并行度对齐,保证一个 Flink SubTask 消费一个 Kafka 分区避免资源浪费。 Flink 选 RocksDB State Backend:用户量有 500 万,HashMap State 会撑爆堆内存,RocksDB 把 State 存本地磁盘堆内存只做缓存。 OLAP 选 Doris:我们有大量维度表需要 JOIN(用户维度、礼物维度),Doris 的 MPP 架构天然支持多表 JOIN,比 ClickHouse 在 JOIN 场景表现更好。"

第三:你在哪个环节做了什么(15秒)

"整个架构是我设计的。Kafka 接入和 Doris 建模都是我做的。Flink 作业的核心逻辑我写,团队里另一位同事负责风控规则引擎的对接。"

这60秒讲完,面试官已经对你的技术深度和项目归属有清晰判断了。


必问追问1:延迟多少?端到端怎么拆?

这是实时项目100%会被问到的追问。回答的关键不是说"5分钟"这个数字,而是能把端到端延迟拆解到每个环节。

低分回答(30分)

"延迟大概 5 分钟左右。"

——这句话等于没说。面试官会立刻追问:"5 分钟是端到端?还是哪一段?怎么拆解?"

高分回答(90分)

"端到端延迟 5 分钟以内,每一段的拆解是:

  1. App 到 Kafka:<500ms —— 客户端埋点 SDK 批量上报,网络延迟占大头
  2. Kafka 到 Flink:200ms —— Flink Source 批量拉取,Consumer Lag 保持在 1000 条以内
  3. Flink 处理延迟:2-3 秒 —— 清洗 +维度 Lookup +聚合,这里实际上是事件处理延迟
  4. 窗口触发延迟:5 分钟(这是延迟的大头)—— 5分钟滚动窗口,数据要等窗口关闭才输出
  5. Flink 到 Doris:<1 秒 —— Stream Load 批量写入,攒批 5 秒或 10MB 触发
  6. Doris 到查询展示:1-2 秒 —— Metabase 查询 + 展示

延迟的大头是窗口触发延迟——这是业务选择的结果,因为运营需要的是5分钟粒度的聚合数据而不是单条明细。如果改成 1 分钟窗口,延迟能降到 1 分钟多,但数据密度不够稀疏(每个维度的数据量少,结果抖动大)。"

这个回答的3个关键点:

  1. 完整拆解到每个环节——6个环节都有数字
  2. 指出延迟大头——窗口触发延迟是5分钟
  3. 给出业务解释——为什么窗口是5分钟不是1分钟

面试官听完立刻明白——这人对整个链路了如指掌,不是只知道"5分钟"这个数。


必问追问2:怎么保证不丢数据/不重复?Exactly-Once 怎么实现?

Exactly-Once 是实时项目的终极面试题。能把这个讲清楚的候选人不多,能讲清楚的直接+20分。

回答框架——端到端 Exactly-Once 的三要素

"端到端 Exactly-Once 需要三个环节同时保证:

第一,Source 端:Kafka 的 Offset 语义 + Flink Checkpoint 对齐。 Flink 从 Kafka 消费时,把每个 Checkpoint 里包含的 Kafka Offset 一起快照。Flink 作业挂了重启后,从 Checkpoint 恢复时会同时回到对应的 Offset 重新消费,保证不丢数据。

第二,中间处理:Flink Checkpoint + Barrier 对齐。 Flink 的 Checkpoint 机制通过 Barrier 对齐保证算子状态的一致性——所有算子处理到同一个 Barrier 时才会快照,避免某个算子快照在前、某个在后导致状态不一致。Checkpoint 我们设的间隔是 60 秒(下面有详细讲)。

第三,Sink 端:两阶段提交(2PC)到 Doris。 Flink Sink 不是直接写入 Doris,而是用两阶段提交——

  • 预提交阶段(preCommit):Flink 把这批数据攒成一个事务写入 Doris,但事务先不提交(Doris 的 Stream Load 支持 label 实现事务性)
  • 提交阶段(commit):Flink Checkpoint 成功后,通知 Doris 提交这批事务
  • 如果 Flink 挂了重启,没提交的事务会回滚,重启后会从 Checkpoint 重新处理这批数据

这三个环节加起来,实现了端到端 Exactly-Once——数据既不会丢也不会重复。"

补充讲 Checkpoint 间隔的 trade-off

面试官经常会追问:"Checkpoint 间隔为什么设 60 秒?"

"60 秒是数据一致性和系统性能的权衡。

间隔太短(比如 10 秒):Checkpoint 太频繁,每次都要触发 Barrier 对齐、State 快照、通知 Sink 提交事务,严重影响吞吐量。而且 RocksDB State 的快照涉及磁盘 IO,频繁快照会抢占业务处理资源。

间隔太长(比如 5 分钟):故障恢复时数据回放量大——如果 Flink 挂在第4分30秒,从上次 Checkpoint 恢复就要重新处理 4 分 30 秒的数据,恢复时间长,而且写入 Doris 的数据可能还没提交导致下游短暂不一致。

60 秒是我们反复压测出来的最优值——Checkpoint 成功率 >99%,单次 Checkpoint 耗时 <3 秒,故障恢复时间 <2 分钟。这个数字在我们的业务量级(5000万条/天)下最平衡。"

实际代码示例——Flink Sink 到 Doris 的两阶段提交

// Flink 作业的 Doris Sink 配置(Flink Doris Connector)
DorisSink.Builder<String> builder = DorisSink.builder();

DorisOptions.Builder dorisBuilder = DorisOptions.builder()
    .setFenodes("doris-fe:8030")
    .setTableIdentifier("realtime_dws.gmv_5min")
    .setUsername("flink_user")
    .setPassword("xxx");

// 两阶段提交配置
DorisExecutionOptions.Builder execBuilder = DorisExecutionOptions.builder()
    .setLabelPrefix("gmv-sink-" + UUID.randomUUID())  // 保证事务幂等
    .setStreamLoadProp(props)                          // Stream Load 配置
    .setBufferCount(3)
    .setBufferSize(8 * 1024 * 1024)                   // 攒批 8MB 触发
    .setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE);  // 关键:开启 EOS

// Checkpoint 配置
env.enableCheckpointing(60000);  // 60 秒间隔
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setCheckpointTimeout(30000);  // 30 秒超时
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(5000);

// State Backend
env.setStateBackend(new EmbeddedRocksDBStateBackend());
env.getCheckpointConfig().setCheckpointStorage("hdfs:///flink/checkpoints/");

builder.setDorisOptions(dorisBuilder.build())
       .setDorisExecutionOptions(execBuilder.build())
       .setSerializer(new SimpleStringSerializer());

stream.sinkTo(builder.build());

讲的时候怎么说:

"代码核心是三件事:DeliveryGuarantee.EXACTLY_ONCE开启两阶段提交、enableCheckpointing(60000)设置60秒Checkpoint间隔、EmbeddedRocksDBStateBackend配置RocksDB状态后端。labelPrefix 加 UUID 保证事务幂等——同一个 label 的事务 Doris 只接受一次,重复提交会被拒绝。"


必问追问3:乱序数据怎么处理?Watermark 怎么设?

核心概念先讲透

"乱序数据在实时流里是常态不是异常——网络延迟、客户端时间不同步、设备断网重连都会导致事件到达顺序不等于事件发生顺序。

Flink 用 Event Time + Watermark 机制处理乱序:

  • Event Time:事件实际发生的时间(从数据里读,不是到达 Flink 的时间)
  • Watermark:一个时间戳,代表'Event Time <= 这个时间的数据我都认为收齐了'
  • 窗口触发:Watermark 超过窗口结束时间时,窗口关闭并输出结果

Watermark 相当于告诉系统'我愿意等多久的迟到数据'。"

我们项目的 Watermark 策略

// 从 Kafka 源读数据时分配 Watermark
DataStream<OrderEvent> orderStream = env
    .fromSource(kafkaSource, 
        // Watermark 策略:允许 30 秒乱序
        WatermarkStrategy
            .<OrderEvent>forBoundedOutOfOrderness(Duration.ofSeconds(30))
            .withTimestampAssigner((event, ts) -> event.getEventTime()),
        "kafka-source");

// 侧输出流:处理迟到数据(Watermark 之后还来的数据)
OutputTag<OrderEvent> lateDataTag = new OutputTag<OrderEvent>("late-data"){};

// 5 分钟滚动窗口聚合 GMV
SingleOutputStreamOperator<GmvResult> result = orderStream
    .keyBy(OrderEvent::getGiftId)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .allowedLateness(Time.minutes(1))    // 再额外允许 1 分钟迟到
    .sideOutputLateData(lateDataTag)      // 超过 1 分钟的迟到数据走侧输出流
    .aggregate(new GmvAggregator());

// 迟到数据单独处理:写入一张对账表,第二天离线合并
DataStream<OrderEvent> lateStream = result.getSideOutput(lateDataTag);
lateStream.addSink(new LateDataDorisSink());

讲 Watermark 的 3 个维度

维度1:为什么 30 秒?

"30 秒是我们反复观察得出的——通过 Flink Web UI 看 Watermark 和系统时间的差值,99% 的数据能在 30 秒内到达,剩下 1% 走迟到处理。

设得太短(比如 5 秒):大量数据被判为迟到,主流数据丢失严重。 设得太长(比如 5 分钟):窗口一直不触发,端到端延迟爆炸。"

维度2:迟到数据怎么处理?

"我们用双重机制:

  • 窗口 + allowedLateness:30 秒Watermark + 额外 1 分钟 lateness——总共给 1 分半钟的宽容期,这段时间内到达的数据还能更新原窗口结果
  • 侧输出流:超过 1 分半钟还来的数据走侧输出流,写入late_data 对账表,第二天离线任务合并到正式数据

这样既保证了实时数据的时效,又保证了最终一致性——迟到数据不丢,只是不影响实时大盘。"

维度3:Watermark 不推进怎么办?

"Watermark 不推进通常是某个 Kafka 分区没有数据——Flink 默认按所有分区里最小的 Watermark 推进,一个分区不来数据,整体 Watermark 就卡住。

我们的做法是配置 withIdleness——如果某个 Partition 超过 1 分钟没数据,标记为 idle,不参与 Watermark 计算。

.withIdleness(Duration.ofMinutes(1))"


必问追问4:遇到过数据倾斜吗?怎么处理的?

真实踩坑故事——热门礼物 keyBy 倾斜

"遇到过很严重的一次。我们按 gift_id 做 keyBy 后做窗口聚合,结果发现有一个 SubTask 处理了 90% 的数据,整个作业延迟飙到 20 分钟。

排查过程:打开 Flink Web UI 的 Backpressure 和 Metrics,看到其中一个 SubTask 的 numRecordsIn 是其他 SubTask 的 100 倍左右。原因很明显——有一个爆款礼物'皇冠',当时正好是主播活动期间,这一个 gift_id 的交易量占了全平台 90%。"

解决方案——两阶段聚合

// 第一阶段:局部聚合(gift_id + 随机后缀打散)
DataStream<LocalAgg> localAgg = orderStream
    .map(event -> {
        // 热点key后面加随机后缀打散(100 个桶)
        int salt = ThreadLocalRandom.current().nextInt(100);
        return new KeyedEvent(event.getGiftId() + "_" + salt, event);
    })
    .keyBy(keyed -> keyed.getKey())
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .aggregate(new LocalAggregator());   // 局部聚合结果

// 第二阶段:全局聚合(去掉后缀,按原 gift_id 汇总)
DataStream<GmvResult> globalAgg = localAgg
    .map(local -> {
        // 去掉随机后缀
        String originalGiftId = local.getKey().split("_")[0];
        return new KeyedLocalAgg(originalGiftId, local);
    })
    .keyBy(keyed -> keyed.getKey())
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .aggregate(new GlobalAggregator()); // 全局聚合最终结果

讲效果

"改成两阶段聚合后,处理时间从 5 分钟降到 30 秒——因为局部聚合把热点数据打散到 100 个 SubTask 并行处理,每个 SubTask 的数据量变得均匀。

这个方案的核心代价是多了一轮 Shuffle——但相比倾斜导致的超长延迟,这个代价完全可以接受。

另外一个替代方案是用 Flink 的 MiniBatch + LocalGlobal 聚合(Flink SQL 提供的自动优化),不用手写两阶段。但 DataStream API 场景下,手写两阶段更灵活,能精细控制打散规则。"


必问追问5:State 管理怎么做的?为什么选 RocksDB?

State Backend 的三种选择

类型 存储位置 适合场景 劣势
HashMap JVM 堆内存 State 小(<几GB)、低延迟要求 大 State 会爆堆、Full GC
RocksDB 本地磁盘(堆外+磁盘) State 大(GB~TB级)、用户量大 序列化开销、访问比堆内存慢
EmbeddedRocksDB 同上但 Flink 1.15+ 内嵌 同上 + 减少依赖 同上

为什么我们选 RocksDB

"我们选 RocksDB 有三个原因:

第一,State 规模太大。 用户在线状态、用户维度缓存、窗口聚合中间结果——所有这些 State 加起来有十几 GB。如果用 HashMap Backend,全部放 JVM 堆里,分分钟 OOM 或者 Full GC 卡死。

第二,Checkpoint 稳定性。 RocksDB 支持增量 Checkpoint——每次只快照变化的 SST 文件,不用全量快照 State。我们 60 秒 Checkpoint 间隔下,单次 Checkpoint 耗时稳定在 3 秒以内。换成 HashMap 的全量快照,单次要 30 秒以上,Checkpoint 基本没法正常做。

第三,可以容忍轻微访问延迟。 RocksDB 把 State 存本地 SSD,访问比 HashMap 慢(纳秒级 → 微秒级),但我们的业务对延迟不敏感到这个量级——5 分钟窗口里多 100 微秒无所谓。"

State TTL 配置

// 用户在线状态 State 配置 TTL(避免状态无限增长)
ValueStateDescriptor<UserStatus> descriptor = new ValueStateDescriptor<>(
    "user-status", UserStatus.class);

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.hours(24))           // 24 小时 TTL
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .cleanupInRocksdbCompactFilter(1000)  // 在 RocksDB Compaction 时清理
    .build();

descriptor.enableTimeToLive(ttlConfig);

讲的时候怎么说:

"没有 TTL 的话,用户 State 会一直累积——一年就是几千万用户的历史状态,State 爆炸。我们设了 24 小时 TTL,用户 24 小时没活动就清理状态,Compaction 时物理删除。State 大小稳定在 10-15GB,不再无限增长。"


必问追问6:为什么选 Doris 不选 ClickHouse?

这个问题在实时 OLAP 岗位100% 被问。

选型对比

对比维度 Apache Doris ClickHouse
多表 JOIN MPP 原生支持,好几张表 JOIN 都 OK 弱,推荐大宽表模式
实时写入 Stream Load,5秒级延迟 批量写入,秒级~分钟级
UPDATE/DELETE Unique 模型原生支持 不支持(或新版本实验性)
并发 中高(几百 QPS) 单查询性能极强,但并发弱
运维难度 相对简单(FE+BE 两角色) 较复杂(分布式依赖 ZK)
SQL 兼容 MySQL 协议,标准 SQL 自有 SQL 方言
适合场景 业务查询、多维分析、实时报表 日志分析、单表大宽表查询

我们选 Doris 的具体原因

"我们的场景有三个特点:

第一,多表 JOIN 频繁。 交易事实表要 JOIN 用户维度、礼物维度、渠道维度——运营看数据都是'某渠道某类礼物的用户画像'这种复合分析。Doris 的 MPP 天然支持多表 JOIN,而 CK 在 JOIN 场景下性能会下降很多(CK 推荐宽表但我们维度太多做不成宽表)。

第二,需要 UPDATE 语义。 用户画像表需要根据最新行为更新。Doris 的 Unique 模型天然支持主键 UPDATE——相同主键的数据自动覆盖。CK 的 MergeTree 不支持原生 UPDATE,ReplacingMergeTree 虽然能做但数据最终一致性延迟不可控。

第三,并发查询压力大。 运营、产品、风控、BI 同时查——几百 QPS 的并发。Doris 的并发能力比 CK 好很多,CK 单查询快但并发上不去。

不选 CK 的反向理由:CK 更适合'单表大宽表 + 低并发分析'的场景(比如日志分析、漏斗分析),不适合我们这种'多维度业务分析'。"


其他高频追问

追问7:实时和离线数据不一致怎么办?

"这是实时数仓的经典永恒问题。我们的策略是'实时为主,离线对账':

实时数据用于日常监控和即时决策——误差<1% 可接受 离线数据每天凌晨跑全量,作为T+1 金标准

每天早上生成对账报告,对比实时和离线的关键指标差异。如果差异<1% 就接受(通常是乱序数据和迟到数据导致的小偏差),超过 1% 就排查——通常是 Flink 重启导致的短暂数据丢失、或者维度表变化没及时同步。

绝对精确的场景(财务结算、合规上报)永远以离线数据为准,实时数据只做监控不做决策。这个边界我们从一开始就和业务方明确了。"

追问8:Flink 作业挂了怎么恢复?

"Flink 的容错机制分三层:

第一层:自动 Failover。 TaskManager 或者 JobManager 挂了,Flink Session Cluster 会自动重启作业——从最近的 Checkpoint 恢复 State 和 Kafka Offset,恢复时间通常 1-2 分钟。

第二层:Savepoint 备份。 我们每天凌晨 3 点手动触发一次 Savepoint 存到 HDFS。如果 Checkpoint 意外损坏(比如磁盘故障),可以从 Savepoint 恢复——丢最多 1 天的数据。

第三层:Kafka 保底。 最坏情况所有 Checkpoint 都坏了,从 Kafka 的 earliest 重新消费——Kafka 保留 3 天数据,够我们补。但这样会产生重复数据,靠下游 Doris 的 Unique 模型基于主键幂等写入去重。

三层保障下来,数据不丢的目标能达到 99.99%。"

追问9:反压怎么处理?

"反压排查的标准套路是从下游往上游找:

第一步:Flink Web UI 看 Backpressure。 红色节点就是反压源。

第二步:看 Metrics 定位具体瓶颈。

  • 如果是 Sink 反压(最常见):通常是下游写入慢。比如 Doris Stream Load 写入量太大超过 BE 处理能力——解决方案是增加 Doris BE 节点或Flink 这边攒批更大批量写入。
  • 如果是 算子反压:通常是 CPU/Memory 瓶颈。可以增加并行度、优化算子逻辑(比如维度关联从同步 Lookup 改成异步 Lookup)。
  • 如果是 Source 反压(罕见):通常是 Kafka Consumer 跟不上 Producer——增加 Kafka 分区数和 Flink 并行度。

第三步:看 GC Log。 频繁 Full GC 也会表现为'反压'——通常是 State 太大堆内存不够,调整堆大小或者换 RocksDB State Backend。

我们遇到最多的反压场景:大促期间流量暴增,Doris 写入成为瓶颈——解决方案是提前 1 天扩 Doris 节点 +调大 Flink Sink 的 sink.buffer-flush.max-rows 参数从 10000 改到 100000 减少写入频率。"

追问10:你觉得这个实时架构有什么可以改进的?

"三个改进方向:

第一,Doris 写入优化。 当前 Stream Load 高频小批量写入,Doris 的 Compaction 压力大(有时 BE 磁盘 IO 打满)。可以改成 Flink 攒更大批量再写——比如从 5 秒 10MB 改成 30 秒 50MB,减少写入频次。代价是延迟增加 25 秒。

第二,引入流批一体。 目前实时和离线是两套独立链路,相同业务逻辑要写两遍代码——Flink SQL 写一遍、Spark SQL 再写一遍,维护成本大。可以用 Paimon 或 Iceberg 做流批一体存储,一套代码同时支撑实时和离线,降低维护成本。

第三,成本优化。 Flink 集群是 7x24 常驻的,低峰期资源浪费严重。可以按业务峰值设计弹性伸缩——低峰期自动缩容 50%。阿里云的 Flink 全托管版本有这个能力,自建 YARN 集群要自己实现。

这三个都是已知的技术债,但业务优先级更高,暂时没做。"


面试回答分层——60分 vs 75分 vs 90分

60分回答(技术栈复读机)

"我们用 Kafka 做消息队列,Flink 做实时计算,Doris 做 OLAP 查询。数据从 Kafka 进来,Flink 处理,写到 Doris。前端查 Doris。"

面试官内心OS:"这人没主导过实时项目,大概率只写过 Flink SQL。"

问题诊断:

  • 没有业务场景——完全不知道为什么要做实时
  • 没有选型理由——为什么 Kafka 不用 Pulsar、为什么 Doris 不用 CK
  • 没有具体实现——Checkpoint、Watermark、State 一字未提
  • 没有数字——延迟、规模、效果全是空的

75分回答(知道概念但没深度)

"我在XX公司做了一个实时数仓项目。用 Kafka + Flink + Doris 架构,Flink 开了 Exactly-Once,Watermark 处理乱序,Checkpoint 做容错。端到端延迟大概5分钟,支撑了实时大盘。遇到过数据倾斜,加了随机前缀打散。"

面试官内心OS:"知道术语,但感觉没深入。追问一个就知道真假了——他说开了 EOS,知道原理吗?"

问题诊断:

  • 有框架没血肉——每个点都是一句话带过
  • 没有拆解——延迟5分钟但没拆解到每段
  • 没有权衡——Checkpoint 60秒 vs 10秒 vs 5分钟怎么选的
  • 没有代码/配置细节——Watermark 多少秒、allowedLateness 多少

90分回答(前面完整示范的那版)

结构完整(STAR)+ 架构图清晰 + 配置数字具体(Kafka 32分区、Watermark 30秒、Checkpoint 60秒、RocksDB State、5000万条/天、端到端5分钟)+ 核心追问都有代码级别的回答(两阶段提交、两阶段聚合、State TTL)+ 有真实踩坑故事(热门礼物倾斜、5分钟→30秒)+ 有反思(Doris写入优化、流批一体)

面试官内心OS:"这人绝对是架构设计者,整个链路每个细节都真懂。可以讨论更深的话题了。"

90分和75分的最大差距——具体配置数字 + 代码级别的追问回答 + 真实踩坑故事。这三样是"真做过"的硬证据。


总结

实时项目是大数据面试里技术深度最可量化的项目类型。要讲出 90 分,核心是——

  • 业务驱动不是技术驱动:讲清楚"为什么需要实时"(刷单损失、风控告警、实时决策),不是"我们想用Flink"
  • 架构图要能60秒讲完:数据流向 + 关键组件配置 + 你负责的部分,白板能画清楚
  • 延迟能拆解到每个环节:App→Kafka、Kafka→Flink、Flink处理、窗口触发、Flink→Doris、查询展示——6个环节每段都有数字
  • Exactly-Once 讲三要素:Source端Kafka Offset + 中间Checkpoint Barrier对齐 + Sink端两阶段提交,缺一不可
  • Watermark 讲三维度:多少秒、怎么处理迟到数据、Partition idle怎么办
  • State Backend 讲 trade-off:为什么选 RocksDB(State规模、Checkpoint稳定性、增量快照)
  • 数据倾斜讲具体故事:两阶段聚合代码是"真做过"的硬证据
  • OLAP 选型讲业务特点:Doris vs CK 的 3 个决策点(多表JOIN、UPDATE语义、并发)
  • 反压排查讲标准套路:Backpressure → Metrics → GC Log,从下游往上游找
  • 面试策略:实时项目是技术深度的最佳舞台——其他候选人只会念技术栈,只要你能讲出配置数字+代码实现+踩过的坑,就是 top 10%

下一篇

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