选择序列化框架

Choosing serialization frameworks

我正在阅读有关使用 java 序列化的缺点以及使用序列化框架的必要性的信息。有很多框架,如 avro、parquet、thrift、protobuff。

问题是选择序列化框架时要考虑的所有参数是什么框架。

我想亲身体验一个实际用例和compare/choose 基于需求的序列化框架。

有人可以就此主题提供帮助吗?

有很多因素需要考虑。我将介绍一些重要的内容。

0) 模式优先或代码优先

如果您的项目将涉及不同的语言,代码优先方法可能会出现问题。有一个可以序列化的 JAVA class 很好,但是如果必须在 C 中反序列化它可能会很麻烦。

一般来说,我更喜欢模式优先的方法,以防万一。

1) 对象间标记

一些序列化会产生一个字节流,可以看到一个对象在哪里停止,另一个对象在哪里开始。其他没有。

因此,如果您有一个消息传输/数据存储,它将为您分离出成批的字节,例如ZeroMQ 或数据库字段,那么您可以使用不区分消息的序列化。示例包括 Google 协议缓冲区。通过传输/存储完成分离,reader 可以获得一批字节,确保它包含一个对象,并且只包含一个对象。

如果您的消息传输/数据存储不区分字节批次,例如网络流或文件,那么您要么发明自己的标记,要么使用为您标记的序列化。示例包括 ASN.1 BER、XML.

2) 规范

这是一个属性序列化,这意味着序列化数据描述了它自己的结构。原则上,规范消息的 reader 不必预先知道消息结构是什么,它可以在读取字节时简单地解决这个问题(即使它不知道字段名称)。这在您不完全确定数据来源的情况下很有用。如果数据不是规范的,reader 必须事先知道对象结构是什么,否则反序列化是不明确的。

规范序列化的示例包括 ASN.1 BER、ASN.1 规范 PER、XML。那些不包括 ASN.1 uPER,可能 Google 协议缓冲区(我可能错了)。

AVRO 做了一些不同的事情——数据模式本身就是序列化数据的一部分,所以总是可以从任意数据重建对象。正如您可以想象的那样,用于此的库在 C 等语言中有些笨拙,但在动态语言中更好。

3) 大小和值受限。

一些序列化技术允许开发人员对字段的值和数组的大小设置约束。目的是从包含此类约束的模式文件生成的代码将在序列化和反序列化时自动验证对象。

这非常有用 - 自动完成免费的、架构驱动的内容检查。很容易发现不合规格的数据。

这在大型异类项目(使用许多不同的语言)中非常有用,因为关于什么有效什么无效的所有真实来源都来自模式,并且仅来自模式,并且由自动生成的代码。开发人员不能忽略/绕过约束,当约束发生变化时,每个人都会注意到。

示例包括 ASN.1(通常通过工具集可以很好地完成)、XML(通常不能通过免费/便宜的工具集正确完成;MS 的 xsd.exe 有意忽略任何此类限制)和 JSON(一直到对象验证器)。在这三者中,ASN.1 具有迄今为止最详尽的约束语法;真的很强大

不存在的示例 - Google 协议缓冲区。在这方面 GPB 非常令人恼火,因为它根本没有约束。具有值和大小约束的唯一方法是将它们作为注释写在 .proto 文件中并希望开发人员阅读它们并注意它们,或者采用其他某种非源代码方法。由于 GPB 主要针对异构系统(实际上支持太阳下的所有语言),我认为这是一个非常严重的遗漏,因为必须为项目中使用的每种语言手动编写值/大小验证代码。那是浪费时间。 Google 可以在 .proto 和代码生成器中添加句法元素来支持这一点,而根本不改变线路格式(它都在自动生成的代码中)。

4) 二进制/文本

二进制序列化会更小,序列化/反序列化可能会更快一些。文本序列化更易于调试。但是二进制序列化可以完成的事情令人惊奇。例如,可以轻松地将 ASN.1 解码器添加到 Wireshark(您使用 ASN.1 工具从 .asn 模式文件编译它们),等等 - 在程序数据的有线解码上。我认为 GPB 也是可能的。

ASN.1 uPER 在带宽受限的情况下非常有用;它会自动使用大小/值约束来节省线路上的位。例如,一个在 0 到 15 之间有效的字段只需要 4 位,这就是 uPER 将使用的。我认为 uPER 在 3G、4G 和 5G 等协议中占有重要地位并非巧合。这种“最小位数”方法比压缩文本线格式要优雅得多(JSON 和 XML 做了很多工作,使它们不那么臃肿)。

5) 值

这有点奇怪。在 ASN.1 中,模式文件既可以定义对象的结构,也可以定义对象的值。使用更好的工具,您最终会得到(在您的 C++、JAVA 等源代码中)classes,并且预定义 class 的对象已经填充了值。

为什么有用?好吧,我经常用它来定义项目常量,并提供对约束限制的访问。例如,假设您在消息中有一个有效长度为 15 的数组字段。您可以在字段约束中使用文字 15,也可以在约束中引用整数值对象的值,开发人员也可以使用该整数。

--ASN.1 value that, in good tools, is built into the 
--generated source code
arraySize INTEGER ::= 16

--A SET that has an array of integers that size
MyMessage ::= SET
{
    field [0] SEQUENCE (SIZE(arraySize)) OF INTEGER
}

这在您想遍历该约束的情况下非常方便,因为循环可以是

for (int i = 0; i < arraySize; i++) {do things with MyMessage.field[i];} // ArraySize is an integer in the auto generated code built from the .asn schema file

显然,如果需要更改约束,这就太棒了,因为它唯一需要更改的地方是在模式中,然后是项目重新编译(使用它的每个地方都将使用新值)。更好的是,如果它在模式文件中重命名,则重新编译会在使用它的项目中的任何地方识别(因为使用它的开发人员编写的源代码仍在使用旧名称,现在是未定义的符号 --> 编译器错误。

ASN.1 约束可以变得非常复杂。这是可以完成的微小 尝试。这对于系统开发人员来说非常棒,但对于工具开发人员来说实施起来却相当复杂。

arraySize INTEGER ::= 16
minSize INTEGER ::= 4
maxVal INTEGER ::= 31
minVal INTEGER ::= 16
oddVal INTEGER ::= 63

MyMessage2 ::= SET
{
    field_1 [0] SEQUENCE (SIZE(arraySize)) OF INTEGER,                              -- 16 elements
    field_2 [1] SEQUENCE (SIZE(0..arraySize)) OF INTEGER,                           -- 0 to 16 elements
    field_3 [2] SEQUENCE (SIZE(minSize..arraySize)) OF INTEGER,                     -- 4 to 16 elements
    field_4 [3] SEQUENCE (SIZE(minSize<..<arraySize)) OF INTEGER,                   -- 5 to 15 elements
    field_5 [4] SEQUENCE (SIZE(minSize<..<arraySize)) OF INTEGER(0..maxVal),        -- 5 to 15 elements valued 0..31
    field_6 [5] SEQUENCE (SIZE(minSize<..<arraySize)) OF INTEGER(minVal..maxVal),   -- 5 to 15 elements valued 16..31
    field_7 [6] SEQUENCE (SIZE(minSize<..<arraySize)) OF INTEGER(minVal<..maxVal),  -- 5 to 15 elements valued 17..31
    field_8 [7] SEQUENCE (SIZE(arraySize)) OF INTEGER(minVal<..<maxVal),            -- 16 elements valued 17..30
    field_9 [8] INTEGER (minVal..maxVal AND oddVal)                                 -- valued 16 to 31, and also 63
    f8_indx [10] INTEGER (0..<arraySize)                                            -- index into field 8, constrained to be within the bounds of field 8
}

据我所知,只有 ASN.1 这样做。然后,只有更昂贵的工具才能真正从模式文件中提取这些元素。有了它,这使得它在大型项目中非常有用,因为与数据及其约束以及如何处理它相关的所有内容都只在 .asn 架构中定义,而在其他任何地方都没有定义。

正如我所说,我经常使用它来处理合适的项目类型。一旦将其应用到整个项目中,所节省的时间和风险就非常可观。它也改变了项目的动态;知道整个项目只需重新编译即可对模式进行后期更改。因此,项目后期的协议更改从高风险变为您每天都愿意做的事情。

6) 线格式对象类型

一些序列化线格式将标识线格式中对象的类型 bytestrean。这有助于 reader 在许多不同类型的对象可能来自一个或多个来源的情况下。其他连载不会。

ASN.1 因线格式而异(它有几种,包括一些二进制格式以及 XML 和 JSON)。 ASN.1 BER 在其线格式中使用类型、值和长度字段,因此 reader 可以提前查看对象的标签,从而相应地对字节流进行解码。这个很有用。

Google Protocol Buffers 并没有完全做同样的事情,但是如果 .proto 中的所有消息类型都捆绑到一个最终的 oneof 中,那就是只有每个序列化,那么你可以达到同样的目的

7) 工具成本。

ASN.1 工具的范围从非常非常昂贵(而且非常好)到免费(而且不太好)。许多其他工具是免费的,尽管我发现最好的 XML 工具(适当注意值/大小限制)也非常昂贵。

8) 语言覆盖

如果您听说过它,它很可能已被许多不同语言的工具所涵盖。如果没有,那就更少了。

优秀的商业 ASN.1 工具包括 C/C++/Java/C#。有一些免费的 C/C++ 完全不同。

9) 质量

如果工具质量差,连载技术也学不会。

根据我的经验,GPB 很好(它通常会按照它说的去做)。商用ASN1工具非常好,全面超越GPB的工具集。 AVRO 工作。我听说 Capt'n Proto 偶尔会出现一些问题,但我自己没有使用过它,所以你必须检查一下。 XML 使用好的工具。

10) 摘要

以防你看不出来,我是 ASN.1 的忠实粉丝。

GPB 因其广泛的支持和熟悉度而非常有用,但我确实希望 Google 能为字段和数组添加值/大小限制,并包含值表示法。如果他们这样做了,就有可能拥有与 ASN.1 相同的项目工作流程。如果 Google 只添加这两个功能,我认为 GPB 已经非常接近“完整”,只需要相当于 ASN.1 的 uPER 就可以为那些存储空间很少的人完成它 space 或带宽。

请注意,其中很多内容都集中在项目的情况,以及技术实际有多好/多快/多成熟。