数据存储-编码与演化

应用程序更改时可能会改变数据库,此时使用滚动升级(阶段升级)时新旧数据格式会在系统中同时共处,系统想要顺利运行就需要保证双向兼容性

向前兼容:旧代码可以读取新数据

向后兼容:新代码可以读取旧数据

向前兼容比较棘手,因为需要忽略掉新数据格式中新增的那部分。

1 编码数据的格式

若要将数据写入文件或者由网络发送,则必须将其编码(Encoding)为某种子包含的字节序列(例如JSON),而从字节序列到内存中表示的过程称为解码(Decoding)

1.1 语言特定的格式

许多编程语言都内建了将内存对象编码为字节序列的支持,例如java的java.id.Serializable。但是这会导致这类编码与编程语言的深度绑定,并且解码过程中实例化任意类的能力会导致安全问题,效率也低,所以通常不使用语言内置的编码。

1.2 JSON,XML和二进制变体

JSON、XML和CSV是文本格式,具有人类可读性,他们作为数据交换格式非常受欢迎,但是他们都有各自的问题:

  • XML太过冗长和复杂
  • XML和CSB不能区分数字和字符串,JSON不能区分整数和浮点数且不能指定精度
  • JSON和XML对Unicode字符串有很好的支持,但是他们不支持二进制数据。
  • CSV没有任何模式,因此程序需要定义每行和每列的含义。

二进制编码更紧凑且解析更快,数据量大时会发挥更大的优势。这导致了二进制编码版本的JSON与XML的出现(BSON、BJSON、WBXML等)。

1.3 Thrift与Protocol Buffers

Apache Thirft 与 protobuf 是基于相同原理的二进制编码库。

他们都带有一个代码生成工具,生成了以各种编程语言实现模式的类,我们的代码可以调用生成的代码对模式的记录进行编码和解码。

编码的记录就是其编码字段的拼接,每个字段由其标签号码标识并用数据类型注释。如果没有设置字段值,则简单的从编码记录中省略。由此,字段标记对编码数据的含义至关重要,字段名可以随意更改因为编码数据永远不会引用字段名,但是更改字段标记会使所有现有的编码数据无效。

模式演变

模式演变需要我们保证向前&向后兼容性

当我们进行模式演变时,可以添加新的标签号码从而添加新的字段到架构中。如果旧的代码试图读取新代码写入的数据,其标签号码不能识别,所以就被忽略了。同时数据类型的注释允许解析器确定需要跳过的字节数,这就保持了向前兼容性。

只要每个字段都有一个唯一的标签号码,那么新代码就总是可以读取到旧的数据,因为标签号码仍然具有相同的含义。但是当我们将一个新添加的字段设置为必须,那么当新代码读入旧代码的数据时将读不到。因此,后来增加的每个字段必须是非必须的的或具有默认值。

删除字段时,我们只能删除一个非必填的字段,并且不能再次使用相同的标签号码。

当改变字段的数据类型时,值有可能失去精度或被扼杀(例如32变64位)

1.4 Avro

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

Avro的字节序列中没有可识别字段和数据类型,编码只是由连在一起的值组成,这使其占用空间更小。解析时,需要按照它们在架构中的顺序遍历这些字段,并告诉架构我每个字段的数据类型,这意味着阅读器与作者之间的模式不匹配会导致解码错误。

作者模式与读者模式

编码的就是作者,解码的就是读者。

作者和读者的模式不必相同,他们只需要兼容即可。例如作者和读者的模式的字段顺序不同是被允许的,因为模式解析通过字段名解析字段。作者模式中有读者模式里没有的字段会被忽略,反之则用默认值填充。

模式演变

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

为了保持兼容性,我们只能添加或删除具有默认值的字段,使用新模式的阅读器读取旧模式写入的记录时,才不会缺少默认值,才能保持向后兼容性。当我们删除没有默认值的字段时,旧阅读器将无法读取新作者写的数据,而打破向前兼容性。

Avro中,null不是所有变量都能接受的默认值。当我们要使用null作为默认值时必须使用联合类型,例如 union{null,string}表示该字段可以是null或者字符串。

当我们改变数据类型时读者的旧模式还是能匹配作家的心模式,所以字段不向前兼容。同样,向联合类型添加分支也不能向前兼容。

动态生成的模式

与其他二进制方法相比,Avro方法的一个优点是架构不包含标签号码,这导致了Avro对动态生成的模式更加友善。

这使得Avro能更容易的从关系模式(关系型数据库)生成一个Avro模式(在我们之前看到的JSON表示中),并使用该模式对数据库内容进行编码,并将其全部转储到Avro对象容器文件中。

当数据库模式发生变化后,则可以从更新的数据库模式生成新的Avro模式并导出数据,数据导出过程中不需要注意模式的改变因为每次运行时都可以简单的进行模式转换。由于字段是通过名字来标识的,所以更新的作者和模式仍然可以和旧的读者模式匹配。

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

Thrift和Protobuf依赖代码生成,这在静态语言中很有用,它允许将高效的内存中结构用于解码的数据,并且编写访问数据结构的程序时允许在IDE中进行类型检查和自动完成。

在动态类型的编程语言中,生成代码没有意义,因为没有编译时的类型检查器来满足。

Avro可以代码生成,同时也能在不生成代码时使用。当我们有一个嵌入了作者模式的对象容器文件时就可以哟Avro库打开它,这样就能以查看JSON文件相同的方式查看该数据。这个属性非常适用于动态类型的数据处理语言(如Apache pig)

1.5 模式的优点

尽管JSON、XML和CSV等文本数据格式非常普遍,但基于模式的二进制代码也是一个可行的选择,它有以下优点:

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

2 数据流的类型

2.1 数据库中的数据流

在数据库中,写入数据库的过程对数据进行编码,从数据库读取的过程对数据进行解码。

向后兼容性显然是必要的。而当版本滚动升级时数据库中一个值可能被新版本的代码写入,然后被旧版本读出,因此数据库一般也需要向前兼容。

归档存储

定期为数据库创建一个快照用于备份,此时即使数据库中包含不同时代的模式的数据,数据转储也通常使用最新模式进行编码。

由于数据转存是一次写入且不改变的,此时用Avro对象容器文件等格式非常合适。

2.2 服务中的数据流:REST与RPC

服务器通过网络公开API,并且客户端可以连接到服务器以向该API发出请求。服务器公开的API被称为服务。

客户端向Web服务器发出请求时GET请求下载HTML等,并向POST请求提交数据到服务器,而在移动端时通常只传递编码数据(如JSON)。尽管HTTP可能被用作传输协议,但顶层实现的API是特定于应用程序的,客户端和服务器需要就该API的细节达成一致。

服务器本身是另一个服务的客户端,这个使得大型应用程序能按照功能区域分解为较小的服务,当服务需要来自另一个服务的某些功能或数据时,就会向另一个服务发出请求,这种构建应用的方式称为面向服务的体系架构(SOA) ,即微服务架构

某些方面,服务类似于数据库,他们允许客户端的提交和查询数据,但是它做了一定程度的封装,于是服务可以对客户能做什么和不能做什么施加细粒度的限制。

微服务架构的一个关键设计目标是通过使服务独立部署和演化来使应用程序更易于更改和维护。团队发布新版本服务时不必与其他团队协调。

Web服务

当服务使用HTTP作为底层通信协议时,可称之为Web服务。Web服务不仅在Web上使用,而且能在几个不同的环境中使用。

  1. 运行在用户设备上的客户端应用程序,通过HTTP发送请求。
  2. 一种服务向同一组织拥有的另一项服务提出请求,这些服务通常位于同一数据中心内,作为面向服务/微型架构的一部分。 (支持这种用例的软件有时被称为 中间件(middleware)
  3. 一种服务通过互联网向不同组织所拥有的服务提出请求。这用于不同组织后端系统之间的数据交换。此类别包括由在线服务(如信用卡处理系统)提供的公共API,或用于共享访问用户数据的OAuth。

REST和SOAP:

  • REST:REST不是一个协议,而是一个基于HTTP原则的设计哲学。强调简单的数据格式,使用RTL来标识资源,使用HTTP功能进行缓存控制,身份验证和内容类型协商。目前越来越受欢迎。根据REST原则设计的API称为RESTful。
  • SOAP:SOAP是用于制作网络API请求的基于XML的协议(SOAP和SOA不一样,SOA是构建系统的一般方法)。它常用且独立于于HTTP,有很多相关标准增加了许多功能。

SOAP Web服务的API使用称为Web服务描述语言(WSDL)的基于XML的语言来描述。 WSDL支持代码生成,客户端可以使用本地类和方法调用(编码为XML消息并由框架再次解码)访问远程服务。这在静态类型编程语言中非常有用,但在动态类型编程语言中很少

RPC的问题

基于远程过程调用(RPC)思想,RPC模型试图向远程网络服务发出请求,让本地函数调用远程函数就像调用通过进程中的函数一样(这种抽象称为位置透明)。

然而,网络请求和本地函数调用其实非常不同:

  • 网络问题会影响调用
  • 网络请求可能超时,此时本地无法获取具体情况
  • 发送的对象较大会造成网络压力
RPC的方向

新一代的RPC框架更加明确了远程请求和本地调用函数的不同。其中一些框架还提供了服务发现。

二进制编码格式的自定义RPC协议比JSON over REST性能好,但是RESTful API受支持于更多的编程语言和平台,且拥有大量可用工具。

数据编码于RPC的演化

可独立更改和部署的RPC客户端和服务器对于可演化性来讲至关重要。

RPC的演化只需要在请求上有向后兼容性,对响应有向前兼容性。

RPC经常被用于跨越组织边界的通信,所以服务的兼容性很难完成,服务的提供者无法强迫客户升级,所以需要长期保持兼容性。因此,当需要进行兼容性更改时,服务提供商通常会并排维护多个版本的服务API。

API的版本化没有一个公认的标准。常见的是在URL或HTTP Accept头中使用版本号。

2.3 消息传递中的数据流

与RPC相比,消息队列的优点:

  • 如果收件人不可用或过载,可用充当缓冲区,提高系统可靠性
  • 可用自动将消息重新发送到已经崩溃的进程,防止消息丢失
  • 避免发件人需要知道收件人的IP地址和端口号
  • 允许一条消息发给多个收件人(发布-订阅模式)
  • 将发件人与收件人逻辑分离

同时,消息队列是单向的,发送者不会等待消息被传递,而只是发送它,然后忘记它。

消息代理 / 消息掮客(message broker)

掮客[qián kè]

消息代理通过在正式消息传递协议之间转换消息来使得应用、系统和服务之间能相互通信。消息代理是消息传递中间件或面向消息的中间件 (MOM) 解决方案中的软件模块。

一个主题只提供单向数据流。但是,消费者本身可能会将消息发布到另一个主题上,因此可以将它们链接在一起,或者发送给原始消息的发送者使用的回复队列。

消息代理通常不会执行任何特定的数据模型 - 消息只是包含一些元数据的字节序列,因此您可以使用任何编码格式。如果编码是向后兼容的,则您可以灵活地更改发行商和消费者的独立编码,并以任意顺序进行部署。

分布式的Actor框架

Actor模型是一个通用的并发编程模型。每个Actor实例封装字节相关的状态,并且和其它Actor物理隔离。一个Actor就是一个工人,与进程或线程一样能工作或处理任务。

Actor模型用于跨越多个节点来扩展应用程序。多个Actor通过发消息进行交流但是不会产生数据竞争。

位置透明在Actor模型中比在RPC中效果更好,因为它已经假定消息可能丢失。尽管网络上的延迟可能比同一个进程中的延迟更高,但是在使用参与者模型时,本地和远程通信之间的基本不匹配是较少的。

分布式的Actor框架实质上是将消息代理和角色编程模型集成到一个框架中。但是,滚动升级时消息可能会从运行新版本的节点发送到运行旧版本的节点,所以需要担心先前向后兼容性的问题。

3 小结

编码的细节不仅影响其效率,更重要的是应用程序的体系结构和部署它们的选项。

本章讨论了几种数据编码格式及其兼容性属性,包括文本格式还有二进制格式。还讨论了数据流的几种模式,包括数据库、RPC和REST API、异步消息传递,说明了数据编码是重要的不同场景。

相关推荐
高兴就好(石2 小时前
DB-GPT部署和试用
数据库·gpt
这孩子叫逆3 小时前
6. 什么是MySQL的事务?如何在Java中使用Connection接口管理事务?
数据库·mysql
罗政3 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
Karoku0663 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
码农郁郁久居人下3 小时前
Redis的配置与优化
数据库·redis·缓存
拾光师4 小时前
spring获取当前request
java·后端·spring
MuseLss4 小时前
Mycat搭建分库分表
数据库·mycat
Hsu_kk5 小时前
Redis 主从复制配置教程
数据库·redis·缓存
DieSnowK5 小时前
[Redis][环境配置]详细讲解
数据库·redis·分布式·缓存·环境配置·新手向·详细讲解
程序猿小D5 小时前
第二百三十五节 JPA教程 - JPA Lob列示例
java·数据库·windows·oracle·jdk·jpa