4.3 Savepoint 与作业升级:带着状态搬家


4.3 Savepoint 与作业升级:带着状态搬家

本节摘要:作业改了逻辑要发新版,跑了半年的状态怎么办?savepoint 是答案:一次全量、格式稳定、专为"搬家"设计的快照。本节讲清 checkpoint 与 savepoint 的分工、算子 UID 在状态映射中的关键作用、状态不兼容的典型场景与迁移手法,并给出一套可直接套用的升级流程。

发布日的固定戏码

每个维护过生产作业的人都知道那套流程:业务要改统计口径,代码改完了,测试环境验证通过,然后问题来了——直接重启部署,作业从零开始积累状态,去重要重新记、窗口要从头攒,几小时的业务数据被算乱。带着状态升级,靠的是 savepoint:人为触发的一次完整快照,格式跨版本稳定,专为"停下、搬家、接着跑"设计。本节是第 4 章从理论走向工程实务的一站。

Checkpoint 与 Savepoint 的分工

两者技术上同源(都是分布式快照),定位却泾渭分明。checkpoint 是自动心跳:周期触发、追求轻快、可配置增量、格式允许随引擎版本演进优化,用途是故障恢复。savepoint 是人工手术:命令触发、强制全量、二进制格式长期稳定、允许"恢复时跳过未匹配的状态",用途是版本迁移、集群搬迁、扩缩容重平行化。

一句话记分工:checkpoint 防"天灾",savepoint 应"人祸"。天灾不可预期所以要快,人祸可以规划所以求稳。这也解释了为什么生产作业要用 savepoint 做定期归档:万一新版本上线发现严重问题要回滚,最近一次 savepoint 就是退路——回滚也是一次"搬家"。

算子 UID:状态对门的钥匙

savepoint 恢复时,引擎要回答一个匹配问题:旧作业的状态数据,该交给新作业里的哪个算子?匹配的钥匙是算子 UID。每个算子在快照里以 UID 登记,恢复时新作业按 UID 对号入座。由此推出生产开发的铁律:给每个关键算子显式指定 UID

不显式指定的后果是 UID 由拓扑结构自动生成——你只是加了一个 map 算子,后续所有算子的自动 UID 全部变样,savepoint 恢复时通通对不上号,状态整卷作废。这类事故的特点是:测试环境一切正常(没有旧状态可恢复),一到生产升级日爆雷。所以 UID 要在第一次上线时就写进代码,而不是出事后补:

SingleOutputStreamOperator<Stat> stats = payments .keyBy(Payment::getShopId) .window(SlidingEventTimeWindows.of(Time.hours(1), Time.minutes(1))) .aggregate(new AmountAgg()) .uid("agg-window-shop-stat") // 稳定的身份号:跨版本状态映射的锚点 .name("shop-stat");

状态不兼容的三种典型与处置

升级时最怕"恢复报错:状态类型不兼容"。常见三种情形。情形一:类型变了——把 ValueState 的值类型从整数改成字符串,旧数据无法解读。处置:换一个新的状态名(描述符改名),旧状态自然废弃,另写初始化逻辑重建;或者接受从 savepoint 里丢弃该状态重新积累。情形二:Pojo 结构加字段——通常兼容,新字段取默认值;删字段或改字段类型则视序列化器而定,升级前务必在测试环境用旧 savepoint 演练一次。情形三:算子并行度变了——键控状态按 key 哈希分布,并行度改变时状态会被重新洗牌分配,savepoint 恢复天然支持;这恰是 savepoint 相对 checkpoint 的一个独门用途(扩容不丢状态)。

一套可直接套用的升级流程

流程里有三个容易忽略的检查点。其一,触发停止时用"停止并保存"而不是"粗暴取消",前者等快照写完才退出,返回的快照路径是唯一有效的恢复凭证。其二,新作业启动参数里显式带上快照路径与"允许未恢复状态"的取舍:默认遇到对不上的状态会拒绝启动(防止静默丢状态),确认作废才放开。其三,恢复后别急着宣布成功——观察第一个完整 checkpoint 周期是否正常、抽样核对几条指标与旧版衔接,连续才是升级完成的标志。

💡 关键直觉:savepoint 的成本高在"全量",所以它不替代心跳,只守护变更。变更日之外的稳定性,永远交给 checkpoint;把两者混用的团队,要么升级慢如蜗牛,要么心跳重得喘不过气。

并行度扩容:savepoint 的独门戏法

状态不兼容的三情形之外,savepoint 还有一项 checkpoint 不具备的独门用途:带着状态改并行度。业务量翻倍后想给聚合算子扩容,直接改并行度重启会让键控状态按新并行度重新哈希分布——checkpoint 恢复虽也能做键的重分布,但日常心跳恢复并不触发重分配逻辑;而 savepoint 恢复原生支持"以新并行度读旧状态",键控状态会按键重新洗牌,算子状态则按列表切分或联合分配的策略处理。扩容的标准动作因此定型:先停并保存快照,再用新并行度从快照启动。整个过程业务无感,状态一个不丢。这也解释了为什么 6.1 节说 Kubernetes Operator 的升级参数默认配 savepoint 模式——扩缩容与版本升级共用同一条"带状态搬家"的通道。

本节要点

  • checkpoint 防天灾(自动、增量、求快),savepoint 应人祸(手动、全量、格式稳定),分工不可互换。
  • 算子 UID 是状态映射的锚点,必须在首次上线时就显式指定,拓扑一变自动 UID 全体失联。
  • 状态不兼容三情形:类型变更(换状态名重建)、结构加字段(一般兼容、演练为准)、并行度变化(savepoint 原生支持重洗牌)。
  • 升级流程三检查:等快照写完拿路径、显式带路径启动、恢复后跑满一个 checkpoint 周期再对账宣布成功。

心脏的肌肉、心跳、手术体系都讲完了。最后一站进急诊科:当 checkpoint 连续失败,如何按症状找到病根——那是把本章知识兑现成半夜里的处置能力。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U