为什么要使用Thrift与Protocol Buffers?

news2024/12/24 20:19:52

编码数据的格式

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

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

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

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

语言特定的格式

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

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

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

JSON、XML和二进制变体

当我们谈到可以被多种编程语言读写的标准编码时,JSON 和 XML 是最显眼的角逐者。

它们广为人知,广受支持,也 “广受憎恶”。 XML 经常收到批评:过于冗长与且过份复杂。 JSON 的流行则主要源于(通过成为 JavaScript 的一个子集)Web 浏览器的内置支持,以及相对于 XML 的简单性。 CSV 是另一种流行的与语言无关的格式,尽管其功能相对较弱

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

数字(numbers) 编码有很多模糊之处。在 XML 和 CSV 中,无法区分数字和碰巧由数字组成的字符串(除了引用外部模式)。 JSON 虽然区分字符串与数字,但并不区分整数和浮点数,并且不能指定精度。这在处理大数字时是个问题。例如大于 2^53的整数无法使用 IEEE 754 双精度浮点数精确表示,因此在使用浮点数(例如 JavaScript)的语言进行分析时,这些数字会变得不准确。 Twitter 有一个关于大于 2^53的数字的例子,它使用 64 位整数来标识每条推文。 Twitter API 返回的 JSON 包含了两个推特 ID,一个是 JSON 数字,另一个是十进制字符串,以解决 JavaScript 程序中无法正确解析数字的问题。

JSON 和 XML 对 Unicode 字符串(即人类可读的文本)有很好的支持,但是它们不支持二进制数据(即不带 字符编码(character encoding) 的字节序列)。二进制串是很有用的功能,人们通过使用 Base64 将二进制数据编码为文本来绕过此限制。其特有的模式标识着这个值应当被解释为 Base64 编码的二进制数据。这种方案虽然管用,但比较 Hacky,并且会增加三分之一的数据大小。

XML 和 JSON 都有可选的模式支持。这些模式语言相当强大,所以学习和实现起来都相当复杂。 XML 模式的使用相当普遍,但许多基于 JSON 的工具才不会去折腾模式。对数据的正确解读(例如区分数值与二进制串)取决于模式中的信息,因此不使用 XML/JSON 模式的应用程序可能需要对相应的编码 / 解码逻辑进行硬编码。

CSV 没有任何模式,因此每行和每列的含义完全由应用程序自行定义。如果应用程序变更添加了新的行或列,那么这种变更必须通过手工处理。 CSV 也是一个相当模糊的格式(如果一个值包含逗号或换行符,会发生什么?)。尽管其转义规则已经被正式指定,但并不是所有的解析器都正确的实现了标准。

尽管存在这些缺陷,但 JSON、XML 和 CSV 对很多需求来说已经足够好了。它们很可能会继续流行下去,特别是作为数据交换格式来说(即将数据从一个组织发送到另一个组织)。在这种情况下,只要人们对格式是什么意见一致,格式有多美观或者效率有多高效就无所谓了。让不同的组织就这些东西达成一致的难度超过了绝大多数问题。

二进制编码

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

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

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

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

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

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

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

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

在这里插入图片描述

Thrift与Protocol Buffers

Apache Thrift 和 Protocol Buffers(protobuf)是基于相同原理的二进制编码库。 Protocol Buffers 最初是在 Google 开发的,Thrift 最初是在 Facebook 开发的,并且都是在 2007~2008 开源的。 Thrift 和 Protocol Buffers 都需要一个模式来编码任何数据。要用Thrift 的上述json进行编码,可以使用 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 每一个都带有一个代码生成工具,它采用了类似于这里所示的模式定义,并且生成了以各种编程语言实现模式的类。你的应用程序代码可以调用此生成的代码来对模式的记录进行编码或解码。 用这个模式编码的数据是什么样的?令人困惑的是,Thrift 有两种不同的二进制编码格式,分别称为 BinaryProtocol 和 CompactProtocol。先来看看 BinaryProtocol。使用这种格式的编码来编码之前的例子只需要 59 个字节,如下图所示。
在这里插入图片描述
每个字段都有一个类型注释(用于指示它是一个字符串、整数、列表等),还可以根据需要指定长度(字符串的长度,列表中的项目数) 。出现在数据中的字符串 (“Martin”, “daydreaming”, “hacking”) 也被编码为 ASCII(或者说,UTF-8),与之前类似

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

Thrift CompactProtocol 编码在语义上等同于 BinaryProtocol,但是如 下图所示,它只将相同的信息打包成只有 34 个字节它通过将字段类型和标签号打包到单个字节中,并使用可变长度整数来实现。数字 1337 不是使用全部八个字节,而是用两个字节编码,每个字节的最高位用来指示是否还有更多的字节。这意味着 - 64 到 63 之间的数字被编码为一个字节,-8192 和 8191 之间的数字以两个字节编码,等等。较大的数字使用更多的字节。
在这里插入图片描述
最后,Protocol Buffers(只有一种二进制编码格式)对相同的数据进行编码,如下图所示。 它的打包方式稍有不同,但与 Thrift 的 CompactProtocol 非常相似。 Protobuf 将同样的记录塞进了 33 个字节中。
在这里插入图片描述
需要注意的一个细节:在前面所示的模式中,每个字段被标记为必需或可选,但是这对字段如何编码没有任何影响(二进制数据中没有任何字段指示某字段是否必须)。区别在于,如果字段设置为 required,但未设置该字段,则所需的运行时检查将失败,这对于捕获错误非常有用。

字段标签和模式演变

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

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

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

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

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

数据类型和模式演变

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

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

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

模式的优点

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/537080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos7中安装mattermost

centos7中安装mattermost 步骤如下: 第一步安装依赖项&#xff1a;在终端中执行以下命令以安装所需的依赖项 sudo yum install epel-release sudo yum install yum-utils sudo yum install wget第二步&#xff0c;下载Mattermost安装包&#xff1a;执行以下命令以下载Mattermo…

PowerShell Install 二进制方式部署Python3

Python 前言 Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。Python 由 Guido van Rossum 于 1989 年底发明&#xff0c;第一个公开发行版发行于 1991 年。像 Perl 语言一样, Python 源代码同样遵循 GPL(GNU General Public License) 协议。 Python downlo…

经典神经网络(3)Vgg-Net及其在Fashion-MNIST数据集上的应用

经典神经网络(3)VGG_使用块的网络 1 VGG的简述 1.1 VGG的概述 VGG-Net 是牛津大学计算机视觉组和DeepMind公司共同研发一种深度卷积网络&#xff0c;并且在2014年在ILSVRC比赛上获得了分类项目的第二名和定位项目的第一名。通过使⽤循环和⼦程序&#xff0c;可以很容易地在任…

JavaEE(系列6) -- 多线程(解决线程不安全系列1-- 加锁(synchronized)与volatile)

首先我们回顾一下上一章节引起线程不安全的原因 本质原因:线程在系统中的调度是无序的/随机的(抢占式执行) 1.抢占式执行 2.多个线程修改同一个变量. 一个线程修改一个变量>安全 多个线程读取同一个变量>安全 多个线程修改不同的变量>安全 3.修改操作,不是原子的.(最…

容器安装Datax+Datax-web2.1(一)

目录 简介1、安装Datax-web2.1.11&#xff09;安装docker-compose2&#xff09;创建Datax-web和MySQL容器 2、安装Datax-web2.1.21&#xff09;安装MySQL2&#xff09;初始化数据3&#xff09;安装datax和datax-web4&#xff09;浏览器登录 DataxDatax-web2.1实现MySQL数据库数…

LeetCode 21. 合并两个有序链表 | C++语言版

LeetCode 21. 合并两个有序链表 | C语言版 LeetCode 36&#xff1a; 两个链表的第一个公共结点题目描述解题思路思路一&#xff1a;使用前缀和代码实现运行结果参考文章&#xff1a; 思路二&#xff1a;减少遍历节点数代码实现运行结果参考文章&#xff1a; LeetCode 36&#x…

CANoe-如何在Trace窗口显示SYN和FIN报文、同一条以太网报文在Trace窗口中的多条显示

1、如何在Trace窗口显示SYN和FIN报文 当我们使用CANoe实现TCP通信时,希望在Trace窗口直观显示报文的类型:SYN、ACK、FIN。显然Trace窗口也是支持这样的功能的。但很多时候由于一些人为的不正确的设置和配置,造成无法显示。 如果想解析出SYN报文,首先在Trace窗口选择正确的…

5个高清视频素材网站,免费下载~

免费高清视频素材网站&#xff0c;这几个你一定要知道&#xff0c;建议收藏&#xff01; 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYxMjky 菜鸟图库网素材非常丰富&#xff0c;网站主要还是以设计类素材为主&#xff0c;高清视频素材也很多&#xff0c;像风景、…

dcdc降压电路原理及仿真

在之前的文章 DCDC 降压芯片基本原理及选型主要参数介绍 中已经大致讲解了dcdc降压电路的工作原理&#xff0c;今天再结合仿真将buck电路工作过程讲一讲。 基本拓扑 上图为buck电路的基本拓扑结构&#xff0c;开关打到1&#xff0c;电感充电&#xff1b;开关打到0&#xff0c;…

pdf怎么转换成jpg格式的图片,5种方法详细教程

pdf怎么转换成jpg格式的图片&#xff0c;为什么这样做呢&#xff1f;那是因为将PDF转换成JPG格式的主要原因是方便在演示文稿、网页或社交媒体等平台上展示和分享PDF文件的内容。JPG格式具有广泛的兼容性和易于传输的特点&#xff0c;而且可以轻松地进行编辑和调整大小。此外&a…

Java反射简单介绍_01

文章目录 1. 什么是反射2. Java中类加载的三个阶段3. 反射机制提供的相关类4. Java中获取Class类的三种方式5. Class类提供的功能5.1. 获取Field类方法5.2. 获取Method类方法5.3. 获取Constructor类方法5.4. Class中其他方法 1. 什么是反射 Java中的反射主要是体现在运行期间,…

uniapp 用css画五边形(app 小程序)

效果图 css .scoreLabel{ background: $yxs-theme-color; width: 64rpx; height: 69rpx; line-height: 32rpx; font-size: 28rpx; font-family: DINPro; f…

点亮未来明灯,引领绿色革命

随着全球气候变化日趋严重&#xff0c;能源转型成为解决气候问题和提高全球能源安全合理性的必要措施之一。可持续能源技术因其对环境的友好性和可再生性而成为了当前热点话题。可持续能源技术已经成为人们日益关注的焦点。这项技术可以帮助我们减少对化石燃料的依赖&#xff0…

如何选择适合自己的小程序开发框架

随着微信、支付宝等开放平台的壮大&#xff0c;移动应用生态市场的蓬勃发展&#xff0c;例如小程序已经成为各个企业和开发者的重要选择。为了提高小程序的开发效率和代码重用性&#xff0c;许多第三方开发框架应运而生。 准备为需要的朋友整理一些常见的小程序第三方开发框架…

能伸展脖子的机器人?东京大学最新研究成果:基于鸵鸟肌肉骨骼结构和行为,具有高度灵活性的新型机械臂—RobOstrich(附论文)

原创 | 文 BFT机器人 得益于高度灵活的颈部&#xff0c;鸟类可以做很多事情&#xff0c;无论是转过头梳理自己的后背&#xff0c;在飞行过程中“眼观六路”&#xff0c;还是在地面或树上难以触及的角落和缝隙寻找食物。而在所有鸟类中&#xff0c;鸵鸟以其结实灵巧的颈部脱颖而…

顶部菜单栏-popuwindows

效果 布局文件 猜想与步骤 1.通过.9.png 制作尖尖效果&#xff0c; 2.popuwindows弹出布局框 以及灰色背景 3.点击按钮进入不同功能

QT运行程序后出现无法打开输出文件问题

:-1: error: cannot open output file release\Dailin.exe: Permission denied collect2.exe:-1: error: error: ld returned 1 exit status 如上所示报错。这个是因为用户写的程序开辟空间后没有释放造成的。用户需要把开辟了空间释放就可以了。 我的例子如下。 My_Uart *Ua…

【聚焦】“饶派杯”XCTF车联网安全挑战赛即将开启!

为深入贯彻落实国家网络强国和交通强国战略部署&#xff0c;推动智能网联汽车技术与产业发展、加快该领域人才培养、提升行业创新&#xff0c;打造自主可控、自主研发的中国智能网联汽车安全生态体系&#xff0c;由江西省委网信办、江西省工信厅、上饶市人民政府主办&#xff0…

【云渲染案例】《长月烬明》特效出圈?国内9家视效公司联手打造国风新仙侠!丨瑞云渲染案例

仙侠剧是中国电视剧的一大特色&#xff0c;也是很多观众喜爱的类型。近年来&#xff0c;仙侠剧的制作水平越来越高&#xff0c;由鞠觉亮执导&#xff0c;罗云熙、白鹿领衔主演的《长月烬明》就是一部在今年四月份引起了轰动的虐恋仙侠剧&#xff0c;它以炫丽的特效、新颖的剧情…

目前流行的9大前端框架

1. React 2. Vue 3. Angular 、 4. Svelte 官网&#xff1a;https://svelte.dev 中文官网&#xff1a;https://www.sveltejs.cn Svelte 是一种全新的构建用户界面的方法。传统框架如 React 和 Vue 在浏览器中需要做大量的工作&#xff0c;而 Svelte 将这些工作放到构建应用程…