Schema 演化
本页介绍 schema 演化、元数据共享以及处理不存在的类。
处理类 Schema 演化
在许多系统中,用于序列化的类的 schema 可能会随时间而改变。例如,类中的字段可能会被添加或删除。当序列化和反序列化过程使用不同版本的 jar 时,被反序列化的类的 schema 可能与序列化期间使用的不同。
默认模式:SCHEMA_CONSISTENT
默认情况下,Fory 使用 CompatibleMode.SCHEMA_CONSISTENT 模式序列化对象。此模式假设反序列化过程使用与序列化过程相同的类 schema,从而最小化有效负载开销。但是,如果存在 schema 不一致,反序列化将失败。
兼容模式
如果预期 schema 会发生变化,为了使反序列化成功(即 schema 前向/后向兼容性),用户必须配置 Fory 使用 CompatibleMode.COMPATIBLE。这可以使用 ForyBuilder#withCompatibleMode(CompatibleMode.COMPATIBLE) 方法完成。
在此兼容模式下,反序列化可以处理 schema 变更,如缺失或额外的字段,允许在序列化和反序列化过程具有不同类 schema 时也能成功。
Fory fory = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE)
.build();
byte[] bytes = fory.serialize(object);
System.out.println(fory.deserialize(bytes));
此兼容模式涉及将类元数据序列化到序列化输出中。尽管 Fory 使用复杂的压缩技术来最小化开销,但与类元数据相关联的仍有一些额外的空间成本。
元数据共享
为了进一步降低元数据成本,Fory 引入了类元数据共享机制,允许元数据只发送到反序列化过程一次。
Fory 支持在上下文(例如 TCP 连接)中的多次序列化之间共享类型元数据(类名、字段名、最终字段类型信息等)。此信息将在上下文中的第一次序列化期间发送到对等端。基于此元数据,对等端可以重建相同的反序列化器,这避免了为后续序列化传输元数据,并降低网络流量压力,同时自动支持类型前向/后向兼容性。
使用元数据共享
// Fory.builder()
// .withLanguage(Language.JAVA)
// .withRefTracking(false)
// // 在多次序列化之间共享元数据。
// .withMetaContextShare(true)
// 非线程安全 fory。
MetaContext context = xxx;
fory.getSerializationContext().setMetaContext(context);
byte[] bytes = fory.serialize(o);
// 非线程安全 fory。
MetaContext context = xxx;
fory.getSerializationContext().setMetaContext(context);
fory.deserialize(bytes);
线程安全元数据共享
// 线程安全 fory
fory.setClassLoader(beanA.getClass().getClassLoader());
byte[] serialized = fory.execute(
f -> {
f.getSerializationContext().setMetaContext(context);
return f.serialize(beanA);
}
);
// 线程安全 fory
fory.setClassLoader(beanA.getClass().getClassLoader());
Object newObj = fory.execute(
f -> {
f.getSerializationContext().setMetaContext(context);
return f.deserialize(serialized);
}
);
注意:MetaContext 不是线程安全的,不能在 Fory 实例或多个线程之间重用。在多线程情况下,必须为每个 Fory 实例创建单独的 MetaContext。
有关更多详细信息,请参阅 元数据共享规范。
反序列化不存在的类
Fory 支持反序列化不存在的类。此功能可以通过 ForyBuilder#deserializeNonexistentClass(true) 启用。
启用时,如果启用了元数据共享,Fory 将把此类型的反序列化数据存储在 Map 的延迟子类中。通过使用 Fory 实现的延迟映射,可以避免反序列化期间填充映射的重新平衡成本,从而进一步提高性能。
如果此数据被发送到另一个进程,并且该类在此过程中存在,则数据将被反序列化为此类型的对象,而不会丢失任何信息。
如果未启用元数据共享,新类数据将被跳过,并返回 NonexistentSkipClass 存根对象。
将对象从一种类型复制/映射到另一种类型
Fory 支持将对象从一种类型映射到另一种类型。
注意:
- 此映射将执行深拷贝。所有映射的字段都被序列化为二进制,并从该二进制反序列化以映射到另一种类型。
- 所有结构体类型必须使用相同的 ID 注册,否则 Fory 无法映射到正确的结构体类型。使用
Fory#register(Class)时要小心,因为 Fory 会分配一个自动增长的 ID,如果你在 Fory 实例之间以不同的顺序注册类,这可能会不一致。
public class StructMappingExample {
static class Struct1 {
int f1;
String f2;
public Struct1(int f1, String f2) {
this.f1 = f1;
this.f2 = f2;
}
}
static class Struct2 {
int f1;
String f2;
double f3;
}
static ThreadSafeFory fory1 = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory();
static ThreadSafeFory fory2 = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory();
static {
fory1.register(Struct1.class);
fory2.register(Struct2.class);
}
public static void main(String[] args) {
Struct1 struct1 = new Struct1(10, "abc");
Struct2 struct2 = (Struct2) fory2.deserialize(fory1.serialize(struct1));
Assert.assertEquals(struct2.f1, struct1.f1);
Assert.assertEquals(struct2.f2, struct1.f2);
struct1 = (Struct1) fory1.deserialize(fory2.serialize(struct2));
Assert.assertEquals(struct1.f1, struct2.f1);
Assert.assertEquals(struct1.f2, struct2.f2);
}
}
将 POJO 反序列化为另一种类型
Fory 允许你序列化一个 POJO 并将其反序列化为另一个 POJO。不同的 POJO 意味着 schema 不一致。用户必须将 Fory 配置为 CompatibleMode 设置为 org.apache.fory.config.CompatibleMode.COMPATIBLE。
public class DeserializeIntoType {
static class Struct1 {
int f1;
String f2;
public Struct1(int f1, String f2) {
this.f1 = f1;
this.f2 = f2;
}
}
static class Struct2 {
int f1;
String f2;
double f3;
}
static ThreadSafeFory fory = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory();
public static void main(String[] args) {
Struct1 struct1 = new Struct1(10, "abc");
byte[] data = fory.serializeJavaObject(struct1);
Struct2 struct2 = (Struct2) fory.deserializeJavaObject(bytes, Struct2.class);
}
}
配置选项
| 选项 | 描述 | 默认值 |
|---|---|---|
compatibleMode | SCHEMA_CONSISTENT 或 COMPATIBLE | SCHEMA_CONSISTENT |
checkClassVersion | 检查类 schema 一致性 | false |
metaShareEnabled | 启用元数据共享 | 如果是兼容模式则为 true |
scopedMetaShareEnabled | 每次序列化的作用域元数据共享 | 如果是兼容模式则为 true |
deserializeNonexistentClass | 处理不存在的类 | 如果是兼容模式则为 true |
metaCompressor | 元数据压缩的压缩器 | DeflaterMetaCompressor |
最佳实践
- 对演化的 schema 使用 COMPATIBLE 模式:当类可能在版本之间更改时
- 为网络通信启用元数据共享:减少重复序列化的带宽
- 对结构体映射使用一致的类型 ID:确保相同的注册顺序或显式 ID
- 考虑空间开销:兼容模式会添加元数据,根据需求平衡