数据流模式
编码模式解决了不同架构之间的数据交流问题,为了实现这一目标它需要具备简单的同时包含自动前后兼容的特征,所以模式归根结底是解决系统变更困难的问题。
流模式则讨论另一个话题,数据流动的过程,在软件系统生态架构中数据流动无非下面几种形式:
- 通过数据库。
- 通过服务调用。
- 通过异步消息传递。
基于数据库流动
写模式对数据库编码,读模式对数据库解码。
需要保证向后兼容,否则后面的版本无法读取之前的内容。
但是由于并发性问题,不同的进程看到的数据状态可能具备差别,意味着数据库的数值可以被新版本写入,同时要兼容旧版本继续读取,说明数据库也需要向前兼容性能。
基于数据库的流动显著问题是和模式类似,新增一个字段容易导致数据读取的问题,理想情况下是旧版本代码保持新版本字段的不变,哪怕完全无法解释。
首先需要注意的问题便是新旧版本转化的问题,有时候在应用程序读取新对象进行解码,之后在重新编码的过程中可能会遇到未知字段丢失的问题。
为了解决上面提到的向前兼容问题,数据采用的方式是把磁盘编码的所有数据填充空数值。
注意一些文档数据库本身会利用模式来完成向前兼容,比如 Linkedln 的文档数据库Espresso使用,Avro进行存储,并支持的Avro的模式横化规则。
归档存储
所谓的归档存储指的是对于数据库存储快照,由于使用快照对于数据进行恢复,所以需要对于数据副本进行统一编码。
像Avro对象容器文件这样的对象容器文件十分合适,因为没有额外的模式字段维护,只需要利用框架本身的模式完成转化。
归档存储在第十章“批处理系统”有更多讨论。
基于服务数据流:REST和RPC
REST和RPC的概念相信不需要过多介绍。
在系统应用中WEB应用是最多的,而关于WEB的传输API包括(HTTP、URL、SSL/TLS、HTML)等,这些协议在过去受到广泛认可,现在已经成为大多人同意的标准。
通常情况下HTTP可以用作传输协议 ,但是在顶层实现的API是特定于应用程序的,客户端和服务器需要就API的细节达成一致。
RPC的概念尝尝作用于微服务,现代的系统设计更加倾向于细化分工和服务职责拆分,就算是简单的系统也会按照分模块的方式进行职权拆分,独立部署和快速演化是微服务的目标。
微服务也诞生一个问题,不同的团队持有不同的微服务模块,这带来了API兼容以及数据编码的问题,这也是为什么编码框架和通信框架的诞生。
网络服务
针对WEB服务有两种流行的处理方法:REST和 SOAP,这两个都不算是新东西。REST是基于HTTP协议的设计概念,SOAP是基于XML的协议。
REST 的概念是利用URL标识资源,通过HTTP协议本身完成缓存控制,身份验证和内容类型协商。不同的是为资源定义更为明显的标注你和界限。REST原则所设计的API称为RESTful。
SOAP用于发送API请求,但是由于庞大复杂的多重相关标准,已经被REST简单风格替换。SOAP WEB服务的API叫做WSDL。支持代码生成和访问远程服务,但是同样针对动态编程语言的生成效果很弱。
尽管SOAP及其各种扩展表面上是标准化的,但是不同厂商的实现之间的互操作性往往存在一些问题,SOAP虽然依然被一些大厂商使用,但是针对小公司来说已经不再受到欢迎,而到了现在整个WebService的使用范围也在不断缩小。
ResultFul的API生成工具目前较为主流的是使用Swagger的形式。
远程调用RPC
在过去许多的编程语言的远程方法调用大肆宣扬,但是它们多少都存在缺陷或者一些明显的短板,比如:
- JAVA的EJB远程方法调用仅限于JAVA
- 分布式组件对象模型DCOM 适用于微软平台
- 请求代理体系 CORBA 缺乏前后兼容被放弃。
远程方法调用的思想从上世纪70年代就已经出现了,RPC起初看起来很方便,但这种方法在根本上有明显缺陷, 网络请求与本地函数调用的巨大差别:
- 本地函数调用使可控可维护的。
- 本地函数调用的结果基本可以预知,比如超时和进程崩溃都可以通过各种手段排查。
- 每一次重试失败需要花费相同的时间继续重试,如果一个任务总是在将要完成的时候崩溃,不仅占用资源还容易导致系统的各种复杂情况。
- 本地函数可以借用内存完成对象的之间的高速传递。
- 本地和远程调用端用不同语言实现,所以中间需要进行转化,或者借助编码框架完成前后兼容。
RPC发展
RPC现在虽然没落,但是并没有完全消失,Thrift和Avro带有RPC支持, gRPC是使用 Protocol Buffers的RPC实 现, Fin agle也使用 Thrift , Rest.Ii 使用 HTTP上 的JSON。
事实上RPC框架还在继续发展,比如新一代框架更加明确RPC和本地函数调用的事实。
- Finagle 和 Rest.li 使用 Futures 封装失败异步操作。
- Futres简化多项服务结果合并。
- gRPC支持流
等等这一些改进。此外二进制编码格式也支持自定义的RPC协议,对于一些REST和JSON的协议具有更好的性能。
但是REST的设计风格现在看来反而有点脱裤子放屁,因为不过是包装了一层HT TP协议而已,似乎SOAP的设计才是符合RPC的定义。
RPC 的数据编码和演化
由于是远程调用,设计到不同服务之间的通信,必然设计到编码演进和前后兼容问题,而针对前后兼容问题,RPC出现制定了下面一些方案:
- Thrift 、 gRPC (Protocol Buffers )和Avro RPC可以根据各自编码格式的兼容性规 则进行情化。
- SIAO XML 虽然是可以演化的,但是有一些陷阱。
- RESTFul 使用JSON格式保持兼容性。
此外对于RESTful API ,常用的方在是是在URL或HTTP Accept头中使用 版本号限定调用和兼容性保持。另一种选择是客户端请求的API版本存储服务器,同时提供多版本的接口管理调用功能。
异步消息
RPC 和数据库之间的异步数据消息传递是本章的最后一个话题,和RPC调用类似,客户端的请求同样低延迟到另一个服务进程。消息队列通过暂存消息的方式,寻找生成者和消费者。
和RPC相比的消息队列有下面几个特点:
- 消息队列可以充当缓冲照顾双方的处理能力。
- 避免发送方需要知道接收方IP和地址的问题。
- 支持一个消息发给多个接收方。
- 逻辑上的发送方和接收方分离。
消息队列比较显著的问题是消息传递是单向的,此外还有一个显著的特点是通常并不希望消费方进行回应。发送者发送之后通常会忘记他的存在。
消息队列
消息队列最早是由一些商用收费软件控制,后面在出现各种开源软件kafka、activeMQ、HornetQ、RabbitMQ等流行。
同一个主题上可以绑定多个生产者和消费者,消息队列不会强制任何数据类型,消息传递的元数据都是一些字节队列。
此外,主题通常只指定单向流,但是消息本身会发给另一个主题和可能存在的多个消费者绑定。
消息队列的另一显著优势是前后兼容很容易实现,最大灵活的调整双方即可。消息队列的内容在12章会继续进行阐述。
分布式Actor框架
Actor模型是1973年提出的一个分布式并发编程模式,在Erlang语言中得到广泛支持和应用。
Actor是基于单进程的并发编程模型,所有的逻辑被封装到Actor而不是现成当中,每个Actor代表客户端的一个实体,也就是可以把每一个线程等同于一个进程看待。由于是单进程的设计,不需要线程问题,每个Actor都可以自由调度。
Actor 模型的计算方式与传统面向对象编程模型(Object-Oriented Programming,OOP)类似,一个对象接收到一个方法的调用请求(类似于一个消息),从而去执行该方法。
Actor的最大特点是可以编程模型可以跨越多个节点扩展应用程序,无论发送和接收方是否在一个节点。换种说法是在不同的节点上消息被透明封装为字节序列并且通过网络传递,同时在另一端解码。
分布式Actor实际上就是把消息队列和Actor的编程模型绑定到单进程当中,可以简单看作是特殊版本的消息队列,这样有一个好处是屏蔽了复杂性,但是坏处是程序眼无法细粒度的控制编程模型和函数,同时具备前后兼容性的问题,因为新版本的节点可能被送到旧节点。
下面是Actor处理消息编码的方式实际应用:
- 使用Akka抽象让JAVA内置序列化,可以利用Protocol Buffers完成前后兼容。
- Orleans 使用自定义编码格式,需要部署新版本应用程序,同样可以支持序列化插件。
- 在Erlang OTP 当中,但是很难对于记录模式更改。
Akka:对并行程序的简单的高层的抽象;异步非阻塞、高性能的事件驱动的编程模型;非常轻量的事件驱动处理(1G内存可容纳270万个Actors)。
Actor 分布式并行计算模型: The Actor Model for Concurrent Computation - 腾讯云开发者社区-腾讯云 (tencent.com)
深入解析actor 模型(一): actor 介绍及在游戏行业应用 - 知乎 (zhihu.com)
小结
本章内容量比较庞大。第一个纬度是讨论了数据格式的编码问题,以此产生了内存结构转为网络或磁盘字节流的方法。在编码的细节内部可以看到哪怕是一个字节的变动都有可能带来性能影响,同时不同的设计理念直接影响系统的部署方式。
在许多服务需要滚动升级的情况下,新版本需要依次部署到几个节点,滚动升级是在不损害旧版本正常运行下“不停机”升级系统版本的通用手段,同时有效降低部署上线的风险。
Avro - 这一章作者对它他吹特吹,但是个人并没有使用过类似的编码框架经验,关于各种体验笔记没有讲述。
滚动升级需要考虑最大问题是数据格式的前后兼容问题,在微服务和模块更加细化的今天,这样的情况更加频繁出现,哪怕是小项目也可以实现分布式部署,这样也带来了编码框架的前后兼容影响。
接着本章讨论了下面这些问题,首先是关于编码问题的讨论:
- 特定语言只在特定的领域适用,虽然JVM的野心是统合所有的编程语言,但是显然还有漫漫长路要走。
- JSON、XML是经典的通用兼容模式语言,但是因为广泛使用的JSON诞生于JS在数字类型上存在明显纰漏。
- Thirft、Protocol Buffers 和 Avro 遵循二进制编码的原则,对于数据进行前后兼容和高效编码,静态编程语言对于这样的框架十分受用,得到广泛的编程语言的认可和支持。尤其是在GO的领域大放异彩。
之后是数据流的讨论,数据流目前已经非常成熟:
- 数据库,因为存在“旧版本”数据读取的场景,通常使用特殊方式对于数据进行编解码,保证数据向前兼容读取。
- RPC以及RESTFUL