返回介绍

编码数据的格式

发布于 2024-08-24 16:53:18 字数 15678 浏览 0 评论 0 收藏 0

程序通常(至少)使用两种形式的数据:

  1. 在内存中,数据保存在对象,结构体,列表,数组,哈希表,树等中。 这些数据结构针对 CPU 的高效访问和操作进行了优化(通常使用指针)。
  2. 如果要将数据写入文件,或通过网络发送,则必须将其 编码(encode) 为某种自包含的字节序列(例如,JSON 文档)。 由于每个进程都有自己独立的地址空间,一个进程中的指针对任何其他进程都没有意义,所以这个字节序列表示会与通常在内存中使用的数据结构完全不同i
i. 除一些特殊情况外,例如某些内存映射文件或直接在压缩数据上操作(如 列压缩 中所述)。 ↩

所以,需要在两种表示之间进行某种类型的翻译。 从内存中表示到字节序列的转换称为 编码(Encoding) (也称为 序列化(serialization)编组(marshalling) ),反过来称为 解码(Decoding)ii 解析(Parsing)反序列化(deserialization)反编组() unmarshalling)译 i

ii. 请注意, 编码(encode)加密(encryption) 无关。 本书不讨论加密。 ↩
译 i. Marshal 与 Serialization 的区别:Marshal 不仅传输对象的状态,而且会一起传输对象的方法(相关代码)。 ↩

术语冲突

不幸的是,在 第七章 : 事务(Transaction) 的上下文里, 序列化(Serialization) 这个术语也出现了,而且具有完全不同的含义。尽管序列化可能是更常见的术语,为了避免术语重载,本书中坚持使用 编码(Encoding) 表达此含义。

这是一个常见的问题,因而有许多库和编码格式可供选择。 首先让我们概览一下。

语言特定的格式

许多编程语言都内建了将内存对象编码为字节序列的支持。例如,Java 有 java.io.Serializable 【1】,Ruby 有 Marshal 【2】,Python 有 pickle 【3】等等。许多第三方库也存在,例如 Kryo for Java 【4】。

这些编码库非常方便,可以用很少的额外代码实现内存对象的保存与恢复。但是它们也有一些深层次的问题:

  • 这类编码通常与特定的编程语言深度绑定,其他语言很难读取这种数据。如果以这类编码存储或传输数据,那你就和这门语言绑死在一起了。并且很难将系统与其他组织的系统(可能用的是不同的语言)进行集成。
  • 为了恢复相同对象类型的数据,解码过程需要 实例化任意类 的能力,这通常是安全问题的一个来源【5】:如果攻击者可以让应用程序解码任意的字节序列,他们就能实例化任意的类,这会允许他们做可怕的事情,如远程执行任意代码【6,7】。
  • 在这些库中,数据版本控制通常是事后才考虑的。因为它们旨在快速简便地对数据进行编码,所以往往忽略了前向后向兼容性带来的麻烦问题。
  • 效率(编码或解码所花费的 CPU 时间,以及编码结构的大小)往往也是事后才考虑的。 例如,Java 的内置序列化由于其糟糕的性能和臃肿的编码而臭名昭着【8】。

因此,除非临时使用,采用语言内置编码通常是一个坏主意。

JSON,XML 和二进制变体

谈到可以被许多编程语言编写和读取的标准化编码,JSON 和 XML 是显眼的竞争者。它们广为人知,广受支持,也 广受憎恶 。 XML 经常被批评为过于冗长和不必要的复杂【9】。 JSON 倍受欢迎,主要由于它在 Web 浏览器中的内置支持(通过成为 JavaScript 的一个子集)以及相对于 XML 的简单性。 CSV 是另一种流行的与语言无关的格式,尽管功能较弱。

JSON,XML 和 CSV 是文本格式,因此具有人类可读性(尽管语法是一个热门辩题)。除了表面的语法问题之外,它们也有一些微妙的问题:

  • 数字的编码多有歧义之处。XML 和 CSV 不能区分数字和字符串(除非引用外部模式)。 JSON 虽然区分字符串和数字,但不区分整数和浮点数,而且不能指定精度。
  • 当处理大量数据时,这个问题更严重了。例如,大于$2^{53}$的整数不能在 IEEE 754 双精度浮点数中精确表示,因此在使用浮点数(例如 JavaScript)的语言进行分析时,这些数字会变得不准确。 Twitter 上有一个大于$2^{53}$的数字的例子,它使用一个 64 位的数字来标识每条推文。 Twitter API 返回的 JSON 包含了两种推特 ID,一个 JSON 数字,另一个是十进制字符串,以此避免 JavaScript 程序无法正确解析数字的问题【10】。
  • JSON 和 XML 对 Unicode 字符串(即人类可读的文本)有很好的支持,但是它们不支持二进制数据(不带字符编码(character encoding) 的字节序列)。二进制串是很实用的功能,所以人们通过使用 Base64 将二进制数据编码为文本来绕开这个限制。模式然后用于表示该值应该被解释为 Base64 编码。这个工作,但它有点 hacky,并增加了 33%的数据大小。 XML 【11】和 JSON 【12】都有可选的模式支持。这些模式语言相当强大,所以学习和实现起来相当复杂。 XML 模式的使用相当普遍,但许多基于 JSON 的工具嫌麻烦才不会使用模式。由于数据的正确解释(例如数字和二进制字符串)取决于模式中的信息,因此不使用 XML/JSON 模式的应用程序可能需要对相应的编码/解码逻辑进行硬编码。
  • CSV 没有任何模式,因此应用程序需要定义每行和每列的含义。如果应用程序更改添加新的行或列,则必须手动处理该变更。 CSV 也是一个相当模糊的格式(如果一个值包含逗号或换行符,会发生什么?)。尽管其转义规则已经被正式指定【13】,但并不是所有的解析器都正确的实现了标准。

尽管存在这些缺陷,但 JSON,XML 和 CSV 已经足够用于很多目的。特别是作为数据交换格式(即将数据从一个组织发送到另一个组织),它们很可能仍然很受欢迎。这种情况下,只要人们对格式是什么意见一致,格式多么美观或者高效就没有关系。 让不同的组织达成一致的难度超过了其他大多数问题。

二进制编码

对于仅在组织内部使用的数据,使用最小公分母编码格式的压力较小。例如,可以选择更紧凑或更快的解析格式。虽然对小数据集来说,收益可以忽略不计,但一旦达到 TB 级别,数据格式的选择就会产生巨大的影响。

JSON 比 XML 简洁,但与二进制格式一比,还是太占地方。这一事实导致大量二进制编码版本 JSON & XML 的出现,JSON(MessagePack,BSON,BJSON,UBJSON,BISON 和 Smile 等)(例如 WBXML 和 Fast Infoset)。这些格式已经被各种各样的领域所采用,但是没有一个像 JSON 和 XML 的文本版本那样被广泛采用。

这些格式中的一些扩展了一组数据类型(例如,区分整数和浮点数,或者增加对二进制字符串的支持),另一方面,它们没有盖面 JSON / XML 的数据模型。特别是由于它们没有规定模式,所以它们需要在编码数据中包含所有的对象字段名称。也就是说,在 例 4-1 中的 JSON 文档的二进制编码中,需要在某处包含字符串 userNamefavoriteNumberinterest

例 4-1 本章中用于展示二进制编码的示例记录

{
    "userName": "Martin",
    "favoriteNumber": 1337,
    "interests": ["daydreaming", "hacking"]
}

我们来看一个 MessagePack 的例子,它是一个 JSON 的二进制编码。图 4-1 显示了如果使用 MessagePack 【14】对 例 4-1 中的 JSON 文档进行编码,则得到的字节序列。前几个字节如下:

  1. 第一个字节 0x83 表示接下来是 3 个字段(低四位= 0x03 )的 对象 object (高四位= 0x80 )。 (如果想知道如果一个对象有 15 个以上的字段会发生什么情况,字段的数量塞不进 4 个 bit 里,那么它会用另一个不同的类型标识符,字段的数量被编码两个或四个字节)。
  2. 第二个字节 0xa8 表示接下来是 8 字节长的字符串(最高四位= 0x08)。
  3. 接下来八个字节是 ASCII 字符串形式的字段名称 userName 。由于之前已经指明长度,不需要任何标记来标识字符串的结束位置(或者任何转义)。
  4. 接下来的七个字节对前缀为 0xa6 的六个字母的字符串值 Martin 进行编码,依此类推。

二进制编码长度为 66 个字节,仅略小于文本 JSON 编码所取的 81 个字节(删除了空白)。所有的 JSON 的二进制编码在这方面是相似的。空间节省了一丁点(以及解析加速)是否能弥补可读性的损失,谁也说不准。

在下面的章节中,能达到比这好得多的结果,只用 32 个字节对相同的记录进行编码。

图 4-1 使用 MessagePack 编码的记录(例 4-1)

Thrift 与 Protocol Buffers

Apache Thrift 【15】和 Protocol Buffers(protobuf)【16】是基于相同原理的二进制编码库。 Protocol Buffers 最初是在 Google 开发的,Thrift 最初是在 Facebook 开发的,并且在 2007~2008 年都是开源的【17】。 Thrift 和 Protocol Buffers 都需要一个模式来编码任何数据。要在 Thrift 的 例 4-1 中对数据进行编码,可以使用 Thrift 接口定义语言(IDL) 来描述模式,如下所示:

struct Person {
    1: required string       userName,
    2: optional i64          favoriteNumber,
    3: optional list<string> interests
}

Protocol Buffers 的等效模式定义看起来非常相似:

message Person {
    required string user_name       = 1;
    optional int64  favorite_number = 2;
    repeated string interests       = 3;
}

Thrift 和 Protocol Buffers 每一个都带有一个代码生成工具,它采用了类似于这里所示的模式定义,并且生成了以各种编程语言实现模式的类【18】。您的应用程序代码可以调用此生成的代码来对模式的记录进行编码或解码。 用这个模式编码的数据是什么样的?令人困惑的是,Thrift 有两种不同的二进制编码格式iii ,分别称为 BinaryProtocol 和 CompactProtocol。先来看看 BinaryProtocol。使用这种格式的编码来编码 例 4-1 中的消息只需要 59 个字节,如 图 4-2 所示【19】。

图 4-2 使用 Thrift 二进制协议编码的记录

iii. 实际上,Thrift 有三种二进制协议:CompactProtocol 和 DenseProtocol,尽管 DenseProtocol 只支持 C ++实现,所以不算作跨语言[18]。 除此之外,它还有两种不同的基于 JSON 的编码格式【19】。 真逗! ↩

与 图 4-1 类似,每个字段都有一个类型注释(用于指示它是一个字符串,整数,列表等),还可以根据需要指定长度(字符串的长度,列表中的项目数) 。出现在数据中的字符串 ( Martin , daydreaming , hacking ) 也被编码为 ASCII(或者说,UTF-8),与之前类似。

图 4-1 相比,最大的区别是没有字段名 (userName, favoriteNumber, interest) 。相反,编码数据包含字段标签,它们是数字 (1, 2 和 3) 。这些是模式定义中出现的数字。字段标记就像字段的别名 - 它们是说我们正在谈论的字段的一种紧凑的方式,而不必拼出字段名称。

Thrift CompactProtocol 编码在语义上等同于 BinaryProtocol,但是如 图 4-3 所示,它只将相同的信息打包成只有 34 个字节。它通过将字段类型和标签号打包到单个字节中,并使用可变长度整数来实现。数字 1337 不是使用全部八个字节,而是用两个字节编码,每个字节的最高位用来指示是否还有更多的字节来。这意味着-64 到 63 之间的数字被编码为一个字节,-8192 和 8191 之间的数字以两个字节编码,等等。较大的数字使用更多的字节。

图 4-3 使用 Thrift 压缩协议编码的记录

最后,Protocol Buffers(只有一种二进制编码格式)对相同的数据进行编码,如 图 4-4 所示。 它的打包方式稍有不同,但与 Thrift 的 CompactProtocol 非常相似。 Protobuf 将同样的记录塞进了 33 个字节中。

图 4-4 使用 Protobuf 编码的记录

需要注意的一个细节:在前面所示的模式中,每个字段被标记为必需或可选,但是这对字段如何编码没有任何影响(二进制数据中没有任何字段指示是否需要字段)。所不同的是,如果未设置该字段,则所需的运行时检查将失败,这对于捕获错误非常有用。

字段标签和模式演变

我们之前说过,模式不可避免地需要随着时间而改变。我们称之为模式演变。 Thrift 和 Protocol Buffers 如何处理模式更改,同时保持向后兼容性?

从示例中可以看出,编码的记录就是其编码字段的拼接。每个字段由其标签号码(样本模式中的数字 1,2,3)标识,并用数据类型(例如字符串或整数)注释。如果没有设置字段值,则简单地从编码记录中省略。从中可以看到,字段标记对编码数据的含义至关重要。您可以更改架构中字段的名称,因为编码的数据永远不会引用字段名称,但不能更改字段的标记,因为这会使所有现有的编码数据无效。

您可以添加新的字段到架构,只要您给每个字段一个新的标签号码。如果旧的代码(不知道你添加的新的标签号码)试图读取新代码写入的数据,包括一个新的字段,其标签号码不能识别,它可以简单地忽略该字段。数据类型注释允许解析器确定需要跳过的字节数。这保持了前向兼容性:旧代码可以读取由新代码编写的记录。

向后兼容性呢?只要每个字段都有一个唯一的标签号码,新的代码总是可以读取旧的数据,因为标签号码仍然具有相同的含义。唯一的细节是,如果你添加一个新的领域,你不能要求。如果您要添加一个字段并将其设置为必需,那么如果新代码读取旧代码写入的数据,则该检查将失败,因为旧代码不会写入您添加的新字段。因此,为了保持向后兼容性,在模式的初始部署之后 添加的每个字段必须是可选的或具有默认值

删除一个字段就像添加一个字段,倒退和向前兼容性问题相反。这意味着您只能删除一个可选的字段(必填字段永远不能删除),而且您不能再次使用相同的标签号码(因为您可能仍然有数据写在包含旧标签号码的地方,而该字段必须被新代码忽略)。

数据类型和模式演变

如何改变字段的数据类型?这可能是可能的——检查文件的细节——但是有一个风险,值将失去精度或被扼杀。例如,假设你将一个 32 位的整数变成一个 64 位的整数。新代码可以轻松读取旧代码写入的数据,因为解析器可以用零填充任何缺失的位。但是,如果旧代码读取由新代码写入的数据,则旧代码仍使用 32 位变量来保存该值。如果解码的 64 位值不适合 32 位,则它将被截断。

Protobuf 的一个奇怪的细节是,它没有列表或数组数据类型,而是有一个字段的重复标记(这是第三个选项旁边必要和可选)。如 图 4-4 所示,重复字段的编码正如它所说的那样:同一个字段标记只是简单地出现在记录中。这具有很好的效果,可以将可选(单值)字段更改为重复(多值)字段。读取旧数据的新代码会看到一个包含零个或一个元素的列表(取决于该字段是否存在)。读取新数据的旧代码只能看到列表的最后一个元素。

Thrift 有一个专用的列表数据类型,它使用列表元素的数据类型进行参数化。这不允许 Protocol Buffers 所做的从单值到多值的相同演变,但是它具有支持嵌套列表的优点。

Avro

Apache Avro 【20】是另一种二进制编码格式,与 Protocol Buffers 和 Thrift 有趣的不同。 它是作为 Hadoop 的一个子项目在 2009 年开始的,因为 Thrift 不适合 Hadoop 的用例【21】。

Avro 也使用模式来指定正在编码的数据的结构。 它有两种模式语言:一种(Avro IDL)用于人工编辑,一种(基于 JSON),更易于机器读取。

我们用 Avro IDL 编写的示例模式可能如下所示:

record Person {
    string                userName;
    union { null, long }  favoriteNumber = null;
    array<string>         interests;
}

等价的 JSON 表示:

{
    "type": "record",
    "name": "Person",
    "fields": [
        {"name": "userName", "type": "string"},
        {"name": "favoriteNumber", "type": ["null", "long"], "default": null},
        {"name": "interests", "type": {"type": "array", "items": "string"}
    ] 
}

首先,请注意架构中没有标签号码。 如果我们使用这个模式编码我们的例子记录( 例 4-1 ),Avro 二进制编码只有 32 个字节长,这是我们所见过的所有编码中最紧凑的。 编码字节序列的分解如 图 4-5 所示。

如果您检查字节序列,您可以看到没有什么可以识别字段或其数据类型。 编码只是由连在一起的值组成。 一个字符串只是一个长度前缀,后跟 UTF-8 字节,但是在被包含的数据中没有任何内容告诉你它是一个字符串。 它可以是一个整数,也可以是其他的整数。 整数使用可变长度编码(与 Thrift 的 CompactProtocol 相同)进行编码。

图 4-5 使用 Avro 编码的记录

为了解析二进制数据,您按照它们出现在架构中的顺序遍历这些字段,并使用架构来告诉您每个字段的数据类型。这意味着如果读取数据的代码使用与写入数据的代码完全相同的模式,则只能正确解码二进制数据。阅读器和作者之间的模式不匹配意味着错误地解码数据。

那么,Avro 如何支持模式演变呢?

作者模式与读者模式

有了 Avro,当应用程序想要编码一些数据(将其写入文件或数据库,通过网络发送等)时,它使用它知道的任何版本的模式编码数据,例如,架构可能被编译到应用程序中。这被称为作者的模式。

当一个应用程序想要解码一些数据(从一个文件或数据库读取数据,从网络接收数据等)时,它希望数据在某个模式中,这就是读者的模式。这是应用程序代码所依赖的模式,在应用程序的构建过程中,代码可能是从该模式生成的。

Avro 的关键思想是作者的模式和读者的模式不必是相同的 - 他们只需要兼容。当数据解码(读取)时,Avro 库通过并排查看作者的模式和读者的模式并将数据从作者的模式转换到读者的模式来解决差异。 Avro 规范【20】确切地定义了这种解析的工作原理,如 图 4-6 所示。

例如,如果作者的模式和读者的模式的字段顺序不同,这是没有问题的,因为模式解析通过字段名匹配字段。如果读取数据的代码遇到出现在作者模式中但不在读者模式中的字段,则忽略它。如果读取数据的代码需要某个字段,但是作者的模式不包含该名称的字段,则使用在读者模式中声明的默认值填充。

图 4-6 一个 Avro Reader 解决读写模式的差异

模式演变规则

使用 Avro,向前兼容性意味着您可以将新版本的架构作为编写器,并将旧版本的架构作为读者。相反,向后兼容意味着你可以有一个作为读者的新版本的模式和作为作者的旧版本。

为了保持兼容性,您只能添加或删除具有默认值的字段。 (我们的 Avro 模式中的字段 favourNumber 的默认值为 null )。例如,假设您添加一个默认值的字段,所以这个新的字段存在于新的模式中,而不是旧的。当使用新模式的阅读器读取使用旧模式写入的记录时,将为缺少的字段填充默认值。

如果你要添加一个没有默认值的字段,新的阅读器将无法读取旧作者写的数据,所以你会破坏向后兼容性。如果您要删除没有默认值的字段,旧的阅读器将无法读取新作者写入的数据,因此您会打破兼容性。在一些编程语言中,null 是任何变量可以接受的默认值,但在 Avro 中并不是这样:如果要允许一个字段为 null ,则必须使用联合类型。例如, union {null,long,string} 字段;表示该字段可以是数字或字符串,也可以是 null 。如果它是 union 的分支之一,那么只能使用 null 作为默认值iv 。这比默认情况下可以为 null 是更加冗长的,但是通过明确什么可以和不可以是什么,有助于防止错误的 null 【22】。

iv. 确切地说,默认值必须是联合的第一个分支的类型,尽管这是 Avro 的特定限制,而不是联合类型的一般特征。 ↩

因此,Avro 没有像 Protocol Buffers 和 Thrift 那样的 optionalrequired 标记(它有联合类型和默认值)。

只要 Avro 可以转换类型,就可以改变字段的数据类型。更改字段的名称是可能的,但有点棘手:读者的模式可以包含字段名称的别名,所以它可以匹配旧作家的模式字段名称与别名。这意味着更改字段名称是向后兼容的,但不能向前兼容。同样,向联合类型添加分支也是向后兼容的,但不能向前兼容。

但作者模式到底是什么?

到目前为止,我们已经讨论了一个重要的问题:读者如何知道作者的模式是哪一部分数据被编码的?我们不能只将整个模式包括在每个记录中,因为模式可能比编码的数据大得多,从而使二进制编码节省的所有空间都是徒劳的。 答案取决于 Avro 使用的上下文。举几个例子:

  • 有很多记录的大文件

    Avro 的一个常见用途 - 尤其是在 Hadoop 环境中 - 用于存储包含数百万条记录的大文件,所有记录都使用相同的模式进行编码。 (我们将在 第 10 章 讨论这种情况。)在这种情况下,该文件的作者可以在文件的开头只包含一次作者的模式。 Avro 指定一个文件格式(对象容器文件)来做到这一点。

  • 支持独立写入的记录的数据库

    在一个数据库中,不同的记录可能会在不同的时间点使用不同的作者的模式编写 - 你不能假定所有的记录都有相同的模式。最简单的解决方案是在每个编码记录的开始处包含一个版本号,并在数据库中保留一个模式版本列表。读者可以获取记录,提取版本号,然后从数据库中获取该版本号的作者模式。使用该作者的模式,它可以解码记录的其余部分。 (例如 Espresso 【23】就是这样工作的。)

  • 通过网络连接发送记录

    当两个进程通过双向网络连接进行通信时,他们可以在连接设置上协商模式版本,然后在连接的生命周期中使用该模式。 Avro RPC 协议(参阅 通过服务的数据流:REST 和 RPC )如此工作。

具有模式版本的数据库在任何情况下都是非常有用的,因为它充当文档并为您提供了检查模式兼容性的机会【24】。作为版本号,你可以使用一个简单的递增整数,或者你可以使用模式的散列。

动态生成的模式

与 Protocol Buffers 和 Thrift 相比,Avro 方法的一个优点是架构不包含任何标签号码。但为什么这很重要?在模式中保留一些数字有什么问题?

不同之处在于 Avro 对动态生成的模式更友善。例如,假如你有一个关系数据库,你想要把它的内容转储到一个文件中,并且你想使用二进制格式来避免前面提到的文本格式(JSON,CSV,SQL)的问题。如果你使用 Avro,你可以很容易地从关系模式生成一个 Avro 模式(在我们之前看到的 JSON 表示中),并使用该模式对数据库内容进行编码,并将其全部转储到 Avro 对象容器文件【25】中。您为每个数据库表生成一个记录模式,每个列成为该记录中的一个字段。数据库中的列名称映射到 Avro 中的字段名称。

现在,如果数据库模式发生变化(例如,一个表中添加了一列,删除了一列),则可以从更新的数据库模式生成新的 Avro 模式,并在新的 Avro 模式中导出数据。数据导出过程不需要注意模式的改变 - 每次运行时都可以简单地进行模式转换。任何读取新数据文件的人都会看到记录的字段已经改变,但是由于字段是通过名字来标识的,所以更新的作者的模式仍然可以与旧的读者模式匹配。

相比之下,如果您为此使用 Thrift 或 Protocol Buffers,则字段标记可能必须手动分配:每次数据库模式更改时,管理员都必须手动更新从数据库列名到字段标签。 (这可能会自动化,但模式生成器必须非常小心,不要分配以前使用的字段标记。)这种动态生成的模式根本不是 Thrift 或 Protocol Buffers 的设计目标,而是为 Avro。

代码生成和动态类型的语言

Thrift 和 Protobuf 依赖于代码生成:在定义了模式之后,可以使用您选择的编程语言生成实现此模式的代码。这在 Java,C ++或 C#等静态类型语言中很有用,因为它允许将高效的内存中结构用于解码的数据,并且在编写访问数据结构的程序时允许在 IDE 中进行类型检查和自动完成。

在动态类型编程语言(如 JavaScript,Ruby 或 Python)中,生成代码没有太多意义,因为没有编译时类型检查器来满足。代码生成在这些语言中经常被忽视,因为它们避免了明确的编译步骤。而且,对于动态生成的模式(例如从数据库表生成的 Avro 模式),代码生成对获取数据是一个不必要的障碍。

Avro 为静态类型编程语言提供了可选的代码生成功能,但是它也可以在不生成任何代码的情况下使用。如果你有一个对象容器文件(它嵌入了作者的模式),你可以简单地使用 Avro 库打开它,并以与查看 JSON 文件相同的方式查看数据。该文件是自描述的,因为它包含所有必要的元数据。

这个属性特别适用于动态类型的数据处理语言如 Apache Pig 【26】。在 Pig 中,您可以打开一些 Avro 文件,开始分析它们,并编写派生数据集以 Avro 格式输出文件,而无需考虑模式。

模式的优点

正如我们所看到的,Protocol Buffers,Thrift 和 Avro 都使用模式来描述二进制编码格式。他们的模式语言比 XML 模式或者 JSON 模式简单得多,它支持更详细的验证规则(例如, 该字段的字符串值必须与该正则表达式匹配 或 该字段的整数值必须在 0 和 100 之间 )。由于 Protocol Buffers,Thrift 和 Avro 实现起来更简单,使用起来也更简单,所以它们已经发展到支持相当广泛的编程语言。

这些编码所基于的想法绝不是新的。例如,它们与 ASN.1 有很多相似之处,它是 1984 年首次被标准化的模式定义语言【27】。它被用来定义各种网络协议,其二进制编码(DER)仍然被用于编码 SSL 证书(X.509),例如【28】。 ASN.1 支持使用标签号码的模式演进,类似于 Protocol Buf-fers 和 Thrift 【29】。然而,这也是非常复杂和严重的文件记录,所以 ASN.1 可能不是新应用程序的好选择。

许多数据系统也为其数据实现某种专有的二进制编码。例如,大多数关系数据库都有一个网络协议,您可以通过该协议向数据库发送查询并获取响应。这些协议通常特定于特定的数据库,并且数据库供应商提供将来自数据库的网络协议的响应解码为内存数据结构的驱动程序(例如使用 ODBC 或 JDBC API)。

所以,我们可以看到,尽管 JSON,XML 和 CSV 等文本数据格式非常普遍,但基于模式的二进制编码也是一个可行的选择。他们有一些很好的属性:

  • 它们可以比各种 二进制 JSON 变体更紧凑,因为它们可以省略编码数据中的字段名称。
  • 模式是一种有价值的文档形式,因为模式是解码所必需的,所以可以确定它是最新的(而手动维护的文档可能很容易偏离现实)。
  • 保留模式数据库允许您在部署任何内容之前检查模式更改的向前和向后兼容性。
  • 对于静态类型编程语言的用户来说,从模式生成代码的能力是有用的,因为它可以在编译时进行类型检查。

总而言之,模式进化允许与 JSON 数据库提供的无模式/模式读取相同的灵活性(请参阅第 39 页的 文档模型中的模式灵活性 ),同时还可以更好地保证数据和更好的工具。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文