使用 Apache Fury 实现极快的序列化

在本文中,我们将了解Apache 软件基金会下的一个孵化项目Apache Fury。该库承诺提供极快的性能、强大的功能和多语言支持。

我们将研究该项目的一些基本功能并将其性能与其他框架进行比较。

使用 Apache Fury 进行序列化
序列化是软件开发中的一个关键过程,可实现系统之间的高效数据交换。它允许应用程序共享状态并通过它进行通信。

Apache Fury 是一个序列化库,旨在解决现有库和框架的局限性。它提供了一个高性能、易于使用的库,用于跨各种编程语言序列化和反序列化数据。旨在高效处理复杂的数据结构和大量数据。Apache Fury 提供的主要功能包括:

  • 高性能:Apache Fury 针对速度进行了优化,确保在序列化和反序列化过程中的开销最小。
  • 跨语言支持:支持多种编程语言,使其适用于不同的开发环境(Java/Python/C++/Golang/JavaScript/Rust/Scala/TypeScript)。
  • 复杂数据结构:能够轻松处理复杂的数据模型。
  • 紧凑序列化:生成紧凑的序列化数据,降低存储和传输成本。
  • GraalVM 原生镜像支持:GraalVM 原生镜像需要 AOT 编译序列化,不需要反射/序列化 JSON 配置。

代码示例
首先,我们需要向我们的项目添加所需的依赖项,以便我们可以开始与 Fury 库 API 进行交互:

<dependency>
    <groupId>org.apache.fury</groupId>
    <artifactId>fury-core</artifactId>
    <version>0.5.0</version>
</dependency>

首次尝试 Fury 时,让我们使用不同的数据类型和至少一个嵌套对象创建一个简单的结构,以便我们可以在实际应用程序中模拟日常用例。为此,我们需要创建一个UserEvent类来表示稍后将被序列化的用户事件的状态:

public class UserEvent implements Serializable {
    private final String userId;
    private final String eventType;
    private final long timestamp;
    private final Address address;
    // Constructor and getters
}

为了给我们的事件对象引入更多的复杂性,让我们使用名为Address 的 Java POJO 为地址定义一个嵌套结构:

public class Address implements Serializable {
    private final String street;
    private final String city;
    private final String zipCode;
    // Constructor and getters
}

一个重要的方面是 Fury 不需要类实现Serializable接口。但是,稍后我们将使用 Java 本机序列化程序,它确实需要它。接下来,我们应该启动 Fury 上下文。

Fury 设置
现在,我们将了解如何设置 Fury,以便开始使用它:

class FurySerializationUnitTest {
    @Test
    void whenUsingFurySerialization_thenGenerateByteOutput() {
        Fury fury = Fury.builder()
          .withLanguage(Language.JAVA)
          .withAsyncCompilation(true)
          .build();
        fury.register(UserEvent.class);
        fury.register(Address.class);
        
        // ...
}

在此代码片段中,我们创建了 Fury 对象并将 Java 定义为要使用的协议,因为它最适合这种情况。但是,如前所述,Fury 支持跨语言序列化(例如使用Language.XLANG ) 。此外,我们将withAsyncCompilation选项设置为true,这允许使用 JIT(即时)在后台编译序列化程序,并且我们的应用程序可以继续处理其他任务而无需等待编译完成。它使用非阻塞编译来实现此优化。

Fury 设置完成后,我们需要注册可能被序列化的类。这很重要,因为 Fury 可以使用预生成的架构或元数据来简化序列化和反序列化过程。这样就无需运行时反射,因为运行时反射可能很慢且占用大量资源。

此外,注册类有助于减少在序列化和反序列化期间动态确定类结构所带来的开销。这可以缩短处理时间。最后,从安全角度来看,这很重要,因为我们创建了一个允许序列化和反序列化的类的安全列表。

Fury 的注册表可防止意外或恶意序列化意外类,这可能会导致安全漏洞,例如反序列化攻击。它还可以降低利用序列化机制或类本身漏洞的风险。反序列化任意或意外类可能会导致代码执行漏洞。

使用 Fury
现在 Fury 已配置完毕,我们可以使用此对象执行多个序列化和反序列化操作。它提供了许多 API,可以访问序列化过程的底层和高层细节,但在我们的例子中,我们可以调用以下方法:

@Test
void whenUsingFurySerialization_thenGenerateByteOutput() { 
    //... setup
    byte[] serializedData = fury.serialize(event);
    UserEvent temp = (UserEvent) fury.deserialize(serializedData);
   
//...
}

我们需要它来使用该库执行这两个基本操作并利用其巨大潜力。尽管如此,我们如何将它与 Java 中使用的其他知名序列化框架进行比较?接下来,我们将进行一些实验来进行这样的比较。

比较
首先,本教程并不打算对 Apache Fury 和其他框架进行广泛的基准测试。话虽如此,为了了解该项目旨在实现的性能类型,让我们看看不同的库和框架在我们的示例用例中的表现如何。为了进行比较,我们使用了 Java Native 序列化、Avro 序列化和Protobuf协议缓冲区

  • 一开始,Protobuf 的表现优于 Fury,
  • 但后来,Fury 似乎表现更好,很可能是因为 JIT 编译器的性质。

然而,正如我们所观察到的,两者都表现优异
 
在序列化过程的输出方面,Protobuf 似乎性能略好一些,不过 Fury 和它之间的差异看起来相当小,所以我们可以说它们的性能也是相当的。

再次强调,这可能不适用于所有情况。这不是一项广泛的基准测试,而是基于我们的用例的比较。尽管如此,Apache Fury 提供了出色的性能和易于使用的功能,这正是该项目的目标。

结论
在本教程中,我们了解了 Fury,这是一个序列化库,它提供极快、跨语言、由 JIT(即时编译)和零拷贝序列化和反序列化功能。此外,我们还了解了它与 Java 生态系统中使用的其他知名序列化框架相比的性能。

无论哪个库或框架更快/更高效,Fury 处理复杂数据结构和提供跨语言支持的能力使其成为需要高速数据处理的现代应用程序的绝佳选择。通过整合 Apache Fury,开发人员可以确保他们的应用程序以最小的开销执行序列化和反序列化任务,从而提高整体效率和性能。