计算机程序在通过网络进行数据交换过程中会使用到数据格式编码,一些本地持久化文件也是需要进行编码存储,前面讲到了数据密集型应用设计中一个需要考虑的有可扩展性和可演变性,本文将对数据编码的可演进性进行探讨,改如何设计数据的编码方式?用什么样的数据格式?应用程序随着时间的变化数据格式和编码也会发生变化,怎么做到向前兼容?

在关系模型中数据表结构在项目需求分析阶段就已经确定了,通过DDL创建表结构,但是在项目运行一段时间后不能保证需求没有变化,如果发生了变化这时就需要对其表结构进行修改,例如使用ALTER来修改其表字段结构,这个就属于数据格式的变化了。数据库中的表结构一改,那么业务ORM层的映射对象代码也要跟着变化。这种变化在服务端,如果是线上项目就需要进行滚动升级发布,可以尝试着部署几个节点跟踪其运行状态;而如果客户端应用,只能看用户自己是否愿意升级程序;但是也可以在第一个版本发布的时候再客户端加入强制升级的代码,如果有一些紧急Bug修复可以强制升级。


软件兼容性

人类语言发展过程也是类似的,现代人能看得懂古代的人类的语言和文字,但是古代的人类不一定能看得到现代人的文字和语言。这种场景换到程序设计上就是软件的兼容性问题,开发软件都明白设计一个好的通用型API可以以免后期继续添加其他的新的API接口,例如开发一个本地存储软件需要设计数据存储的格式,在第一个版本开发的时候就要考虑到兼容问题,编写一个存储软件要考虑兼容性分为两种:

  • 向后兼容:新的代码可以读取老的代码编写的数据文件。
  • 向前兼容:较旧的代码可以读取新的代码编写数据文件。

目前来看向前兼容是最难实现的,因为不能时间回到过去,在软件设计初期就要考虑到这些问题,设计提供一些灵活的接口去实现,或者通过动态的配置文件,定义一个新的标准去做。

业界很多知名的软件都有相关的解决方法,和兼容性问题解决方案,笔者我将会列举几个知名软件在兼容性方面的设计。如果都写个Java程序都知道Java源代码编译输出的是Class,而运行Java程序的是JVM,但是就是因为这两个东西不能打包在一起就会出现很多兼容问题,使用低版本的JVM不能运行高版本Java编译器编译出来的Class文件,而高版本的JVM会向前兼容底版本Class文件。不能向后兼容的问题很多知名软件都存在,Java为了区分不同版本编译的Class文件,在每个文件头部使用了特殊的编号,如果懂得这些原理的人可以通过十六进制编辑器修改其版本,运行高版本Class文件。

存储相关的软件Apache Cassandra在它所产生的数据文件也会有相应的版本标记,方便后续维护的时候能区分数据文件是哪个版本生成的,可以使用对应的程序代码进行操作。


数据编码格式

程序运行所产生的数据绝大部分在局部栈空间或者在堆空间中,但是都属于计算机内存中的数据,在内存中维护数据结构比在磁盘上维护数据结构要简单的多,但是大部分分布式系统需要多台机器共享内存中的数据,这时就需要使用数据编码和序列化功能了。

大部分编程语言内存都支持正反序列化的类库,帮助将内存中的数据序列化成一个特定的数据串。大部分前后端分离项目使用的是XML和JSON做数据交换,XML相比JSON需要更多字符串空间和结构化部分,JSON相比XML要简单以字符串和键值对组成数据对象。

XML和JSON对于人类是方便阅读的,因为是纯文件格式又带来另外一个问题被编码的数据没有具体的数据类型。JSON可以区分数字和字符串,但无法区分浮点型的精度。例如服务器端使用Java计算出来的浮点数类型通过JOSN传送到前端给JavaScript时,就会出现不能明确表示,Twitter为了解决这样的问题就是返回的JSON中使用了十进制字符串表示。另外也不用别出心裁使用特定的序列化格式,把格式和特定的语言绑定到一起,这样会导致后面迁移项目或者扩展项目时对使用的编程语言产生依赖关系。


二进制编码

对应比较传统的方式数据编码采用JSON和XML是完全没有问题的,只是为了把数据能按照特定格式进行编码传输或者存储,但如果是一些对性能和空间利用率的场景下这些方式就不适用了,数据规模达到TB级别的,数据格式的选择会产生很大的影响。JSON和XML都算是在一种文本格式基础之上所进行改造的一种格式,相比二进制格式占用的空间要大的多,特别是XML格式很多冗余的标签信息。

目前比较轻量级的二进制数据格式有BSONUBJSONSmile)、WEBXML等,这些都是类似于JSON数据格式,但是相比JSON数据组织方式确实二进制,更节省空间在网络传输的时候性能也要相应的提升;例如下面就为BSON和传统JSON的编码差异:

Bson:
  \x16\x00\x00\x00               // 总文档大小
  \x02                           // 0x02 = 类型:String(字符串)
  hello\x00                      // 字段名
  \x06\x00\x00\x00world\x00      // 字段值(值大小,值,空终止符)
  \x00                           // 0x00 = 类型:EOO('end of object',对象结尾)

但是有这些二进制数据编码格式还是远远不够的,日常开发需求和定义的数据结构复杂多样性,这时就需要能根据特定数据类型描述来生成对应二进制数据编码的工具了。例如现在微服务使用的gRPC协议传输使用的Protocol Buffers,在分布式应用程序中经常使用这些二进制序列化数据结构格式来做不同系统之间的数据交换工作,在分布式系统之间通过[接口描述语言
](https://zh.wikipedia.org/zh-cn/%E6%8E%A5%E5%8F%A3%E6%8F%8F%E8%BF%B0%E8%AF%AD%E8%A8%80)进行接口的数据结构定义。

相关的技术实现有Apache ThriftGoogle Protocol Buffers,这些都需要开发者在使用之前进行数据结构建模,也叫模板定义,提供特定模板来创建生成对应编程语言所需要的代码结构,这些代码将用来生成或解析代表这些数据结构的字节流。

这些模式定义描述二进制编码格式,相比JSON模式要简单,但是支持更详细的验证规则,对于静态类型编程语言来说,生成的代码可以编译时进行类型类型检查。通过Protocol Buffers进行编码的数据满足程序兼容性,Protocol Buffers没有使用字段进行编码而是使用的Tags进行的,每个字段都有标识,可以轻松修改模式中代码字段名称,而编码永远不直接引用字段名称,但是不能修改编码中标签,这样会导致被编码数据失效。


数据流传递

在一个大型数据系统中往往不是单个服务节点运作的,大多数都是支持水平扩展并且有不同服务节点进行运作的,每个节点所能操作的只是本地内存,如果需要共享内存中的数据,就要将其序列化编码通过网络字节流传到另外一台计算机上。因为是不同的计算机共享数据,就要考虑到数据编码的向后兼容性问题,例如发布新功能使用滚动发布,这时一个大型系统上有新代码,也有旧代码同时运行着,如何保证互相不受影响正常工作?

通常开发微服务一般使用的都是RPC和REST进行数据交换工作,基于WEB大部分数据交换格式采用的JSON和XML被称为Ajax技术,这种模式之下服务器不需要渲染整个页面给前台,而是通过JSON编码数据对象,也称之为RESTful API不限于浏览器客户端,只要支持发送HTTP请求的程序都可以算是客户端,使用JSON能适用于各类的程序编程语言和不同的客户端,这也是JSON的优点也是公共API主流风格。

另外一种就是基于服务内部之间的数据传输RPC的方式,例如Java语言特有的RMI,但RMI和特定的语言绑定到一起了,不能适用于其他编程语言场景。RPC一般要有多个服务返回,这是要考虑很多网络因素并且,优点就是可以使用模式定制语言来描述数据格式,和编程语言的无关性,RPC多用于同一组织内多项服务之间的请求,通常发生在同一数据中心内,而HTTP这种只能对外访问使用。


其他资料

最后修改:2023 年 04 月 13 日
如果觉得我的文章对你有用,请随意赞赏 🌹 谢谢 !