一文讲透 Java 中transient的用处(结合 Flink 理解)
文章目录
- 一、transient 是干什么的?
- 二、Java 默认的序列化规则
- 三、如果不加 transient 会怎样?
- 四、Flink 中 State 的真实存储位置
- 五、为什么 Flink State 一定要 transient?
- 六、哪些字段该加 transient?哪些不该?
- 七、常见误区
- 八、推荐的标准写法模板
- 九、总结
在 Java / Flink 开发中,我们经常看到字段前面加了一个transient,
但很多人只知道“照着写”,并不清楚它到底解决了什么问题。
本文从 Java 序列化原理 出发,再结合 Flink State 的真实运行模型,一次性把transient讲清楚。
一、transient 是干什么的?
一句话定义:
transient 用来告诉 Java:这个字段不要参与对象的序列化与反序列化。classUserimplementsSerializable{String name;transientString password;}序列化之后:
name会被保存password会被忽略,反序列化后为null
二、Java 默认的序列化规则
在 Java 中,只要满足以下条件:
- 类实现了
Serializable - 字段 不是
static - 字段 不是
transient
👉 那么这个字段就会被 自动序列化。
也就是说:
privateValueState<LastPoint> lastPoint;在 Java 看来,这只是一个普通成员变量,会跟着对象一起被序列化。
三、如果不加 transient 会怎样?
1️⃣ 普通 Java 场景
ObjectOutputStream oos =newObjectOutputStream(...); oos.writeObject(obj);transient字段不会被写入- 非
transient字段会被写入
这是 Java 层面的规则。
2️⃣ 在 Flink 中的隐蔽问题(重点)
Flink 在以下场景中,会涉及算子对象的序列化:
- 作业下发(JobManager → TaskManager)
- Task 重启 / failover
- checkpoint / savepoint
- 扩缩容(rescale)
如果你不加 transient:
privateValueState<LastPoint> lastPoint;Java 会尝试序列化这个 State 句柄对象,而这通常会导致:
NotSerializableException- 作业启动失败
- checkpoint 异常
- failover 后状态错乱
📌 很多问题不是立刻出现,而是线上才爆
四、Flink 中 State 的真实存储位置
很多人以为:
ValueState 是存在这个字段里的
这是 错误的。
真实结构是:
Operator 对象(Java) └── transient ValueState handle(句柄) ↓ StateBackend(RocksDB / 内存) └── 真正的状态数据 也就是说:
ValueState / ListState / MapState
👉 只是一个“访问入口(handle)”- 真正的数据由 Flink StateBackend 管理
五、为什么 Flink State 一定要 transient?
原因一:State 不是业务数据
State:
- 不是你对象的一部分
- 不该由 Java 序列化
- 不该跟着对象“复制”
它的生命周期由 Flink 运行时 管理,而不是 JVM。
原因二:State 需要在运行时重新绑定
Flink 的正确流程是:
new Operator() ↓ open() ↓ getRuntimeContext().getState(...) 而不是:
反序列化旧对象里的 state 所以 State 字段必须:
transient- 在
open()中初始化
六、哪些字段该加 transient?哪些不该?
✅ 必须 / 强烈建议加 transient
ValueStateListStateMapStateReducingState- 运行时句柄(Metric、client、连接等)
privatetransientValueState<A> stateA;❌ 不要加 transient
- 配置类
- 常量
- 业务规则
- 普通 POJO 字段
privatefinalTrajConfig cfg;这些字段:
- 需要被序列化
- 需要在每个 task 中保持一致
七、常见误区
❌ 误区 1:不加 transient 也能跑
是的,但只是在:
- 本地模式
- 未开启 checkpoint
- 未发生 failover
👉 这是延迟爆炸型 Bug
❌ 误区 2:在构造函数里初始化 State
publicMyFn(){ lastPoint =getRuntimeContext().getState(...);// ❌}原因:
- 构造阶段还没有 RuntimeContext
- state 绑定信息还不存在
八、推荐的标准写法模板
publicclassMyProcessFunctionextendsKeyedProcessFunction<K, IN, OUT>{privatefinalConfig cfg;privatetransientValueState<A> stateA;privatetransientListState<B> buffer;privatetransientMapState<Long,Boolean> dedup;@Overridepublicvoidopen(Configuration parameters){ stateA =getRuntimeContext().getState(newValueStateDescriptor<>("stateA",A.class)); buffer =getRuntimeContext().getListState(newListStateDescriptor<>("buffer",B.class)); dedup =getRuntimeContext().getMapState(newMapStateDescriptor<>("dedup",Long.class,Boolean.class));}}九、总结
transient的本质作用:
阻止 Java 默认序列化,
让运行时资源交给运行时系统(如 Flink)管理。
在 Flink 中可以再加一句:
State 的生命周期属于 Flink,不属于 Java 对象。