Flink 流式计算的状态之道从 Table/SQL 语义到算子状态与 TTL 精准控制

1. 统一语义的起点:动态表与变更流

  • Flink 的 Table API & SQL批流语义统一 :无论输入是否有界,都视为动态表 ,以 变更日志(Changelog) 体现结果演进(RowKindINSERT/DELETE/UPDATE_BEFORE/UPDATE_AFTER)。
  • 有界流可启用 Batch Runtime Mode 获得更优算子(如 sort-merge join、阻塞交换),但同样的查询在 Streaming 模式也能跑,最终结果一致(批模式通常折叠为 insert-only)。

2. 显式 vs 隐式:哪些查询会"吃状态"?

2.1 显式有状态(典型:聚合、Join、去重)

下面这个词频统计需要按 word 维护计数,自然是Keyed State见图 1):

sql 复制代码
CREATE TABLE doc (word STRING) WITH ('connector'='...');
CREATE TABLE word_cnt (
  word STRING PRIMARY KEY NOT ENFORCED,
  cnt  BIGINT
) WITH ('connector'='...');

INSERT INTO word_cnt
SELECT word, COUNT(*) AS cnt
FROM doc
GROUP BY word;
  • Key 空间随新词增多而增长,状态体量持续上涨------需要窗口、TTL 或业务限界来兜底。
md 复制代码
<!-- FIG:STATEFUL_FROM_QUERY -->
![图 1:按查询派生的有状态算子(词频聚合,含 Keyed State 与 RowKind)](path/to/fig-stateful-from-query.png)
> 注:状态算子对相同 key 进行累加,向下游产生 +I/-U/+U。

2.2 隐式有状态(输入/下游契约触发)

即便是看似"无状态"的 SELECT *,也可能因为上游/下游的变更契约 被 Planner 插入Changelog 归一化 算子(ChangelogNormalize),从而自动变成有状态见图 2):

sql 复制代码
CREATE TABLE upsert_kafka (
  id INT PRIMARY KEY NOT ENFORCED,
  message STRING
) WITH ('connector' = 'upsert-kafka', ...);

SELECT * FROM upsert_kafka;
  • 若下游需要完整变更 (含 UPDATE_BEFORE),而上游仅提供 INSERT/UPDATE_AFTER/DELETE,Planner 会在中间加一个状态化归一化算子补齐 BEF/AFT。
md 复制代码
<!-- FIG:STATEFUL_FROM_TRAIT -->
![图 2:由输入特征隐式派生的有状态算子(upsert-kafka → ChangelogNormalize)](path/to/fig-stateful-from-trait.png)
> 注:上游仅 INSERT/UPDATE_AFTER/DELETE,算子补齐 UPDATE_BEFORE 以满足下游完整变更。

3. DataStream × Table 融合:怎么"进出两界"更顺手?

常用桥接是 StreamTableEnvironment

java 复制代码
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);

// DataStream -> Table(插入流,自动推导 schema)
DataStream<String> ds = env.fromElements("Alice","Bob","John");
Table t = tEnv.fromDataStream(ds);
tEnv.createTemporaryView("InputTable", t);

// SQL 清洗后再回 DataStream
Table result = tEnv.sqlQuery("SELECT UPPER(f0) AS name FROM InputTable");

// 仅 append-only 用 toDataStream;有更新请选择 toChangelogStream
DataStream<Row> out = tEnv.toDataStream(result);
out.print();
env.execute();
  • 只插入(append-only)toDataStream
  • 带更新toChangelogStream(产出 RowKind,可对接 Upsert Sink 或自定义算子)。
  • 事件时间/水印 :可在 fromDataStream(..., Schema) 中声明 rowtimeWATERMARK,或继承 DataStream 的 source watermark(SOURCE_WATERMARK())。

4. 事件时间与水印:三种声明姿势

  1. 字段派生 rowtime + 策略
java 复制代码
Table t = tEnv.fromDataStream(ds,
  Schema.newBuilder()
    .columnByExpression("rowtime","CAST(event_time AS TIMESTAMP_LTZ(3))")
    .watermark("rowtime","rowtime - INTERVAL '10' SECOND")
    .build());
  1. 继承 DataStream 的水印
java 复制代码
Table t = tEnv.fromDataStream(ds,
  Schema.newBuilder()
    .columnByMetadata("rowtime", "TIMESTAMP_LTZ(3)")
    .watermark("rowtime", "SOURCE_WATERMARK()")
    .build());
  1. 回流 DataStream 时携带时间戳
  • toDataStream(table):单一 rowtime 会写入 record timestamp,水印继续传播
  • 也可将时间戳作为 metadata 输出,不占物理列。

5. 状态保留(TTL):从全局到"算子×输入边"的精细化

5.1 全局 TTL(管道级)

sql 复制代码
SET 'table.exec.state.ttl' = '30 d';
  • 某 Key 在 TTL 内没有更新即被清理;再次到达视为新 Key(计数从 0 开始)。
  • 适合长尾 Key 多且允许"遗忘"的聚合场景。

5.2 算子级 TTL(更细粒度)

适用:同一作业有多个状态且保留期不同(例如去重 1h、聚合 7d)。

三种设置方式(优先级从低到高):

  1. 全局:table.exec.state.ttl

  2. SQL Hint(仅"常规 Join / Group Agg"):

    sql 复制代码
    SELECT /*+ STATE_TTL('6 h') */ user_id, COUNT(*) FROM t GROUP BY user_id;
  3. Compiled Plan JSON(最通用,逐算子逐输入边):见 5.3

注:窗口/区间 Join/Agg/Top-N 不靠 table.exec.state.ttl 控制状态,依据窗口边界自然清理。

5.3 用 Compiled Plan 精准改 TTL

生成 JSON 计划:

java 复制代码
CompiledPlan plan = tEnv.compilePlanSql(
  "INSERT INTO enriched_orders " +
  "SELECT a.order_id, a.order_line_id, b.order_status " +
  "FROM orders a JOIN line_orders b ON a.order_line_id = b.order_line_id");
plan.writeToFile("/path/to/plan.json");

修改 Join 节点的 state 字段(单位 ms):

json 复制代码
"state": [
  {"index": 0, "ttl": "3000 ms", "name": "leftState"},
  {"index": 1, "ttl": "9000 ms", "name": "rightState"}
]

执行计划:

sql 复制代码
EXECUTE PLAN '/path/to/plan.json';

经验法则:下游算子 TTL ≥ 上游算子 TTL ,避免"下游先忘、上游还在"的语义问题。

通过 EXECUTE PLAN 提交的作业,以文件中的 TTL 为准,不会再读取 全局 table.exec.state.ttl

6. 批运行模式(Batch Runtime Mode):同一逻辑,两种执行

java 复制代码
env.setRuntimeMode(RuntimeExecutionMode.BATCH); // 或 STREAMING
StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);
  • BATCH :不产生增量更新,最终折叠成 insert-only;可启用阻塞交换、禁用 checkpoint,资源占用更低。
  • STREAMING :持续产生 +I/-U/+U/-D
  • 使用事件时间 + 水位线 语义时,两模式最终表一致(只是输出方式不同)。

7. 运维观测:你至少要盯的 6 件事

  1. 状态体量:key/bytes、RocksDB 指标(若使用)。
  2. Checkpoint:对齐耗时、大小、失败率。
  3. 水位线:推进速率与滞后分布。
  4. 算子健康:Busy/BackPressured/Idle、Records In/Out、Shuffle 延迟。
  5. TTL 效果:清理速率/被清理 key 数,长尾是否受控。
  6. 计划核验EXPLAIN/table.explain() 确认是否引入了意料之外的状态化归一化节点。

8. 升级与演进:保存点不是"万能药"

  • 任何 查询变更或 Planner 规则升级,都可能改变拓扑或中间状态 schema,导致** Savepoint 不兼容**。

  • 建议

    • 小版本(补丁版)一般安全;跨小版本/大版本不保证兼容
    • 变更前后做 EXPLAIN Diff;必要时用**历史数据热身(warm-up)**新作业,再切实时流。
    • 关键任务优先走灰度 + 双写对比

9. 端到端模板:三类常见落地场景

9.1 SQL 预处理 → DataStream 自定义低阶算子

  • 用 Table 生态(Catalog/Connector/函数)做取数与清洗;
  • toDataStream() 回到 DataStream,写 KeyedProcessFunction(定时器/状态机);
  • 落入 Upsert/JDBC/ES。

9.2 DataStream 产两路流 → 注册为表 → SQL 区间 Join → 回 DataStream

  • 两路 DS 补水印;createTemporaryView(..., Schema.watermark(...))
  • SQL 写 interval join;toDataStream() 继续做风控/告警;
  • 批/流模式均可跑,最终结果一致

9.3 带更新的聚合对接 KV

  • toChangelogStream() 承接 SUM/COUNT 等更新;
  • 若目标是 KV/Upsert Sink,优先 Upsert 模式 + 主键 ,减少 UPDATE_BEFORE 流量。

10. 上线前 Checklist(精简版)

  • 是否存在隐式有状态(如 Normalize)?
  • 状态是否窗口化 或有 TTL 兜底?
  • toDataStream(append-only)与 toChangelogStream(更新)是否选用正确?
  • Rowtime/Watermark 的声明或继承是否正确?
  • Sink 能力与 Changelog 模式(append/upsert/retract)匹配
  • 监控到位:状态体量、TTL 清理、背压、水位、checkpoint。
  • 升级前 EXPLAIN 对比;必要时历史回放热身
相关推荐
大大大大晴天15 小时前
Flink生产问题排障-Kryo serializer scala extensions are not available
大数据·flink
tryCbest4 天前
数据库SQL学习
数据库·sql
cowboy2584 天前
mysql5.7及以下版本查询所有后代值(包括本身)
数据库·sql
努力的lpp4 天前
SQL 报错注入
数据库·sql·web安全·网络安全·sql注入
麦聪聊数据4 天前
统一 Web SQL 平台如何收编企业内部的“野生数据看板”?
数据库·sql·低代码·微服务·架构
山峰哥4 天前
吃透 SQL 优化:告别慢查询,解锁数据库高性能
服务器·数据库·sql·oracle·性能优化·编辑器
yumgpkpm5 天前
AI视频生成:Wan 2.2(阿里通义万相)在华为昇腾下的部署?
人工智能·hadoop·elasticsearch·zookeeper·flink·kafka·cloudera
轩情吖5 天前
MySQL初识
android·数据库·sql·mysql·adb·存储引擎
james的分享5 天前
大数据领域核心 SQL 优化框架Apache Calcite介绍
大数据·sql·apache·calcite