以 Fury 为例,深入剖析多语言序列化框架所涉及的底层核心原理和技术,并结合核心金融工程服务进行探索和实践。
序列化已经成为分布式系统中频繁涉及的技术之一。在多个服务或组件之间,我们往往需要通过序列化对对象(Object)进行跨进程、跨节点、甚至跨语言的传输和持久化。因此,序列化框架的性能以及异构语言的支持能力将直接影响整个系统的性能和研发效率。
本系列将以 Fury (Chen 注:2024 年初,该项目更名为 Fory(GitHub: apache/fory),并已捐献给 Apache 基金会,现以 Apache Fory(孵化中)形式运营。文中所有 「Fury」均指现在的 Apache Fory,核心架构和 API 设计未发生根本性变化。)为例,深入剖析多语言序列化框架所涉及的底层核心原理和技术,并结合核心金融工程服务,进行探索和实践。系列文章主要分为以下几个部分:
序列化(Serialization)是将对象转化为字节流的过程,而反序列化(Deserialization)则是将字节流重新转化为对象的过程。传统的序列化方案在效率和安全性上存在一定的局限性,而在金融领域中,海量的交易数据、用户信息等需要进行高效的序列化和反序列化处理,因此我们迫切地需要一个高性能、高扩展性、安全可靠的新一代序列化解决方案。
通常,在分布式系统中进行数据交换时,我们需要将数据以某种格式进行编码,并在传输后解码为原始数据。静态序列化框架就是用来实现这个过程的。这些框架通常会提供一个接口和一个编解码器,使得使用者可以通过定义数据结构(类似于 C 语言的 struct)将其转换为指定的二进制格式。在传输数据之前,该二进制数据会被序列化并压缩。同样,我们需要在接收端对该二进制数据进行解码,还原为原始数据。
Protobuf、FlatBuffer 和 Thrift 都是流行的静态序列化框架,它们都能完成以上过程,但存在如下一些差异:
.fbs schema 文件定义数据结构,通过 flatc 编译器生成各语言代码,风格上与 Protobuf IDL 有所不同。FlatBuffer 则不需要 IDL,开发者使用 C++ 代码生成器手动编写数据结构。(Chen 注:FlatBuffer 有自己的 schema 定义语言(`.fbs` 文件),并非不需要 schema,只是没有采用 Protobuf/Thrift 风格的 IDL 命名。开发者仍需先编写 `.fbs` 文件,再通过 flatc 编译器生成各语言代码。)与静态序列化框架相对应的是动态序列化框架,它是一种能在运行时(Runtime)将对象转换为字节流并进行传输或持久化存储的工具。两者最大的区别在于,动态序列化框架不需要显式地定义数据结构,换言之,它不需要对 Schema 进行编译,而是根据对象的实际类型和数据进行编码和解码。
以下是几个常见的动态序列化框架:
选择动态序列化框架时需要考虑以下因素:跨语言支持、性能要求、序列化结果体积、可扩展性(是否支持复杂对象图、循环引用等特性)。
Fury 序列化框架是为大规模分布式系统和高性能应用场景而设计的。该框架利用了先进的序列化算法和优化策略,旨在提供快速、高效、安全的数据序列化和反序列化能力。与传统的序列化框架相比,Fury 具有以下几个显著特点:
金融业务具有高并发、大数据量、高安全性的特性,而 Fury 框架能够在保证数据完整性和安全性的前提下,提供快速的数据传输和处理能力,并且支持多种数据格式和数据类型的序列化,因此 Fury 框架能够很好地满足复杂金融业务的需求。

Fury 是一个基于 JIT 动态编译的多语言原生序列化框架,支持 Java/Python/Golang/C++ 等多种语言,提供全自动的对象多语言/跨语言序列化能力:
对于 Java 序列化:
writeObject/readObject/writeReplace/readResolve/readObjectNoData)。关于跨语言对象图序列化:
行存序列化:

Fury 针对 Java 语言实现了一套运行时代码生成(Runtime Codegen)框架,利用对象类型的泛型信息进行类型推断,并根据这些信息构建描述序列化代码逻辑的表达式树(Expression Tree)。在运行时,根据表达式树生成高效的 Java 代码,并通过 Janino 编译器将其编译成字节码。这些生成的代码可以加载到用户或者 Fury 创建的 ClassLoader。最后,通过 Java 的 JIT 编译器将代码编译为高效的汇编代码。整个过程可以描述为图 3 的流程图。

为了充分利用 JVM 的性能,Fury 还实现了一套优化器。该优化器将大方法递归拆分为小方法,确保 Fury 生成的所有代码都可以被编译和内联,从而尽可能压榨 JVM 的性能。
通过在运行时动态生成高效的序列化代码,并经过优化器的处理,Fury 能够减少虚方法调用、条件分支、哈希查找、元数据写入和内存读写等操作,大幅加速了序列化的性能。
根据目录结构,可以对 Fury 项目进行如下简要分析:
| 模块 | 说明 |
|---|---|
annotation | 用于注解的类和接口 |
builder | 构建器模式相关类 |
codegen | 代码生成相关的类 |
collection | 常用集合类型和工具类 |
exception | 自定义异常类 |
io | 文件读写等输入输出操作 |
memory | 内存管理相关类和功能 |
pool | 对象池,用于重复使用对象以提高性能 |
resolver | 解析相关类 |
serializer | 对象序列化和反序列化 |
type | 类型处理和转换 |
util | 工具类和辅助方法 |
Fury | 核心类,主要功能入口 |
FuryBuilder | 构建 Fury 对象的构建器类 |
Fury Java object graph serialization 示例:
ThreadSafeFury fury = Fury.builder().withLanguage(Language.JAVA)
// 允许反序列化未知类型的对象,更灵活
// 但如果类包含恶意代码,则可能不安全
.requireClassRegistration(false)
.buildThreadSafeFury();
byte[] bytes = fury.serialize(object);
fury.deserialize(bytes);buildThreadLocalFury 方法解析:
public ThreadLocalFury buildThreadLocalFury() {
this.finish();
ClassLoader loader = this.classLoader;
this.classLoader = null;
ThreadLocalFury threadSafeFury = new ThreadLocalFury((classLoader) -> {
return Fury.newFury(this, classLoader);
});
threadSafeFury.setClassLoader(loader);
return threadSafeFury;
}执行过程:
this.finish() — 为 ClassLoader 加载的类提供初始化和后续处理操作。classLoader,防止内存泄漏。ThreadLocalFury 实例,通过 lambda 表达式在每个线程中创建独立的 Fury 对象副本。序列化核心流程:
public byte[] serialize(Object obj) {
MemoryBuffer buffer = (MemoryBuffer)this.bufferLocal.get();
buffer.writerIndex(0);
((LoaderBinding)this.bindingThreadLocal.get()).get().serialize(buffer, obj);
return buffer.getBytes(0, buffer.writerIndex());
}执行过程:从线程本地变量获取 MemoryBuffer,重置写索引,通过 LoaderBinding 调用序列化方法,最后返回字节数组。
serialize(MemoryBuffer, Object, BufferCallback) 方法负责写入对象的元数据标志位(是否为 null、字节序、语言类型、是否有 callback),然后根据语言类型选择 Java 序列化路径(writeRef)或跨语言路径(xserializeInternal)。
writeRef 方法:
public void writeRef(MemoryBuffer buffer, Object obj) {
if (!this.refResolver.writeRefOrNull(buffer, obj)) {
ClassInfo classInfo = this.classResolver.getOrUpdateClassInfo(obj.getClass());
this.classResolver.writeClass(buffer, classInfo);
this.writeData(buffer, classInfo, obj);
}
}先通过 refResolver 判断对象是否已序列化过(处理共享引用),若未序列化则依次写入类信息和对象数据。
writeClass 方法根据类是否已注册(classId == 0)选择写入完整类名(包名 + 类名)或仅写入 2 字节的 classId,后者效率更高。
writeData 方法通过 switch 按 classId 路由到对应的写入方法,基本类型(Boolean、Byte、Char、Short、Int、Float、Long、Double、String)直接写入,整型和长整型支持可变长度压缩(varint);其他类型递增深度计数并调用对应序列化器的 write 方法。
xserializeInternal 方法用于非 Java 语言的序列化:先写入两个 -1 占位(对象大小和对象池索引),调用 xwriteRef 序列化对象,然后回填实际大小,最后遍历对象池中的 native 对象逐一序列化。
本文介绍了序列化框架的两种范式——静态框架(Protobuf、Thrift、FlatBuffer)和动态框架(JDK、Kryo、Hessian 等)的核心差异,并深入分析了 Fury 的架构和实现。
从源码视角看,Fury 的高性能来自三个核心机制:其一,运行时 JIT 代码生成(避免反射和泛型类型擦除的性能损耗);其二,引用解析器(refResolver)统一处理共享引用和循环引用;其三,基于 classId 的快速路由(writeData 的 switch 分支),绕过了大量元数据查找。
对于金融场景而言,Fury 的价值不只是「快」,更在于它能以最小迁移成本替换现有 JDK/Kryo 方案——无需修改业务代码,且兼容 JDK 自定义序列化协议,这对存量系统的升级改造尤为关键。
随着金融互联网行业的快速发展,对数据序列化和反序列化技术的要求将不断提升。Fury 序列化框架作为一种高性能、可扩展、安全可靠的解决方案,有着广阔的应用前景和市场需求。 Fury 在金融互联网应用中为金融业务的处理和传输提供了高效、安全的解决方案。随着金融互联网的持续发展,Fury 框架将在金融领域中发挥越来越重要的作用,推动金融行业实现更高效、便捷的数字化转型。(Chen 注:原文小结为通用套话,未提炼文章核心洞察,修订版本重写。)Language | 多语言支持 |
ThreadLocalFury | 线程局部变量版本 |
ThreadSafeFury | 线程安全版本 |