过去十多年大数据和分布式系统蓬勃发展,序列化是其频繁使用的技术。当对象需要跨进程、跨语言、跨节点传输、持久化、状态读写时,都需要进行序列化,其性能和易用性影响着系统的运行效率和开发效率。
成都创新互联公司网站建设公司,提供成都网站制作、成都网站设计,网页设计,建网站,PHP网站建设等专业做网站服务;可快速的进行网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,是专业的做网站团队,希望更多企业前来合作!
对于Java序列化,尽管Kryo[1]等框架提供了相比JDK序列化数倍的性能,对于高吞吐、低延迟、大规模数据传输场景,序列化仍然是整个系统的性能瓶颈。为了优化序列化的性能,分布式系统如Spark[2]、Flink[3]使用了专有行列存二进制格式如tungsten[4]和arrow[5]。这些格式减少了序列化开销,但增加了系统的复杂性,牺牲了编程的灵活性,同时也只覆盖了SQL等关系代数计算专有场景。对于通用分布式编程和跨进程通信,序列化性能始终是一个绕不过去的关键问题。
同时随着计算和应用场景的日益复杂化,系统已经从单一语言的编程范式发展到多语言融合编程,对象在语言之间传输的易用性影响着系统开发效率,进而影响业务的迭代效率。而已有的跨语言序列化框架protobuf/flatbuffer/msgpack等由于无法支持引用、不支持Zero-Copy、大量手写代码以及生成的类不符合面向对象设计[6]无法给类添加行为,导致在易用性、灵活性、动态性和性能上的不足,并不能满足通用跨语言编程需求。
基于此,我们开发了Fury,通过一套支持引用、类型嵌入的语言无关协议,以及JIT动态编译加速、缓存优化和Zero-Copy等技术,实现了任意对象像动态语言自动序列化一样跨语言自动序列化,消除了语言之间的编程边界,并提供相比于业界别的框架最高20~200倍的性能。
Fury是一个基于JIT的高性能多语言原生序列化框架,专注于提供极致的序列化性能和易用性:
除了跨语言能力,Fury还具备以下能力:
目前Fury已经支持Java、Python、Golang以及C++。本文将首先简单介绍如何使用Fury,然后将Fury跟别的序列化框架进行功能、性能和易用性比较,Fury的实现原理将在后续文章里面详细介绍。
这里给出跨语言序列化、纯Java序列化以及避免序列化的示例:
通过Fury Format避免序列化
下面是序列化用户自定义类型的一个示例,该类型里面包含多个基本类型以及嵌套类型的字段,在业务应用里面相当常见。需要注意自定义类型跨语言序列化之前需要调用`register`API注册自定义类型,建立类型在不同语言之间的映射关系,同时保证GoLang等静态语言编译器编译代码时不裁剪掉这部分类型的符号。
apf2;
}
public static class SomeClass2 {
Object f1;
String f2;
List
纯Java序列化:
public class CustomObjectExample {
// mvn exec:java -Dexec.mainClass="io.fury.examples.CustomObjectExample"
public static void main(String[] args) {
// Fury应该在多个对象序列化之间复用,不要每次创建新的Fury实例
Fury fury = Fury.builder().withLanguage(Language.JAVA)
.withReferenceTracking(false)
.withClassRegistrationRequired(false)
.build();
byte[] bytes = fury.serialize(createObject());
System.out.println(fury.deserialize(bytes));;
}
}
跨语言序列化:
public class CustomObjectExample {
// mvn exec:java -Dexec.mainClass="io.fury.examples.CustomObjectExample"
public static void main(String[] args) {
// Fury应该在多个对象序列化之间复用,不要每次创建新的Fury实例
Fury fury = Fury.builder().withLanguage(Language.XLANG)
.withReferenceTracking(false).build();
fury.register(SomeClass1.class, "example.SomeClass1");
fury.register(SomeClass2.class, "example.SomeClass2");
byte[] bytes = fury.serialize(createObject());
// bytes can be data serialized by other languages.
System.out.println(fury.deserialize(bytes));;
}
}
from dataclasses import dataclass
from typing import List, Dict
import pyfury
@dataclass
class SomeClass2:
f1: Any = None
f2: str = None
f3: List[str] = None
f4: Dict[pyfury.Int8Type, pyfury.Int32Type] = None
f5: pyfury.Int8Type = None
f6: pyfury.Int16Type = None
f7: pyfury.Int32Type = None
# int类型默认会按照long类型进行序列化,如果对端是更加narrow的类型,
# 需要使用pyfury.Int32Type等进行标注
f8: int = None # 也可以使用pyfury.Int64Type进行标注
f9: pyfury.Float32Type = None
f10: float = None # 也可以使用pyfury.Float64Type进行标注
f11: pyfury.Int16ArrayType = None
f12: List[pyfury.Int16Type] = None
@dataclass
class SomeClass1:
f1: Any
f2: Dict[pyfury.Int8Type, pyfury.Int32Type]
if __name__ == "__main__":
fury_ = pyfury.Fury(reference_tracking=False)
fury_.register_class(SomeClass1, "example.SomeClass1")
fury_.register_class(SomeClass2, "example.SomeClass2")
obj2 = SomeClass2(f1=True, f2={-1: 2})
obj1 = SomeClass1(
f1=obj2,
f2="abc",
f3=["abc", "abc"],
f4={1: 2},
f5=2 ** 7 - 1,
f6=2 ** 15 - 1,
f7=2 ** 31 - 1,
f8=2 ** 63 - 1,
f9=1.0 / 2,
f10=1 / 3.0,
f11=array.array("h", [1, 2]),
f12=[-1, 4],
)
data = fury_.serialize(obj)
# bytes can be data serialized by other languages.
print(fury_.deserialize(data))
package main
import "code.alipay.com/ray-project/fury/go/fury"
import "fmt"
func main() {
type SomeClass1 struct {
F1 interface{}
F2 string
F3 []interface{}
F4 map[int8]int32
F5 int8
F6 int16
F7 int32
F8 int64
F9 float32
F10 float64
F11 []int16
F12 fury.Int16Slice
}
type SomeClas2 struct {
F1 interface{}
F2 map[int8]int32
}
fury_ := fury.NewFury(false)
if err := fury_.RegisterTagType("example.SomeClass1", SomeClass1{}); err != nil {
panic(err)
}
if err := fury_.RegisterTagType("example.SomeClass2", SomeClass2{}); err != nil {
panic(err)
}
obj2 := &SomeClass2{}
obj2.F1 = true
obj2.F2 = map[int8]int32{-1: 2}
obj := &SomeClass1{}
obj.F1 = obj2
obj.F2 = "abc"
obj.F3 = []interface{}{"abc", "abc"}
f4 := map[int8]int32{1: 2}
obj.F4 = f4
obj.F5 = fury.MaxInt8
obj.F6 = fury.MaxInt16
obj.F7 = fury.MaxInt32
obj.F8 = fury.MaxInt64
obj.F9 = 1.0 / 2
obj.F10 = 1 / 3.0
obj.F11 = []int16{1, 2}
obj.F12 = []int16{-1, 4}
bytes, err := fury_.Marshal(value)
if err != nil {
}
var newValue interface{}
// bytes can be data serialized by other languages.
if err := fury_.Unmarshal(bytes, &newValue); err != nil {
panic(err)
}
fmt.Println(newValue)
}
共享引用和循环引用是程序里面常见的构造,很多数据结构如图都包含大量的循环引用,而手动实现这些包含共享引用和循环引用的对象,需要大量冗长复杂易出错的代码。跨语言序列化框架支持循环引用可以极大简化这些复杂场景的序列化,加速业务迭代效率。下面是一个包含循环引用的自定义类型跨语言序列化示例。
import com.google.common.collect.ImmutableMap;
import io.fury.*;
import java.util.Map;
public class ReferenceExample {
public static class SomeClass {
SomeClass f1;
Mapf2;
Mapf3;
}
public static Object createObject() {
SomeClass obj = new SomeClass();
obj.f1 = obj;
obj.f2 = ImmutableMap.of("k1", "v1", "k2", "v2");
obj.f3 = obj.f2;
return obj;
}
}
Java序列化:
public class ReferenceExample {
// mvn exec:java -Dexec.mainClass="io.fury.examples.ReferenceExample"
public static void main(String[] args) {
// Fury应该在多个对象序列化之间复用,不要每次创建新的Fury实例
Fury fury = Fury.builder().withLanguage(Language.JAVA)
.withReferenceTracking(true)
.withClassRegistrationRequired(false)
.build();
byte[] bytes = fury.serialize(createObject());
System.out.println(fury.deserialize(bytes));;
}
}
跨语言序列化:
public class ReferenceExample {
// mvn exec:java -Dexec.mainClass="io.fury.examples.ReferenceExample"
public static void main(String[] args) {
// Fury应该在多个对象序列化之间复用,不要每次创建新的Fury实例
Fury fury = Fury.builder().withLanguage(Language.XLANG)
.withReferenceTracking(true).build();
fury.register(SomeClass.class, "example.SomeClass");
byte[] bytes = fury.serialize(createObject());
// bytes can be data serialized by other languages.
System.out.println(fury.deserialize(bytes));;
}
}
from typing import Dict
import pyfury
class SomeClass:
f1: "SomeClass"
f2: Dict[str, str]
f3: Dict[str, str]
if __name__ == "__main__":
fury_ = pyfury.Fury(reference_tracking=True)
fury_.register_class(SomeClass, "example.SomeClass")
obj = SomeClass()
obj.f2 = {"k1": "v1", "k2": "v2"}
obj.f1, obj.f3 = obj, obj.f2
data = fury_.serialize(obj)
# bytes can be data serialized by other languages.
print(fury_.deserialize(data))
package main
import "code.alipay.com/ray-project/fury/go/fury"
import "fmt"
func main() {
type SomeClass struct {
F1 *SomeClass
F2 map[string]string
F3 map[string]string
}
fury_ := fury.NewFury(true)
if err := fury_.RegisterTagType("example.SomeClass", SomeClass{}); err != nil {
panic(err)
}
value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}}
value.F3 = value.F2
value.F1 = value
bytes, err := fury_.Marshal(value)
if err != nil {
}
var newValue interface{}
// bytes can be data serialized by other languages.
if err := fury_.Unmarshal(bytes, &newValue); err != nil {
panic(err)
}
fmt.Println(newValue)
}
对于大规模数据传输场景,内存拷贝有时会成为整个系统的瓶颈。为此各种语言和框架做了大量优化,比如Java提供了NIO能力,避免了内存在用户态和内核态之间的来回拷贝;Kafka使用Java的NIO来实现零拷贝;Python Pickle5提供了Out-Of-Band Buffer[7]序列化能力来避免额外拷贝。对于高性能跨语言数据传输,序列化框架也需要能够支持Zero-Copy,避免数据Buffer的额外拷贝。下面是一个Fury序列化多个基本类型数组组成的对象树的示例,分别对应到Java基本类型数组、Python Numpy数组、Golang 基本类型slice。对于ByteBuffer零拷贝,在本文的性能测试部分也给出了部分介绍。
Java序列化
import io.fury.*;
import io.fury.serializers.BufferObject;
import io.fury.memory.MemoryBuffer;
import java.util.*;
import java.util.stream.Collectors;
public class ZeroCopyExample {
// mvn exec:java -Dexec.mainClass="io.fury.examples.ZeroCopyExample"
public static void main(String[] args) {
// Fury应该在多个对象序列化之间复用,不要每次创建新的Fury实例
Fury fury = Fury.builder()
.withLanguage(Language.JAVA)
.withClassRegistrationRequired(false)
.build();
List