注:书本链接:GitHub - Vonng/ddia: 《Designing Data-Intensive Application》DDIA中文翻译
注:译者文本网站:简介 · ddia-cn
注:如有侵权,请联系删除
第四章:编码与演化
当数据 格式(format) 或 模式(schema) 发生变化时,通常需要对应用程序代码进行相应的更改(例如,为记录添加新字段,然后修改程序开始读写该字段)。但在大型应用程序中,代码变更通常不会立即完成:
- 对于 服务端(server-side) 应用程序,可能需要执行 滚动升级 (rolling upgrade) (也称为 阶段发布(staged rollout) ),一次将新版本部署到少数几个节点,检查新版本是否运行正常,然后逐渐部完所有的节点。这样无需中断服务即可部署新版本,为频繁发布提供了可行性,从而带来更好的可演化性。
- 对于 客户端(client-side) 应用程序,升不升级就要看用户的心情了。用户可能相当长一段时间里都不会去升级软件。
这意味着,新旧版本的代码,以及新旧数据格式可能会在系统中同时共处。系统想要继续顺利运行,就需要保持 双向兼容性:
-
向后兼容 (backward compatibility)
新的代码可以读取由旧的代码写入的数据。
-
向前兼容 (forward compatibility)
旧的代码可以读取由新的代码写入的数据。
向后兼容性通常并不难实现:新代码的作者当然知道由旧代码使用的数据格式,因此可以显示地处理它(最简单的办法是,保留旧代码即可读取旧数据)。
一、编码数据的格式
- 从内存中表示到字节序列的转换称为 编码(Encoding) (也称为 序列化(serialization) 或 编组(marshalling)),反过来称为 解码(Decoding)[^ii](解析(Parsing),反序列化(deserialization),反编组(unmarshalling))
1、语言的特定格式
许多编程语言都内建了将内存对象编码为字节序列的支持。例如,Java 有 java.io.Serializable
【1】,Ruby 有 Marshal
【2】,Python 有 pickle
【3】,等等。许多第三方库也存在,例如 Kryo for Java
【4】。
2、JSON、XML和二进制变体
JSON,XML 和 CSV 属于文本格式,因此具有人类可读性(尽管它们的语法是一个热门争议话题)。除了表面的语法问题之外,它们也存在一些微妙的问题:
- 数字(numbers) 编码有很多模糊之处。
- 不支持二进制数据(即不带 字符编码(character encoding) 的字节序列)。
- XML 【11】和 JSON 【12】都有可选的模式支持。
- CSV 没有任何模式,因此每行和每列的含义完全由应用程序自行定义。
(1)二进制编码
3、Thrift与Protocol Buffers
接口定义语言(IDL) 来描述模式
需要注意的一个细节:在前面所示的模式中,每个字段被标记为必需或可选,但是这对字段如何编码没有任何影响(二进制数据中没有任何字段指示某字段是否必须)。区别在于,如果字段设置为 required
,但未设置该字段,则所需的运行时检查将失败,这对于捕获错误非常有用。
(1)字段标签和模式演变
为了保持向后兼容性,在模式的初始部署之后 添加的每个字段必须是可选的或具有默认值。
(2)数据类型和模式演变
4、Avro
(1)Writer模式与Reader模式
(2)模式演变规则
(3)但Writer模式到底是什么?
(4)动态生成的模式
(5)代码生成和动态类型的语言
5、模式的优点
二、数据流的类型
数据如何在流程之间流动的一些最常见的方式:
- 通过数据库
- 通过服务调用
- 通过异步消息传递
4、数据库中的数据流
(1)在不同的时间写入不同的值
(2)归档存储
5、服务中的数据流:REST与RPC
服务器本身可以是另一个服务的客户端(例如,典型的 Web 应用服务器充当数据库的客户端)。这种方法通常用于将大型应用程序按照功能区域分解为较小的服务,这样当一个服务需要来自另一个服务的某些功能或数据时,就会向另一个服务发出请求。这种构建应用程序的方式传统上被称为 面向服务的体系结构(service-oriented architecture,SOA),最近被改进和更名为 微服务架构。
(1)web服务
(2)远程过程调用(RPC)的问题
Enterprise JavaBeans(EJB)和 Java 的 远程方法调用(RMI) 仅限于 Java。分布式组件对象模型(DCOM) 仅限于 Microsoft 平台。公共对象请求代理体系结构(CORBA) 过于复杂,不提供向后或向前兼容性【41】。
所有这些都是基于 远程过程调用(RPC) 的思想,该过程调用自 20 世纪 70 年代以来一直存在【42】。RPC 模型试图向远程网络服务发出请求,看起来与在同一进程中调用编程语言中的函数或方法相同(这种抽象称为位置透明)
(3)RPC的当前方向
(4)数据编码与RPC的演化
6、消息传递中的数据流
与直接 RPC 相比,使用消息代理有几个优点:
- 如果收件人不可用或过载,可以充当缓冲区,从而提高系统的可靠性。
- 它可以自动将消息重新发送到已经崩溃的进程,从而防止消息丢失。
- 避免发件人需要知道收件人的 IP 地址和端口号(这在虚拟机经常出入的云部署中特别有用)。
- 它允许将一条消息发送给多个收件人。
- 将发件人与收件人逻辑分离(发件人只是发布邮件,不关心使用者)。
(1)消息代理(Message Broker)
(2)分布式的Actor框架
本章小结
在本章中,我们研究了将数据结构转换为网络中的字节或磁盘上的字节的几种方法。我们看到了这些编码的细节不仅影响其效率,更重要的是也影响了应用程序的体系结构和部署它们的选项。
特别是,许多服务需要支持滚动升级,其中新版本的服务逐步部署到少数节点,而不是同时部署到所有节点。滚动升级允许在不停机的情况下发布新版本的服务(从而鼓励在罕见的大型版本上频繁发布小型版本),并使部署风险降低(允许在影响大量用户之前检测并回滚有故障的版本)。这些属性对于可演化性,以及对应用程序进行更改的容易性都是非常有利的。
在滚动升级期间,或出于各种其他原因,我们必须假设不同的节点正在运行我们的应用程序代码的不同版本。因此,在系统周围流动的所有数据都是以提供向后兼容性(新代码可以读取旧数据)和向前兼容性(旧代码可以读取新数据)的方式进行编码是重要的。
我们讨论了几种数据编码格式及其兼容性属性:
- 编程语言特定的编码仅限于单一编程语言,并且往往无法提供向前和向后兼容性。
- JSON、XML 和 CSV 等文本格式非常普遍,其兼容性取决于你如何使用它们。他们有可选的模式语言,这有时是有用的,有时是一个障碍。这些格式对于数据类型有些模糊,所以你必须小心数字和二进制字符串。
- 像 Thrift、Protocol Buffers 和 Avro 这样的二进制模式驱动格式允许使用清晰定义的向前和向后兼容性语义进行紧凑、高效的编码。这些模式可以用于静态类型语言的文档和代码生成。但是,他们有一个缺点,就是在数据可读之前需要对数据进行解码。
我们还讨论了数据流的几种模式,说明了数据编码重要性的不同场景:
- 数据库,写入数据库的进程对数据进行编码,并从数据库读取进程对其进行解码
- RPC 和 REST API,客户端对请求进行编码,服务器对请求进行解码并对响应进行编码,客户端最终对响应进行解码
- 异步消息传递(使用消息代理或参与者),其中节点之间通过发送消息进行通信,消息由发送者编码并由接收者解码
我们可以小心地得出这样的结论:向后/向前兼容性和滚动升级在某种程度上是可以实现的。愿你的应用程序的演变迅速、敏捷部署。