外观
状态 TTL 与状态膨胀治理
难度与前置
难度:进阶
前置:会配 Checkpoint;理解 Keyed State;看过 TaskManager 托管内存 / RocksDB 目录。
读完能做什么:为去重、会话、维表补全类状态选定 TTL 与清理方式,监控状态体量,避免无界膨胀拖垮 Checkpoint。
适用场景
- 去重、会话窗口、维表 JOIN、CEP 等按 key 长期占状态
- Checkpoint / Savepoint 体积持续上涨,恢复变慢
- RocksDB 目录或托管内存告警
不适用:
- 无状态 map/filter 作业
- 状态本身必须「永久保留」的账本类(改用外部存储,而不是无限 Flink 状态)
- 仅偶发大状态峰值且可接受(仍建议设上限与告警)
真实场景(一条)
实时 UV:用 ValueState 按 device_id 去重,未设 TTL。活动期新增设备千万级,状态从 30 GB 涨到 400 GB,Checkpoint 从 20s 变为 8~12 分钟,反压连锁。
治理:StateTtlConfig 对去重状态设 2 天 TTL(业务只关心 48h UV);开启增量 Checkpoint;监控 State Size。状态回到 ~45 GB,Checkpoint < 40s。
怎么做(步骤清单)
1. 先盘点「有哪些状态、业务要活多久」
| 状态用途 | 典型存活 | 过期后影响 |
|---|---|---|
| 精确一次去重 | 业务去重窗口(如 24~72h) | 过期后同一 id 可能再计 |
| 会话 gap | 会话超时 + 缓冲 | 会话被拆开 |
| 维表补全缓存 | 维表变更频率相关 | 读到旧维或 miss |
| 窗口状态 | 窗口结束 + allowedLateness | 与 WM 策略一致 |
写进设计文档:过期语义业务是否可接受。
2. 配置 State TTL(DataStream)
java
StateTtlConfig ttl = StateTtlConfig.newBuilder(Time.days(2))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.cleanupFullSnapshot() // 全量快照时清理
.cleanupIncrementally(1000, true) // 增量清理,降低尖刺
.build();
ValueStateDescriptor<Boolean> desc =
new ValueStateDescriptor<>("seen", Boolean.class);
desc.enableTimeToLive(ttl);要点:
UpdateType:读是否续期(读续期可能导致「永不淘汰」)NeverReturnExpired:业务看不到过期值- 清理策略要与 Checkpoint 模式匹配
3. Flink SQL 中的 TTL
sql
-- 表级状态 TTL(版本与语法以集群 Flink 为准)
ALTER TABLE digist SET (
'state.ttl' = '2 d'
);
-- 或在创建 TABLE / 配置中为闲置状态设置
-- table.exec.state.ttl = 2 d注意:SQL 维表 JOIN、去重算子底层状态都受影响;改 TTL 前确认对指标的误差可接受。
4. 监控状态体量(必做)
关注:
- Checkpoint 大小与时长、对齐时长
- RocksDB
sst总大小、TaskManager 磁盘 - 每算子 state 大小(Flink Metric / 自定义)
bash
# 示意:TM 本地 RocksDB 目录体量
du -sh /tmp/flink/rocksdb/* 2>/dev/null | sort -h | tail告警示例:Checkpoint size 七日滑动增长 > 30%,或单次 > 阈值,则分页查哪个算子。
5. 膨胀时的治理手段(组合拳)
- 加 / 收紧 TTL(优先)
- 缩 key 基数:更好的业务键、过滤测试流量、盐化仅用于计算不长期存
- 状态放到外部:大维表用 JDBC/HBase/Redis 旁路,而不是 Broadcast 无限长大
- 增量 Checkpoint + RocksDB(大状态标配)
- 拆作业:去重与窗口分离,避免一个巨状态作业
6. 变更与恢复注意
- 改 TTL 不一定自动缩小已有 RocksDB 文件,需清理策略实际跑一段时间或迁移状态
- Savepoint 兼容性:算子 uid 保持稳定
- 调小 TTL 会造成「一度去重过的 key 再现」→ 指标可能短期跳动,需公告
7. 与 Watermark / 窗口的边界
- 窗口状态存活应覆盖 窗口长度 + allowedLateness,TTL 过短会丢迟到更新
- 去重状态 TTL 应 ≥ 业务去重窗口;与 Watermark 迟到 分开配置、分开验收
- Checkpoint 间隔过长时,过期清理可见性会延迟——监控要用状态大小,不只靠「感觉」
java
// 算子 UID 固定,便于 Savepoint 升级后继续用状态
deduplicateUid("uv-dedup-device")
.keyBy(e -> e.getDeviceId())
.process(new DedupWithTtlFunction(Time.days(2)));8. 下线与缩容状态
- 长期 deprecated 的 key 空间:调短 TTL + 跑够一个全量 checkpoint 周期
- 必要时:从 Savepoint 剔除算子或迁移到新 uid 作业(有停写窗口)
- 禁止「删本地 RocksDB 目录」当常规缩容手段
常见坑(至少牢记这些)
不去设 TTL
演示环境没事,线上跑三个月 Checkpoint 爆炸。OnReadAndWrite 导致永不淘汰
热 key 一直被读,TTL 形同虚设。去重场景常用 OnCreateAndWrite。TTL 短于业务窗口
48h 去重却 TTL=6h,UV 虚高。TTL ≥ 业务语义窗口。只看堆内存,不看 RocksDB 盘
托管内存/磁盘打满同样挂 TM。用大状态硬扛维表
百万维变更 + 永久状态 = 运维噩梦。维表外置或 TTL + 定期全量刷新。
如何验收(可量化)
- [ ] 设计文档写清每类状态 TTL 与过期语义
- [ ] 线上 Metric:状态大小/Checkpoint 大小趋势可解释且有告警
- [ ] 压测或灰度:过期后重复 key 的行为符合预期(再计或丢弃)
- [ ] Checkpoint 时长回到 SLA(如 p95 < 1 min)
- [ ] 磁盘与托管内存无持续爬升
sql
-- 业务验收:TTL=2d 的日 UV 与离线 2 日去重对照(允许声明误差)
SELECT
rt.dt,
rt.uv AS rt_uv,
off.uv AS off_uv,
(rt.uv - off.uv) * 1.0 / NULLIF(off.uv, 0) AS rel_diff
FROM ads_uv_rt_1d rt
JOIN ads_uv_off_1d off ON rt.dt = off.dt
WHERE rt.dt = '${biz_date}';bash
# Checkpoint 验收
# 比较改 TTL 前后:checkpoint_size_gb、duration_sec
echo "before,after"相关面试题
占位:与题库小程序对齐后补链接(建议关联:Flink State TTL、状态后端、Checkpoint 体积、去重语义)。
改写说明:结构与判断标准参考 Wood 原创数仓/面试文,已改写成可执行清单;未包含未公开公司数据与内部表名。