第四章 数据编码与演化
Everything changes and nothing stands still.
—Heraclitus of Ephesus, as quoted by Plato in Cratylus (360 BCE)
关系数据库可以支持模式的更改,而读时模式则不强制指定一种模式。当更改时,应用代码需要兼容:
- 向后兼容:新的代码可以读取由旧代码编写的数据
- 向前兼容:旧的代码可以读取由新代码编写的数据
数据编码格式
在内存中的数据和将数据写入文件或通过网络发送的时候,这两者之间的转换,也成为序列化和反序列化。
语言特定的格式
使用方便,但与编码语言绑定。
JSON、XML和二进制变体
语言间兼容。
缺点:
- 数字编码模糊,处理大数字时有问题。
- 不支持二进制字符串,通常使用base64将二进制数据编码为文本来解决这个限制。
二进制编码
Thrift和Protocol Buffers
Thrift的BinaryProtocol编码,最大的区别是没有字段名
Thrift的CompactProtocol编码,与BinaryPotocol的区别是将字段类型和标签号打包到单字节中,并使用可变长度整数长度来实现
Protocol Buffers编码,与Thrift的CompactProtocol非常相似
字段标签和模式演化
字段标签(field tag)对编码数据的含义至关重要,可以轻松更改模式中字段的名称,因为编码不会直接引用字段名称。但不能更改字段的标签,会导致所有现有编码数据无效。
- 向前兼容:通过数据类型的注释来通知解析器跳过特定的字节数
- 向后兼容:新代码总可以读旧数据,唯一的细节是,添加新的字段只能是optional
- 删除:不能删除required字段
数据类型和模式演化
可以修改字段的数据类型,但是会存在丢失精度或者被截断的风险。
Protocol Buffers可以把optional改为repeated,读取旧数据的新代码会看到一个包含领个或一个元素的列表,读取新数据的旧代码会看到列表的最后一个元素。
Avro
为适用Hadoop而开发。
不使用标签标识字段或数据类型。
如图,编码只是由连在一起的一些列值组成。一个字符串只是一个长度前缀,后跟UTF-8字节流。解析的时候需要按照它们在模式中的顺序遍历这些字段。
Avro使用读模式和写模式解析和写入字段,两种模式不必完全一样,Avro读端解决两者之间的差异。
writer模式通常附带在数据前。
Avro对动态生成的模式更友好。
模式的优点
- 比二进制json更紧凑
- 模式是一种有价值的文档形式,可以确定是最新的
- 模式运行在部署任何内容之前检查模式更改的向前和向后兼容性
数据流模式
基于数据库的数据流
基于服务的数据流:REST和RPC
将大型应用程序按照功能划分为较小的服务,当一个服务需要另一个服务的某些功能或者数据时,就会向另一个服务发起请求,这种构建应用程序的方式以前叫做SOA,现在叫做微服务。
网络服务
REST不是一种协议,而是一个基于HTTP原则的设计理念。它强调简单的数据格式,使用URL来标识资源,并使用HTTP功能进行缓存控制、身份验证和内容类型协商。
SOAP是一种基于XML的协议,避免使用大多数HTTP功能。
RPC的问题
RPC模型试图使向远程网络服务发出请求看起来与在同一进程中调用变成语言中的函数或方法相同。其缺点:
- 网络可能出问题,必须有所准备
- 网络出问题的时候,根本不知道已经发生了什么
- 请求需要实现幂等
- 网络延时
- 调用本地函数可以搞笑的将引用或指针传递给本地内存中的对象,但是网络请求必须经过编码为字符序列发送
- 客户端和服务端可能使用不同编码,RPC框架必须将数据类型从一种语言转换为另一种语言。因为不是所有语言都具有相同的类型,所以最终可能会很丑陋
REST似乎是公共API的主流风格,RPC则主要侧重于同一组织内多项服务的请求。
基于消息传递的数据流
消息传递通信是单向的:发送方通常不期望收到对其消息的回复。
消息代理
只提供单向数据流。
一个进程向指定的队列或主题发送消息,并且代理确保消息被传递给队列或主题的一个或多个消费者或订阅者。
分布式Actor框架
Actor模型是用于单个进程中并发的编程模型,每个Actor代表一个客户端或实体,通过发送盒接收异步消息与其他Actor通信。
分布式Actor框架中,这个编程模型被用来跨越多个结点来扩展应用程序。
分布式Actor实际上是将消息代理和Actor编程模型集成到单个框架中。