Apache Fory 配置参数详解
Apache Fory 1.7.6 配置参数详解:讲解 ForyBuilder 的语言、引用跟踪、兼容模式与 Schema 演进、数值字符串压缩、类注册、代码生成、缓冲限制与线程安全 Fory 等配置项取值与含义。
Apache Fory 配置参数详解
目标:系统梳理 Apache Fory 1.7.6 的
ForyBuilder配置项与取值含义,帮助在集成时按吞吐、字节体积与兼容性目标做出取舍。文中所有数值均来自真实运行验证。
一、配置入口与总览
1.1 配置入口
Apache Fory 通过 org.apache.fory.Fory 的静态工厂 Fory.builder() 获取 ForyBuilder,链式设置后调用终端方法构建实例:
Fory fory = Fory.builder()
.withLanguage(Language.JAVA)
.withRefTracking(true)
.requireClassRegistration(false)
.build();
ForyBuilder 提供了 40+ 个配置方法,可归纳为:语言与跨语言、引用处理、兼容模式、数值/字符串编码、类注册与安全、代码生成、缓冲与资源限制、线程模型八类。
1.2 关键终端方法
| 方法 | 返回 | 说明 |
|---|---|---|
build() | Fory | 构建普通实例,非线程安全,每线程独占一个 |
buildThreadSafeFory() | ThreadSafeFory | 构建线程安全实例,内部为线程本地 Fory 池 |
buildThreadLocalFory() | ThreadLocalFory | 基于 ThreadLocal 的实例 |
buildThreadSafeForyPool(int) | ThreadSafeFory | 带有界池大小的线程安全实例 |
二、语言与跨语言配置
2.1 withLanguage
| 取值 | 含义 |
|---|---|
Language.JAVA(默认) | 纯 Java 序列化模式,支持任意 POJO、引用、类继承,用于替换 JDK 原生序列化 |
Language.XLANG | 跨语言模式,数据可被 Python/C++/Go 等实现读取,字段必须可空性显式标注 |
withXlang(boolean) 是 withLanguage 的等价布尔开关:withXlang(true) 等价 withLanguage(Language.XLANG)。
2.2 跨语言的类型命名
跨语言模式下不写 Java 全限定类名,而写注册时给定的类型名:
Fory fory = Fory.builder().withLanguage(Language.XLANG).build();
fory.register(Order.class, "example.Order");
实测:同一对象 XLANG + register(Data,"example.Data") 序列化 71 字节(Java 模式注册为 75 字节)。
注意:XLANG 模式下引用类型字段默认非空,字段为
null时会抛IllegalArgumentException: Non-nullable field ... is null,需给字段加@Nullable或在 Java 模式(withXlang(false))下使用。
三、引用跟踪与对象拷贝
3.1 withRefTracking
控制是否跟踪对象引用,以正确还原循环引用与共享引用(同一实例被多处引用)。
| 取值 | 含义 |
|---|---|
true | 开启引用跟踪,可处理循环引用,共享对象只写一次;体积略增 |
false(默认) | 不跟踪,遇到循环引用会无限递归 |
实测:默认配置(未显式开启)序列化一个自引用闭环 x.next=y; y.next=x 时抛异常——
DEFAULT refTracking: THROW SerializationException cause=StackOverflowError
refTracking(false): THROW SerializationException cause=StackOverflowError
开启后:
---- Case3 circular reference ----
a.id=A, a.next.id=B, a.next.next.id=A
identity preserved (a2.next.next == a2): true
Fory bytes : 54
即 withRefTracking(true) 后,闭环对象成功往返且引用同一性保持。
3.2 相关方法
| 方法 | 取值 | 含义 |
|---|---|---|
withRefCopy(boolean) | true/false | 拷贝对象时是否保留引用结构(copy API 相关) |
ignoreStringRef(boolean) | true/false | 忽略字符串实例的引用跟踪,减小体积 |
ignoreTimeRef(boolean) | true/false | 忽略时间类型的引用跟踪 |
四、兼容模式与 Schema 演进
4.1 withCompatibleMode
控制对类结构变化(增删字段、改类型)的容忍度。
| 取值 | 含义 |
|---|---|
CompatibleMode.COMPATIBLE | 兼容模式,写入/读取端字段可增删,支持 Schema 演进;会携带字段元信息,体积略大 |
CompatibleMode.SCHEMA_CONSISTENT | 强一致模式,两端结构必须一致;体积最小、性能最高 |
实测(同一对象):
CompatibleMode.COMPATIBLE : 94
CompatibleMode.SCHEMA_CONSISTENT : 66
withCompatible(boolean) 为等价布尔形式。此例中未显式设置的默认构建结果为 94 字节,与 COMPATIBLE 一致;SCHEMA_CONSISTENT 因省去字段元信息降到 66 字节,但不保证结构变化时的兼容性。
4.2 元信息共享
| 方法 | 含义 |
|---|---|
withMetaShare(boolean) | 在兼容模式下共享类元信息,跨多次序列化摊销元数据开销 |
withScopedMetaShare(boolean) | 以作用域为单位共享元信息,降低共享字典的耦合 |
withMetaCompressor(MetaCompressor) | 自定义元信息压缩器 |
五、数值与字符串编码
5.1 压缩开关
| 方法 | 默认 | 含义 |
|---|---|---|
withIntCompressed(boolean) | true | 使用变长(varint)编码 int,小数值占更少字节 |
withLongCompressed(boolean) | true | 同上,作用于 long;也可 withLongCompressed(Int64Encoding) 指定编码 |
withStringCompressed(boolean) | true | 字符串使用紧凑编码 |
withNumberCompressed(boolean) | true | 一次性开启数值压缩总开关 |
withIntArrayCompressed(boolean) | — | int[] 元素压缩 |
withLongArrayCompressed(boolean) | — | long[] 元素压缩 |
withWriteNumUtf16BytesForUtf8Encoding(boolean) | — | UTF-8 编码时是否写入 UTF-16 字节数(跨语言交互相关) |
实测(含大 int n=123456789、long l=9876543210):
JAVA default : 94
withIntCompressed(false) : 109
withLongCompressed(false) : 93
withStringCompressed(false) : 94
可见关闭 int 压缩后体积由 94 增至 109;long 在本例取值下固定长度编码反而略小(93),说明压缩收益与数值分布相关。
六、类注册与安全
6.1 requireClassRegistration
| 取值 | 含义 |
|---|---|
true(默认) | 未注册的类序列化时抛 InsecureException,防止反序列化漏洞 |
false | 跳过注册校验;仅在环境完全可信时使用 |
实测默认行为:
[no-register] THROW org.apache.fory.exception.InsecureException: class com.example.ForyDemo$User is not registered, ... If your env is 100% secure, you can also avoid this exception by disabling class registration check using `ForyBuilder#requireClassRegistration(false)`
6.2 register 系列
| 方法 | 含义 |
|---|---|
register(Class) | 注册类,序列化时省略类名,体积更小、更安全 |
register(Class, int id) | 用数字 id 注册,进一步压缩 |
register(Class, String typeName) | 以字符串类型名注册(跨语言常用) |
实测注册后体积下降:
JAVA default (未注册,关闭校验) : 94
register(Data.class) : 75
6.3 其他安全/检查项
| 方法 | 含义 |
|---|---|
suppressClassRegistrationWarnings(boolean) | 抑制未注册类的告警日志 |
withTypeChecker(TypeChecker) | 自定义反序列化类型检查器(白/黑名单) |
withClassVersionCheck(boolean) | 是否校验类版本一致性 |
withJdkClassSerializableCheck(boolean) | 是否校验 JDK 类可序列化 |
七、代码生成与异步编译
7.1 withCodegen
| 取值 | 含义 |
|---|---|
true(默认) | 为 POJO 生成 JIT 序列化代码,吞吐最高 |
false | 关闭代码生成,改用反射路径,便于受限环境或调试 |
实测 withCodegen(false) 的序列化字节为 94,与开启时一致——代码生成只影响性能,不影响字节内容。
7.2 withAsyncCompilation
withAsyncCompilation(true) 让首次序列化先走解释执行、后台异步编译 JIT 代码,避免首次调用被编译阻塞。实测字节仍为 94。
八、缓冲与资源限制
| 方法 | 取值示例 | 含义 |
|---|---|---|
withBufferSizeLimitBytes(int) | 正整数 | 单次序列化缓冲上限,防止超大数据占满内存 |
withMaxDepth(int) | 正整数 | 对象图最大深度,防深递归 |
withMaxTypeFields(int) | 正整数 | 单类型最大字段数 |
withMaxTypeMetaBytes(int) | 正整数 | 单类型元信息最大字节数 |
withMaxSchemaVersionsPerType(int) | 正整数 | 单类型最多缓存多少个 schema 版本 |
withMaxGraphMemoryBytes(long) | 正长整 | 对象图最大内存占用 |
withMaxUnbackedContainerItems(int) | 正整数 | 无后备集合载体最大元素数 |
withMapRefLoadFactor(float) | 0~1 | 引用映射的负载因子 |
这些限制用于在不可信或超大对象场景下防止内存耗尽与反序列化攻击。
九、类加载与命名
| 方法 | 含义 |
|---|---|
withClassLoader(ClassLoader) | 指定反序列化时使用的类加载器 |
withSharedRegistry(SharedRegistry) | 复用注册表,多实例间共享类型注册 |
withName(String) | 为 Fory 实例命名,便于日志/调试定位 |
withModule(ForyModule) | 注册自定义模块 |
十、线程模型
| 终端方法 | 适用场景 |
|---|---|
build() → Fory | 单线程或每线程独享;不要跨线程共享 |
buildThreadSafeFory() → ThreadSafeFory | 多线程共享,内部维护线程本地实例 |
buildThreadLocalFory() → ThreadLocalFory | 明确基于 ThreadLocal 的封装 |
buildThreadSafeForyPool(int) | 有界线程池版,控制并发实例上限 |
实测 buildThreadSafeFory() 往返对象字节为 94,与 build() 一致。
十一、真实字节数对照实验
在一台 Ubuntu 22.04(OpenJDK 17)上,使用同一对象(Data{int n=123456789; long l=9876543210L; String s="fory-config-demo"; List<Integer>(5)})运行配置对照,真实回显:
JAVA default : 94
withRefTracking(true) : 94
withIntCompressed(false) : 109
withLongCompressed(false) : 93
withStringCompressed(false) : 94
CompatibleMode.COMPATIBLE : 94
CompatibleMode.SCHEMA_CONSISTENT : 66
withCodegen(false) : 94
withAsyncCompilation(true) : 94
register(Data.class) : 75
XLANG + register(Data,"example.Data"): 71
buildThreadSafeFory() bytes : 94
结论:
- 影响字节体积的主要是:
withCompatibleMode、withIntCompressed/withLongCompressed、是否register; - 影响行为正确性的是:
withRefTracking(循环引用必需)、requireClassRegistration(安全); - 只影响性能的是:
withCodegen、withAsyncCompilation。
十二、常见问题
Q1:默认配置为什么在循环引用上失败?
Java 模式默认 withRefTracking(false)。存在循环引用或共享引用时,请显式 withRefTracking(true),否则会因无限递归抛 StackOverflowError。
Q2:withCompatibleMode 该选哪个?
跨版本、字段会演进的长期存储/消息场景选 COMPATIBLE;两端代码同步发布、追求最小体积与最高性能的 RPC 内部调用可选 SCHEMA_CONSISTENT。
Q3:为什么开启 withCodegen(false) 后字节数不变?
代码生成只改变序列化执行的实现路径(生成字节码 vs 反射),不改变线格式,故字节数一致,仅性能不同。
Q4:跨语言模式报 Non-nullable field ... is null?
XLANG 模式引用字段默认非空。请为可空字段加 @Nullable,或改用 Java 模式。
Q5:requireClassRegistration(true) 下如何兼顾性能与安全?
保持默认 true,并对业务类逐一 register。注册后因省略类名,字节反而更小(实测 94 → 75)。