修复第 4 章有歧义的内容

This commit is contained in:
fuxuemingzhu 2021-10-09 12:11:43 +08:00
parent cc49c41fdd
commit 0e3b73a951

18
ch4.md
View File

@ -154,7 +154,7 @@ Thrift和Protocol Buffers每一个都带有一个代码生成工具它采用
与[图4-1](img/fig4-1.png)相比,最大的区别是没有字段名`(userName, favoriteNumber, interests)`。相反,编码数据包含字段标签,它们是数字`(1, 2和3)`。这些是模式定义中出现的数字。字段标记就像字段的别名 - 它们是说我们正在谈论的字段的一种紧凑的方式,而不必拼出字段名称。
Thrift CompactProtocol编码在语义上等同于BinaryProtocol但是如[图4-3](img/fig4-3.png)所示它只将相同的信息打包成只有34个字节。它通过将字段类型和标签号打包到单个字节中并使用可变长度整数来实现。数字1337不是使用全部八个字节而是用两个字节编码每个字节的最高位用来指示是否还有更多的字节。这意味着-64到63之间的数字被编码为一个字节-8192和8191之间的数字以两个字节编码等等。较大的数字使用更多的字节。
Thrift CompactProtocol编码在语义上等同于BinaryProtocol但是如[图4-3](img/fig4-3.png)所示它只将相同的信息打包成只有34个字节。它通过将字段类型和标签号打包到单个字节中并使用可变长度整数来实现。数字1337不是使用全部八个字节而是用两个字节编码每个字节的最高位用来指示是否还有更多的字节。这意味着-64到63之间的数字被编码为一个字节-8192和8191之间的数字以两个字节编码等等。较大的数字使用更多的字节。
![](img/fig4-3.png)
@ -166,7 +166,7 @@ Thrift CompactProtocol编码在语义上等同于BinaryProtocol但是如[图4
**图4-4 使用Protobuf编码的记录**
需要注意的一个细节:在前面所示的模式中,每个字段被标记为必需或可选,但是这对字段如何编码没有任何影响(二进制数据中没有任何字段指示是否需要字段)。所不同的是,如果未设置该字段,则所需的运行时检查将失败,这对于捕获错误非常有用。
需要注意的一个细节:在前面所示的模式中,每个字段被标记为必需或可选,但是这对字段如何编码没有任何影响(二进制数据中没有任何字段指示某字段是否必须)。区别在于,如果字段设置为 `required`,但未设置该字段,则所需的运行时检查将失败,这对于捕获错误非常有用。
#### 字段标签和模式演变
@ -174,7 +174,7 @@ Thrift CompactProtocol编码在语义上等同于BinaryProtocol但是如[图4
从示例中可以看出编码的记录就是其编码字段的拼接。每个字段由其标签号码样本模式中的数字1,2,3标识并用数据类型例如字符串或整数注释。如果没有设置字段值则简单地从编码记录中省略。从中可以看到字段标记对编码数据的含义至关重要。您可以更改架构中字段的名称因为编码的数据永远不会引用字段名称但不能更改字段的标记因为这会使所有现有的编码数据无效。
您可以添加新的字段到架构,只要您给每个字段一个新的标签号码。如果旧的代码(不知道你添加的新的标签号码)试图读取新代码写入的数据,包括一个新的字段,其标签号码不能识别,它可以简单地忽略该字段。数据类型注释允许解析器确定需要跳过的字节数。这保持了向兼容性:旧代码可以读取由新代码编写的记录。
您可以添加新的字段到架构,只要您给每个字段一个新的标签号码。如果旧的代码(不知道你添加的新的标签号码)试图读取新代码写入的数据,包括一个新的字段,其标签号码不能识别,它可以简单地忽略该字段。数据类型注释允许解析器确定需要跳过的字节数。这保持了向兼容性:旧代码可以读取由新代码编写的记录。
向后兼容性呢?只要每个字段都有一个唯一的标签号码,新的代码总是可以读取旧的数据,因为标签号码仍然具有相同的含义。唯一的细节是,如果你添加一个新的字段,你不能设置为必需。如果您要添加一个字段并将其设置为必需,那么如果新代码读取旧代码写入的数据,则该检查将失败,因为旧代码不会写入您添加的新字段。因此,为了保持向后兼容性,在模式的初始部署之后 **添加的每个字段必须是可选的或具有默认值**
@ -184,13 +184,13 @@ Thrift CompactProtocol编码在语义上等同于BinaryProtocol但是如[图4
如何改变字段的数据类型这也许是可能的——详细信息请查阅相关的文档——但是有一个风险值将失去精度或被截断。例如假设你将一个32位的整数变成一个64位的整数。新代码可以轻松读取旧代码写入的数据因为解析器可以用零填充任何缺失的位。但是如果旧代码读取由新代码写入的数据则旧代码仍使用32位变量来保存该值。如果解码的64位值不适合32位则它将被截断。
Protobuf的一个奇怪的细节是它没有列表或数组数据类型而是有一个字段的重复标记这是除必需和可选之外的第三个选项。如[图4-4](img/fig4-4.png)所示,重复字段的编码正如它所说的那样:同一个字段标记只是简单地出现在记录中。这具有很好的效果,可以将可选(单值)字段更改为重复(多值)字段。读取旧数据的新代码会看到一个包含零个或一个元素的列表(取决于该字段是否存在)。读取新数据的旧代码只能看到列表的最后一个元素。
Protobuf的一个奇怪的细节是它没有列表或数组数据类型而是有一个字段的重复标记`repeated`这是除必需和可选之外的第三个选项)。如[图4-4](img/fig4-4.png)所示,重复字段的编码正如它所说的那样:同一个字段标记只是简单地出现在记录中。这具有很好的效果,可以将可选(单值)字段更改为重复(多值)字段。读取旧数据的新代码会看到一个包含零个或一个元素的列表(取决于该字段是否存在)。读取新数据的旧代码只能看到列表的最后一个元素。
Thrift有一个专用的列表数据类型它使用列表元素的数据类型进行参数化。这不允许Protocol Buffers所做的从单值到多值的演变但是它具有支持嵌套列表的优点。
### Avro
Apache Avro 【20】是另一种二进制编码格式与Protocol Buffers和Thrift有趣的不同。 它是作为Hadoop的一个子项目在2009年开始的因为Thrift不适合Hadoop的用例【21】。
Apache Avro 【20】是另一种二进制编码格式与Protocol Buffers和Thrift有着有趣的不同。 它是作为Hadoop的一个子项目在2009年开始的因为Thrift不适合Hadoop的用例【21】。
Avro也使用模式来指定正在编码的数据的结构。 它有两种模式语言一种Avro IDL用于人工编辑一种基于JSON更易于机器读取。
@ -292,7 +292,7 @@ Avro的关键思想是Writer模式和Reader模式不必是相同的 - 他们只
Thrift和Protobuf依赖于代码生成在定义了模式之后可以使用您选择的编程语言生成实现此模式的代码。这在JavaC ++或C等静态类型语言中很有用因为它允许将高效的内存中结构用于解码的数据并且在编写访问数据结构的程序时允许在IDE中进行类型检查和自动完成。
在动态类型编程语言如JavaScriptRuby或Python生成代码没有太多意义因为没有编译时类型检查器来满足。代码生成在这些语言中经常被忽视因为它们避免了显的编译步骤。而且对于动态生成的模式例如从数据库表生成的Avro模式代码生成对获取数据是一个不必要的障碍。
在动态类型编程语言如JavaScriptRuby或Python生成代码没有太多意义因为没有编译时类型检查器来满足。代码生成在这些语言中经常被忽视因为它们避免了显的编译步骤。而且对于动态生成的模式例如从数据库表生成的Avro模式代码生成对获取数据是一个不必要的障碍。
Avro为静态类型编程语言提供了可选的代码生成功能但是它也可以在不生成任何代码的情况下使用。如果你有一个对象容器文件它嵌入了Writer模式你可以简单地使用Avro库打开它并以与查看JSON文件相同的方式查看数据。该文件是自描述的因为它包含所有必要的元数据。
@ -425,7 +425,7 @@ Web服务仅仅是通过网络进行API请求的一系列技术的最新版本
* 每次调用本地功能时,通常需要大致相同的时间来执行。网络请求比函数调用要慢得多,而且其延迟也是非常可变的:好的时候它可能会在不到一毫秒的时间内完成,但是当网络拥塞或者远程服务超载时,可能需要几秒钟的时间完成一样的东西。
* 调用本地函数时,可以高效地将引用(指针)传递给本地内存中的对象。当你发出一个网络请求时,所有这些参数都需要被编码成可以通过网络发送的一系列字节。如果参数是像数字或字符串这样的基本类型倒是没关系,但是对于较大的对象很快就会变成问题。
客户端和服务可以用不同的编程语言实现所以RPC框架必须将数据类型从一种语言翻译成另一种语言。这可能会捅出大篓子因为不是所有的语言都具有相同的类型 —— 例如回想一下JavaScript的数字大于$2^{53}$的问题(请参阅“[JSONXML和二进制变体](#JSONXML和二进制变体)”)。用单一语言编写的单个进程中不存在此问题。
- 客户端和服务可以用不同的编程语言实现所以RPC框架必须将数据类型从一种语言翻译成另一种语言。这可能会捅出大篓子因为不是所有的语言都具有相同的类型 —— 例如回想一下JavaScript的数字大于$2^{53}$的问题(请参阅“[JSONXML和二进制变体](#JSONXML和二进制变体)”)。用单一语言编写的单个进程中不存在此问题。
所有这些因素意味着尝试使远程服务看起来像编程语言中的本地对象一样毫无意义,因为这是一个根本不同的事情。 REST的部分吸引力在于它并不试图隐藏它是一个网络协议的事实尽管这似乎并没有阻止人们在REST之上构建RPC库
@ -479,13 +479,13 @@ RPC方案的前后向兼容性属性从它使用的编码方式中继承
一个主题只提供单向数据流。但是,消费者本身可能会将消息发布到另一个主题上(因此,可以将它们链接在一起,就像我们将在[第十一章](ch11.md)中看到的那样),或者发送给原始消息的发送者使用的回复队列(允许请求/响应数据流类似于RPC
消息代理通常不会执行任何特定的数据模型 - 消息只是包含一些元数据的字节序列,因此您可以使用任何编码格式。如果编码是向后和向前兼容的,您可以灵活地对发布者和消费者的编码进行独立的修改,并以任意顺序进行部署。
消息代理通常不会执行任何特定的数据模型 —— 消息只是包含一些元数据的字节序列,因此您可以使用任何编码格式。如果编码是向后和向前兼容的,您可以灵活地对发布者和消费者的编码进行独立的修改,并以任意顺序进行部署。
如果消费者重新发布消息到另一个主题,则可能需要小心保留未知字段,以防止前面在数据库环境中描述的问题([图4-7](img/fig4-7.png))。
#### 分布式的Actor框架
Actor模型是单个进程中并发的编程模型。逻辑被封装在actor中而不是直接处理线程以及竞争条件锁定和死锁的相关问题。每个actor通常代表一个客户或实体它可能有一些本地状态不与其他任何角色共享它通过发送和接收异步消息与其他角色通信。消息传送不保证:在某些错误情况下,消息将丢失。由于每个角色一次只能处理一条消息,因此不需要担心线程,每个角色可以由框架独立调度。
Actor模型是单个进程中并发的编程模型。逻辑被封装在actor中而不是直接处理线程以及竞争条件锁定和死锁的相关问题。每个actor通常代表一个客户或实体它可能有一些本地状态不与其他任何角色共享它通过发送和接收异步消息与其他角色通信。不保证消息传送:在某些错误情况下,消息将丢失。由于每个角色一次只能处理一条消息,因此不需要担心线程,每个角色可以由框架独立调度。
在分布式Actor框架中此编程模型用于跨多个节点伸缩应用程序。不管发送方和接收方是在同一个节点上还是在不同的节点上都使用相同的消息传递机制。如果它们在不同的节点上则该消息被透明地编码成字节序列通过网络发送并在另一侧解码。