本章对比了JSON、Thrift、Protocol Buffer、Avro几种编码方式在模式演进时如何保持兼容性,更好的支持程序滚动升级。随后列举了几种进程间数据交互的方式,这些交互方式利用之前介绍的数据编码方案保持服务的兼容性。
一般而言,向后兼容比较容易,因为编写新代码时,清楚旧数据的格式。
程序数据两种表示形式:
前者转为后者称为编码(也称序列化),后者转为前者称为解码(也称反序列化)。
{ "userName": "Martin", "favoriteNumber": 1337, "interests": ["daydreaming", "hacking"]}
文本形式,易读性强,开发调试友好,数字编码有模糊之处,缺少模式,占用空间大。
Thrift有两种编码格式,为BinaryProtocol和CompactProtocol。Protocol Buffer常被称为pb,只有一种编码方式,和Thrift的CompactProtocol非常类似。由于Thrift和Protocol Buffer二者非常类似,因此下面只介绍pb。
接口定义语言(IDL)描述模式
message Person { required string user_name = 1; optional int64 favorite_number = 2; repeated string interests = 3;}
模式随着时间不断变化,称为模式演化。
添加新字段时,向前兼容通过忽略不认识的字段实现,向后兼容不能添加必填(required)字段。删除字段时向前和向后兼容性和添加字段时相反。这意味着只能删除非必填字段,而且之前的字段标签不能再用,免得老代码写入的数据被够被新代码忽略。
数据类型改变在某些方式是支持的,比如pb的单值到多值的互转,但是面临着潜在的数据丢失问题。
pb相比json占用空间更小的原因
- 使用字段标签(tag)代替了字段名。
- 紧凑的二进制编码,避免了文本编码的许多元字符,比如逗号,引号。
- 使用可变长度的int编码整型字段(varint,字节最高位标识是否还有下一个字节),进一步降低了空间占用。
Avro是Hadroop孵化出得一个项目,广泛地用在Hadroop生态中。
当应用程序想要对某些数据进行编码,它使用所知道的模式模式的任何版本来编码数据,这被称为写模式。当应用程序想要解码某些数据,它期望数据符合某个模式,即读模式。
Avro的关键思想是,写模式和读模式不必是完全一模一样,它们只需保持兼容。例如,写模式和读模式字段顺序不同,这也没有问题,因为模式解析通过字段名匹配(这里不太懂,编码的时候没有将字段名编码进去,那解码是如何根据字段名匹配呢?)。如果一个字段写模式有读模式没有,则忽略,反之,则用默认值填充。
相比pb和thrift,avro对动态模式支持更好。
总结下,二进制编码的好处:
这种方式或许是大家见得最多,使用得最多的一种方式了。也就是应用程序将数据库当作中转站,将数据写入,并在之后读取写入的数据,通常会涉及多种不同功能的服务以及众多的接口。
服务器公开的API称为服务。
将大型应用程序按照功能分解为较小的服务,当一个服务需要另一个服务的功能或数据时,就会向另一个服务发出请求。这种构建应用程序的方式被称为面向服务的体系架构(service-oriented architecture,SOA),或者微服务体系结构(microservices architecture)。
微服务体系的一个关键设计目标是,通过使服务可独立部署和演化,让应用程序更易于修改和维护。
当HTTP为用作底层通信协议时,它被称为Web服务。有两种流行的Web服务方法:REST和SOAP。
REST不是一种协议,而是一个基于HTTP原则的设计理念。它强调简单的数据格式,使用URL标识资源,并使用HTTP功能进行缓存控制、身份验证和内容类型协商。根据REST原则设计的API称为RESTful。
SOAP是一种基于XML的协议,用于发出网络API请求。虽然它最常用于HTTP,但其目的是独立于HTTP,并避免使用大多数HTTP功能。相反,它带有庞大而复杂的多种相关标准(Web服务框架,Web Service Framework,称为WS-*)和新增的各种功能。SOAP Web服务的API被称作WSDL(Web Service Description Language)来描述。
SOAP消息复杂,严重依赖工具支持、代码生成和IDE,不同厂商实现之间标准不一。RESTful倾向于更简单的方法,涉及较少的自动化工具和代码生成,受到小公司的青睐。
到底什么是REST?
REST全称Resource Representational State Transfer,资源表现层状态转移。这名字越听越让人糊涂,它的真正意思是用url标识资源位置,用HTTP动词(GET/POST/DELETE/PUT)表示操作,用HTTP状态码标识访问状态。
它的一些最佳实践:
- 使用名词,而不是动词,且推荐名词复数。
BAD/getProducts/listOrders/retrieveClientByOrder?orderId=1GOODGET /products : will return the list of all productsPOST /products : will add a product to the collectionGET /products/4 : will retrieve product #4PATCH/PUT /products/4 : will update product #4
- 保证HEAD和GET是安全的,不改变资源状态。
- 资源地址采用嵌套结构,例如
GET /friends/10375923/profile
这样做地好处是啥呢?
- 看url就知道请求的什么资源。
- 看method就知道要干什么。
- 看状态码就知道结构如何。
与RPC相比,使用消息代理有几个优点:
消息传递是单向的,RPC通常是双向的,消息是fire-and-forget。
两种实现:
分布式Actor框架的实质是将消息代理和Actor模型集成到单个框架中。
这一点的重要性相信调过别人接口的人都知道,当你使用一个json接口,很难有人能说清这个json会包含哪些字段,更比提每个字段的意义了。 ↩︎