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