Fury序列化快速入门

安装

官方的 Apache Fury 发行版以源代码形式提供。 要下载源代码,请参见 Fury 下载页面。

使用

通过maven添加Fury依赖:

 

xml

代码解读

复制代码

<dependency> <groupId>org.apache.fury</groupId> <artifactId>fury-core</artifactId> <version>0.7.0</version> </dependency> <!-- row/arrow format support --> <!-- <dependency> <groupId>org.apache.fury</groupId> <artifactId>fury-format</artifactId> <version>0.7.0</version> </dependency> -->

介绍

简介

Fury 是一个极其快速的多语言序列化框架,通过即时编译(JIT)和零拷贝实现。

协议

不同的场景有不同的序列化需求。Fury 针对这些需求设计并实现了多种二进制协议: 跨语言对象图协议:

  • 自动跨语言序列化任何对象,无需IDL定义、模式编译和对象与协议之间的转换。
  • 支持共享引用和循环引用,无重复数据或递归错误。
  • 支持对象多态性。

本地 Java/Python 对象图协议:基于语言的类型系统进行高度优化。 行格式协议:一种缓存友好的二进制随机访问格式,支持跳过序列化和部分序列化,并且可以自动转换为列格式。 新的协议可以基于 Fury 现有的缓冲区、编码、元数据、代码生成等能力轻松添加。所有这些协议共享相同的代码库,对某一协议的优化可以被另一协议重用。

兼容性

模式兼容性

Fury 的 Java 对象图序列化支持类模式的前向/后向兼容性。序列化端和反序列化端可以独立添加/删除字段。 在完成元数据压缩后,我们计划添加对跨语言序列化的支持。

二进制兼容性

我们仍在改进我们的协议,目前 Fury 版本之间不确保二进制兼容性。如果将来需要升级 Fury,请对 Fury 进行着色处理。 在 Fury 1.0 之前将确保二进制兼容性。

安全性

静态序列化(如行格式)本质上是安全的。但动态对象图序列化支持反序列化未注册类型,可能引入安全风险。 例如,反序列化可能会调用初始化构造函数或equals/hashCode 方法,如果方法体包含恶意代码,系统将面临风险。 Fury 为此协议提供了类注册模式选项,并默认启用,仅允许反序列化可信的注册类型或内置类型以确保安全。 Fury 提供了类注册选项,并默认启用此类协议,仅允许反序列化可信的注册类型或内置类型。除非能够确保环境确实安全,否则不要禁用类注册或类注册检查。如果禁用了类注册选项,我们不对安全性负责。

路线图

  • 元数据压缩、自动元数据共享和跨语言模式兼容性。
  • C++/Golang 的 AOT 框架以静态生成代码。
  • 支持 C++/Rust 对象图序列化
  • 支持 Golang/Rust/NodeJS 行格式
  • 支持 ProtoBuffer 兼容性
  • 支持特征和知识图序列化的协议
  • 不断改进我们的序列化基础设施以支持任何新协议

基准测试

不同的序列化框架适用于不同的场景,以下基准测试结果仅供参考。 如果需要针对特定场景进行基准测试,请确保所有序列化框架都为该场景进行了适当配置。 动态序列化框架支持多态性和引用,与静态序列化框架相比,成本更高,除非像 Fury 那样使用 JIT 技术。由于 Fury 会在运行时生成代码,请在收集基准测试数据之前进行预热。

整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

java序列化

 

 

 

java反序列化

 

 

 

 有关类型前向/后向兼容性、堆外支持、零拷贝序列化的更多基准测试,请参见基准测试。

JavaScript

 用于此条形图的数据包括一个具有多种字段类型的复杂对象,JSON 数据的大小为 3KB。

特性

多语言支持:Java/Python/C++/Golang/Javascript/Rust。 零拷贝:受 Pickle5 启发的跨语言带外序列化和堆外读写。 高性能:一个高度可扩展的 JIT 框架,在运行时以异步多线程方式生成序列化器代码,加速序列化,提供 20-170 倍的速度提升,通过:

  • 在生成的代码中内联变量,减少内存访问。
  • 在生成的代码中内联调用,减少虚方法调用。
  • 减少条件分支。
  • 减少哈希查找。
  • 二进制协议:对象图、行格式等。

除了跨语言序列化外,Fury 还具备以下特点:

  • 无需修改任何代码,即可替换 Java 序列化框架,如 JDK/Kryo/Hessian,但速度快 100 倍。可以大大提高高性能 RPC 调用、数据传输和对象持久化的效率。
  • 100% 兼容 JDK 序列化,原生支持 Java 自定义序列化方法 writeObject/readObject/writeReplace/readResolve/readObjectNoData。
  • 支持 Golang 的共享和循环引用对象序列化。
  • 支持 Golang 的自动对象序列化。

指导

java序列化指导

java对象图序列化

当只需要 Java 对象序列化时,与跨语言对象图序列化相比,此模式将具有更好的性能。

快速开始

请注意,创建 Fury 并不便宜,Fury 实例应在序列化之间重用,而不是每次都创建它。你应该将 Fury 保持为静态全局变量,或单例对象或有限对象的实例变量。

fury单线程的用法
 

java

代码解读

复制代码

import org.apache.fury.Fury; import org.apache.fury.config.Language; public static void main(String[] args) { SomeClass object = new SomeClass(); // 请注意,Fury 实例应在多个不同对象的序列化之间重用。 Fury fury = Fury.builder().withLanguage(Language.JAVA) // 允许反序列化未知类型的对象, // 更灵活但安全性较低。 // .withSecureMode(false) .build(); // 注册类型可以减少类名序列化开销,但不是必须的。 // 如果启用了安全模式,则所有自定义类型都必须注册。 fury.register(SomeClass.class); byte[] bytes = fury.serialize(object); System.out.println(fury.deserialize(bytes)); } }

fury多线程的用法
 

java

代码解读

复制代码

import com.study.cache.client.serialization.SomeClass; import org.apache.fury.*; import org.apache.fury.config.*; public class Example { public static void main(String[] args) { SomeClass object = new SomeClass(); //请注意,Fury 实例应在多个不同对象的序列化之间重用。 ThreadSafeFury fury = new ThreadLocalFury(classLoader -> { Fury f = Fury.builder().withLanguage(Language.JAVA) .withClassLoader(classLoader).build(); f.register(SomeClass.class); return f; }); byte[] bytes = fury.serialize(object); System.out.println(fury.deserialize(bytes)); } }

ThreadSafeFury 线程安全的序列化器接口。Fury 不是线程安全的,该接口的实现将是线程安全的,并且支持动态切换类加载器。

Fury实例重用实例
 

java

代码解读

复制代码

import com.study.cache.client.serialization.SomeClass; import org.apache.fury.*; import org.apache.fury.config.*; public class Example { // 重用fury private static final ThreadSafeFury fury = new ThreadLocalFury(classLoader -> { Fury f = Fury.builder().withLanguage(Language.JAVA) .withClassLoader(classLoader).build(); f.register(SomeClass.class); return f; }); public static void main(String[] args) { SomeClass object = new SomeClass(); byte[] bytes = fury.serialize(object); System.out.println(fury.deserialize(bytes)); } }

FuryBuilder 选项

选型名称描述默认值
timeRefIgnored当启用了引用跟踪时,是否忽略在 TimeSerializers和这些类型的子类中注册的所有时间类型的引用跟踪。如果忽略,可以通过调用 Fury#registerSerializer(Class, Serializer) 启用每种时间类型的引用跟踪。例如,fury.registerSerializer(Date.class, new DateSerializer(fury, true))。注意,在任何包含时间字段的类型的序列化器代码生成之前,应该启用引用跟踪,否则这些字段将仍然跳过引用跟踪。true
compressInt启用或禁用 int 压缩以减少大小。true
compressLong启用或禁用 long 压缩以减少大小。true
compressString启用或禁用字符串压缩以减少大小。true
classLoader类加载器不应更新;Fury缓存类的元数据。使用 LoaderBinding 或 ThreadSafeFury 来更新类加载器。Thread.currentThread().getContextClassLoader()
compatibleMode类型前向/后向兼容性配置。还与 checkClassVersion 配置相关。SCHEMA_CONSISTENT:类模式在序列化端和反序列化端之间必须一致。COMPATIBLE:类模式在序列化端和反序列化端之间可以不同。他们可以独立添加/删除字段。CompatibleMode.SCHEMA_CONSISTENT
checkClassVersion确定是否检查类模式的一致性。如果启用,Fury 将使用 classVersionHash 进行检查、写入和一致性检查。当启用 CompatibleMode#COMPATIBLE 时,它将自动禁用。除非能确保类不会演变,否则不建议禁用。false
checkJdkClassSerializable启用或禁用对 java.* 下类的 Serializable 接口的检查。如果java.*下的类不是 Serializable,Fury 将抛出 UnsupportedOperationExceptiontrue
registerGuavaTypes是否预注册 Guava 类型,如 RegularImmutableMap/RegularImmutableList。这些类型不是公共 API,但看起来相当稳定。true
requireClassRegistration禁用可能允许未知类被反序列化,可能导致安全风险。true
suppressClassRegistrationWarnings是否抑制类注册警告。警告可用于安全审计,但可能令人烦恼,此抑制默认启用。true
metaShareEnabled启用或禁用元数据共享模式。false
scopedMetaShareEnabled作用域元数据共享专注于单个序列化过程。在此过程中创建或识别的元数据是独有的,不与其他序列化共享。false
metaCompressor设置一个压缩器进行元数据压缩。注意,传递的 MetaCompressor 应该是线程安全的。默认情况下,将使用基于 Deflater 的压缩器 DeflaterMetaCompressor。用户可以传递其他压缩器,如 zstd,以获得更好的压缩率。DeflaterMetaCompressor
deserializeNonexistentClass启用或禁用对不存在类的数据反序列化/跳过。如果设置了 CompatibleMode.Compatible,则为 true,否则为 false``如果设置了CompatibleMode.Compatible 则为true,否则为false
codeGenEnabled禁用可能会导致初始序列化更快,但后续序列化更慢。true
asyncCompilationEnabled如果启用,序列化首先使用解释模式,并在类的异步序列化器 JIT 完成后切换到 JIT 序列化。false
scalaOptimizationEnabled启用或禁用特定于 Scala 的序列化优化。false
copyRef禁用时,复制性能会更好。但 Fury 深度复制将忽略循环和共享引用。同一对象图的引用将在一次 Fury#copy 中被复制成不同的对象。true

高级用法

创建Fury
单线程Fury
 

java

代码解读

复制代码

Fury fury = Fury.builder() .withLanguage(Language.JAVA) // 启用引用跟踪以支持共享/循环引用。 // 如果没有重复引用,禁用它可以提高性能。 .withRefTracking(false) .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) // 启用类型前向/后向兼容性 // 为了更小的尺寸和更好的性能禁用它。 // .withCompatibleMode(CompatibleMode.COMPATIBLE) // 启用异步多线程编译。 .withAsyncCompilation(true) .build(); byte[] bytes = fury.serialize(object); System.out.println(fury.deserialize(bytes));

线程安全Fury
 

java

代码解读

复制代码

ThreadSafeFury fury = Fury.builder() .withLanguage(Language.JAVA) // 启用引用跟踪以支持共享/循环引用。 // 如果没有重复引用,禁用它可以提高性能。 .withRefTracking(false) // 压缩 int 类型以获得更小的尺寸 // .withIntCompressed(true) // 压缩 long 类型以获得更小的尺寸 // .withLongCompressed(true) .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) // 启用类型前向/后向兼容性 // 为了更小的尺寸和更好的性能禁用它。 // .withCompatibleMode(CompatibleMode.COMPATIBLE) // 启用异步多线程编译。 .withAsyncCompilation(true) .buildThreadSafeFury(); byte[] bytes = fury.serialize(object); System.out.println(fury.deserialize(bytes));

更小的大小

FuryBuilder#withIntCompressed/FuryBuilder#withLongCompressed 可以用于压缩 int/long 类型以减小尺寸。通常,压缩 int 类型已经足够。 这两种压缩默认都是启用的,如果序列化的大小不重要,比如你之前使用 flatbuffers 进行序列化,它不进行任何压缩,那么你应该禁用压缩。如果你的数据全是数字,压缩可能会带来 80% 的性能下降。 对于 int 压缩,Fury 使用 1~5 字节进行编码。每个字节的第一位表示是否有下一个字节。如果第一位被设置,则读取下一个字节,直到下一个字节的第一位未设置为止。 对于 long 压缩,Fury 支持两种编码:

  • Fury SLI(Small Long as Int)编码(默认使用):
    • 如果 long 值在 [-1073741824, 1073741823] 之间,编码为 4 字节 int:| little-endian:((int) value) << 1 |
    • 否则写为 9 字节:| 0b1 | little-endian 8 字节 long |
  • Fury PVL(Progressive Variable-length Long)编码:
    • 每个字节的第一位表示是否有下一个字节。如果第一位被设置,则读取下一个字节,直到下一个字节的第一位未设置为止。
    • 负数将通过 (v << 1) ^ (v >> 63) 转换为正数,以减少小负数的成本。 如果一个数字是 long 类型,通常不能用更小的字节表示,压缩效果不佳,相对于性能成本不值得。如果你发现压缩没有带来太多空间节省,可以尝试禁用 long 压缩。
对象深复制

深复制例子

 

java

代码解读

复制代码

Fury fury=Fury.builder() ... .withRefCopy(true).build(); SomeClass a=xxx; SomeClass copied=fury.copy(a)

使 Fury 深拷贝忽略循环和共享引用,这种深拷贝模式将忽略循环和共享引用。在一次 Fury#copy 操作中,对象图中的相同引用将被复制成不同的对象。

 

java

代码解读

复制代码

Fury fury=Fury.builder() ... .withRefCopy(false).build(); SomeClass a=xxx; SomeClass copied=fury.copy(a)

实现自定义的序列化

在某些情况下,您可能需要为您的类型实现一个序列化器,特别是一些类通过 JDK 的 writeObject/writeReplace/readObject/readResolve 自定义序列化,这些方法的效率很低。例如,您不希望以下的 Foo#writeObject 被调用,您可以参考下面的 FooSerializer 作为示例:

 

java

代码解读

复制代码

class Foo { public long f1; private void writeObject(ObjectOutputStream s) throws IOException { System.out.println(f1); s.defaultWriteObject(); } } class FooSerializer extends Serializer<Foo> { public FooSerializer(Fury fury) { super(fury, Foo.class); } @Override public void write(MemoryBuffer buffer, Foo value) { buffer.writeInt64(value.f1); } @Override public Foo read(MemoryBuffer buffer) { Foo foo = new Foo(); foo.f1 = buffer.readInt64(); return foo; } }

注册序列化器

 

java

代码解读

复制代码

Fury fury=getFury(); fury.registerSerializer(Foo.class,new FooSerializer(fury));

安全和类注册

可以使用 FuryBuilder#requireClassRegistration来禁用类注册,这将允许反序列化未知类型的对象,更灵活但如果类包含恶意代码可能不安全。 除非能够确保环境安全,否则不要禁用类注册。当禁用此选项时,反序列化未知/不可信类型时,init/equals/hashCode 中的恶意代码可能会被执行。 类注册不仅可以降低安全风险,还可以避免类名序列化的开销。 您可以使用 API Fury#register 来注册类。 注意类注册的顺序很重要,序列化和反序列化的两端应该有相同的注册顺序。

 

java

代码解读

复制代码

Fury fury=xxx; fury.register(SomeClass.class); fury.register(SomeClass1.class,200);

如果您调用 FuryBuilder#requireClassRegistration(false) 来禁用类注册检查,可以通过 ClassResolver#setClassChecker 设置 org.apache.fury.resolver.ClassChecker 以控制允许序列化的类。例如,您可以允许以 org.example.*开头的类:

 

java

代码解读

复制代码

Fury fury=xxx; fury.getClassResolver().setClassChecker((classResolver,className)->className.startsWith("org.example."));

 

java

代码解读

复制代码

AllowListChecker checker=new AllowListChecker(AllowListChecker.CheckLevel.STRICT); ThreadSafeFury fury=new ThreadLocalFury(classLoader->{ Fury f=Fury.builder().requireClassRegistration(true).withClassLoader(classLoader).build(); f.getClassResolver().setClassChecker(checker); checker.addListener(f.getClassResolver()); return f; }); checker.allowClass("org.example.*");

Fury 还提供了一个基于允许/禁止列表的检查器 org.apache.fury.resolver.AllowListChecker,以简化类检查机制的自定义。您可以使用此检查器或自行实现更复杂的检查器。

序列化注册

您还可以通过 Fury#registerSerializer API 为类注册自定义序列化器。 或者实现 java.io.Externalizable 接口。

零拷贝
 

java

代码解读

复制代码

import org.apache.fury.*; import org.apache.fury.config.*; import org.apache.fury.serializers.BufferObject; import org.apache.fury.memory.MemoryBuffer; import java.util.*; import java.util.stream.Collectors; public class ZeroCopyExample { //注意,fury创建的实例应该被复用 static Fury fury = Fury.builder() .withLanguage(Language.JAVA) .build(); // mvn exec:java -Dexec.mainClass="io.ray.fury.examples.ZeroCopyExample" public static void main(String[] args) { List<Object> list = Arrays.asList("str", new byte[1000], new int[100], new double[100]); Collection<BufferObject> bufferObjects = new ArrayList<>(); byte[] bytes = fury.serialize(list, e -> !bufferObjects.add(e)); List<MemoryBuffer> buffers = bufferObjects.stream() .map(BufferObject::toBuffer).collect(Collectors.toList()); System.out.println(fury.deserialize(bytes, buffers)); } }

共享元数据

Fury 支持在一个上下文(例如,TCP 连接)中共享多个序列化之间的类型元数据(类名、字段名、最终字段类型信息等),这些信息将在该上下文的首次序列化期间发送到对端。基于这些元数据,对端可以重建相同的反序列化器,从而避免后续序列化传输元数据,减少网络传输压力,并自动支持类型的前向/后向兼容性。

 

java

代码解读

复制代码

// Fury.builder() // .withLanguage(Language.JAVA) // .withRefTracking(false) // // share meta across serialization. // .withMetaContextShare(true) // Not thread-safe fury. MetaContext context=xxx; fury.getSerializationContext().setMetaContext(context); byte[]bytes=fury.serialize(o); // 线程不安全的fury MetaContext context=xxx; fury.getSerializationContext().setMetaContext(context); fury.deserialize(bytes) // 线程安全的fury fury.setClassLoader(beanA.getClass().getClassLoader()); byte[]serialized=fury.execute( f->{ f.getSerializationContext().setMetaContext(context); return f.serialize(beanA); } ); // 线程安全的fury fury.setClassLoader(beanA.getClass().getClassLoader()); Object newObj=fury.execute( f->{ f.getSerializationContext().setMetaContext(context); return f.deserialize(serialized); } );

反序列化不存在的类

Fury 支持反序列化不存在的类,可以通过 FuryBuilder#deserializeNonexistentClass(true) 启用此功能。启用后,并且元数据共享启用时,Fury 会将此类型的反序列化数据存储在 Map 的延迟子类中。通过使用 Fury 实现的延迟 Map,可以避免在反序列化期间填充 Map 的重新平衡成本,进一步提高性能。如果此数据被发送到另一个进程,并且该进程中存在此类,数据将被反序列化为该类型的对象而不丢失任何信息。
如果未启用元数据共享,新类数据将被跳过,并返回一个 NonexistentSkipClass 存根对象。

合并

JDK序列化合并

如果你之前使用的是 JDK 序列化,并且不能同时升级客户端和服务器(这对于在线应用程序来说是常见的情况),Fury 提供了一个工具方法 org.apache.fury.serializer.JavaSerializer.serializedByJDK 用于检查二进制数据是否由 JDK 序列化生成。你可以使用以下模式使现有的序列化协议感知,然后以异步滚动升级的方式将序列化升级到 Fury:

 

java

代码解读

复制代码

if(JavaSerializer.serializedByJDK(bytes)){ ObjectInputStream objectInputStream=xxx; return objectInputStream.readObject(); }else{ return fury.deserialize(bytes); }

更新Fury

目前,二进制兼容性仅确保在小版本中。例如,如果你使用的是 Fury v0.2.0,升级到 Fury v0.2.1 将提供二进制兼容性。但如果升级到 Fury v0.4.1,则不保证二进制兼容性。大多数情况下,没有必要将 Fury 升级到更新的主版本,当前版本已经足够快速和紧凑,并且我们会为近期的旧版本提供一些小的修复。 但是,如果你确实想升级 Fury 以获得更好的性能和更小的尺寸,你需要使用如下代码将 Fury 版本作为头写入序列化数据中以保持二进制兼容性:

 

java

代码解读

复制代码

MemoryBuffer buffer=xxx; buffer.writeVarInt32(2); fury.serialize(buffer,obj);

然后对于反序列化,你需要:

 

java

代码解读

复制代码

MemoryBuffer buffer=xxx; int furyVersion=buffer.readVarInt32() Fury fury=getFury(furyVersion); fury.deserialize(buffer);

getFury 是一个加载相应 Fury 的方法,你可以将不同版本的 Fury 着色并重新定位到不同的包,然后根据版本加载 Fury。 如果你通过小版本升级 Fury,或者你没有旧版 Fury 序列化的数据,你可以直接升级 Fury,无需对数据进行版本控制。

故障排除

类不一致和类版本检查

如果你在创建 Fury 时没有将 CompatibleMode 设置为 org.apache.fury.config.CompatibleMode.COMPATIBLE,并且遇到了奇怪的序列化错误,可能是由于序列化端和反序列化端之间的类不一致导致的。 在这种情况下,你可以调用 FuryBuilder#withClassVersionCheck 来创建 Fury 以验证它,如果反序列化抛出 org.apache.fury.exception.ClassNotCompatibleException,则表示类不一致,你应该使用 FuryBuilder#withCompatibleMode(CompatibleMode.COMPATIBLE) 创建 Fury。 CompatibleMode.COMPATIBLE 会带来更多的性能和空间成本,如果你的类在序列化和反序列化之间始终一致,不要默认设置它。

使用错误的反序列化 API

如果你通过调用 Fury#serialize 序列化一个对象,你应该调用 Fury#deserialize 进行反序列化,而不是 Fury#deserializeJavaObject。 如果你通过调用 Fury#serializeJavaObject 序列化一个对象,你应该调用 Fury#deserializeJavaObject 进行反序列化,而不是 Fury#deserializeJavaObjectAndClass/Fury#deserialize。 如果你通过调用 Fury#serializeJavaObjectAndClass 序列化一个对象,你应该调用 Fury#deserializeJavaObjectAndClass 进行反序列化,而不是 Fury#deserializeJavaObject/Fury#deserialize

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值