数据与 BI

一笔订单怎样进入实时 BI:Kafka、Flink 与 StarRocks

从订单状态变化出发,理解事件标识、Kafka、Flink Checkpoint、StarRocks 主键模型和实时看板之间的边界。

1. 订单已经支付,看板为什么还没变化

业务数据库里,订单 order-1042 已经从待支付改成已支付。运营看板上的成交金额却没有变化。

这个问题不能只检查 Dashboard SQL。一笔订单到达看板,要经过完整链路:

text7 行
订单服务
  → MySQL Binlog / Order Event
  → Kafka
  → Flink
  → StarRocks 明细表
  → 聚合表或物化视图
  → BI Dashboard

每一段都有自己的进度和失败方式。业务库更新成功,只能证明交易系统接受了这次修改;它无法证明 Kafka 已经收到事件、Flink 已经处理、StarRocks 已经提交,或者看板查询使用了正确指标。

2. 一笔订单会产生多次变化

订单并非创建后就保持不变。一个简化过程可能是:

text2 行
CREATED → PAID → SHIPPED → FINISHED
             └→ REFUNDING → REFUNDED

如果把每次变化直接追加到分析表,下面的查询会把同一订单计算多次:

sql3 行
SELECT SUM(amount)
FROM order_event
WHERE status = 'PAID';

分析平台需要明确保存“事件历史”还是“订单当前状态”。两者都可能需要,但用途不同:

数据 适合回答的问题
订单事件明细 状态何时变化,处理路径是什么
订单当前快照 当前有多少已支付订单
指标聚合 某时间范围的成交、退款和转化

事件至少需要稳定身份:

text8 行
event_id
order_id
user_id
shop_id
status
amount
event_time
order_version

event_id 用来识别一次业务事件,order_id 标识业务对象,order_version 帮助判断同一订单变化的先后关系。只依赖消息到达时间,无法处理网络重试和迟到事件。

3. Kafka 保存的是有序分区中的记录

Kafka Topic 被分成多个 Partition。生产者通常用 order_id 作为 Key,让同一订单的事件进入同一分区,从而保持该分区内的顺序。

这不代表所有订单拥有全局顺序,也不代表消费者业务天然不会重复。消费者失败恢复、生产者重试和下游事务边界都会影响最终结果。

Java 订单服务还要解决“数据库已提交,但事件没有发出”的间隙。常见方案会把业务变更和待发送事件放在同一个本地事务中,再由独立发布器把事件送入 Kafka。本文只讨论这条边界,不把某一种 CDC 或 Outbox 实现写成唯一选择。

text8 行
业务事务
  ├─ UPDATE orders
  └─ INSERT event_outbox

发布器
  → 读取未发送事件
  → 写入 Kafka
  → 记录发布结果

即使发布器重试,稳定的 event_id 也让下游有机会识别同一事件。

Flink 可以保存算子状态和数据源进度。发生故障后,作业从 Checkpoint 恢复,重新读取尚未完成的一段数据。

状态可能包括:

text4 行
每个订单已看到的最大版本
窗口中的金额聚合
去重所需的 event_id
Kafka 消费位置

Checkpoint 成功不等于下游结果一定只写一次。端到端语义还依赖 Source、状态后端、Sink 和外部系统是否共同支持相应协议。

StarRocks Flink Connector 的 exactly-once 支持有明确的版本与配置条件。文章写作时需要记录所核对的 Connector、Flink 和 StarRocks 版本,不能只在架构图旁标一个“Exactly Once”。

5. StarRocks 主键表承接订单当前状态

订单当前快照可以使用主键模型表达。下面只保留说明字段:

sql11 行
CREATE TABLE order_current (
  order_id BIGINT NOT NULL,
  user_id BIGINT,
  shop_id BIGINT,
  status VARCHAR(32),
  amount DECIMAL(18, 2),
  event_time DATETIME,
  order_version BIGINT
)
PRIMARY KEY (order_id)
DISTRIBUTED BY HASH(order_id);

上面的 DDL 是说明示例。分桶、排序键、分区、数据副本和更新顺序需要结合数据规模与官方版本配置。

主键表让同一 order_id 的新状态覆盖旧状态。它解决的是表内更新模型,不能自动判断一个版本更旧的迟到事件是否应该覆盖新状态。处理链路仍要携带并校验业务版本。

StarRocks 的 Routine Load 可以持续消费 Kafka 数据,Flink Connector 则适合已经在 Flink 中完成清洗、关联和状态处理的链路。选择入口时要看转换复杂度、状态需求和统一运维方式。

6. 三个“成功”需要分别记录

一次事件进入实时 BI,可以拆成:

text3 行
采集成功:事件进入 Kafka
处理成功:Flink 完成当前 Checkpoint 覆盖的计算
写入成功:StarRocks 提交对应批次

看板仍可能因为查询缓存、物化视图刷新、权限或指标条件而显示旧结果。因此还要记录:

  • Kafka 最新位置与消费积压。
  • Flink 最近成功 Checkpoint 和失败原因。
  • StarRocks 导入任务状态、异常行与数据时间。
  • 指标表最近更新时间与看板查询时间。

把所有延迟合并成一个“接口慢”,很难定位问题发生在哪一段。

7. Java 后端在这条链路里的位置

Java 业务服务负责产生带有稳定标识和业务版本的订单变化。数据平台控制面可以继续管理 Topic、作业、数据表、指标任务和告警记录,但不会替代 Kafka Broker、Flink Runtime 或 StarRocks 查询引擎。

一个可追踪事件需要把业务身份贯穿下去:

text5 行
order_id + event_id
  → Kafka record key / headers
  → Flink record
  → StarRocks detail row
  → BI drill-down

看板发现异常金额时,才能沿着标识回到订单和原始事件。

沿着订单变化拆下去会发现,“实时数仓”并不只是把消息写进一个查询很快的数据库。实时链路实际包含业务事件、消息顺序、状态恢复、写入事务和指标查询等多个环节,每个环节都有可观察的状态,链路才能被追踪和恢复。

相关阅读

参考资料

← 返回文章