为了账号安全,请及时绑定邮箱和手机立即绑定

《Designing Data-Intensive Applications》第4章 读书笔记:编码和进化

标签:
大数据

1.引言

第一章的时候讲了 进化性, 就是应对不断变化的需求
如果说数据模型变了,我们的表以及业务代码也要相应变化,比如说多了一个字段等等
对于关系型数据库,是有schema的,通过修改schema来应对变化的结构(如alter语句)
对于文档性,schema less的(也是schema on read)的,需要自行兼容新老数据结构

当数据变化的时候,要考虑两个问题

向前兼容性:
  新的代码能够解析旧的数据
向后兼容性:
  老的代码能够读出新的数据(忽略掉新的属性)

向后兼容更难一点

这一张讲解encoding data的几种格式,包含json,xml,proto buff,thrift和avro
讲解他们如何处理schema的变化,保证新旧数据的共存,并且讲解在REST和RPC中的作用

2.数据的format以及序列化

数据往往有两种表现形式:
1.内存中,用object,structs,list等记录
2.bytes串,当需要把数据进行持久化或网络发送的时候

当两种表现形式进行转换的时候,就需要encoding技术(java中称为序列化)
有很多第三方的包提供这个功能,但是往往有下面几个问题

1.针对特定的语言:encoding时用A语言,那么decoding的时候往往也要A语言
2.安全性:如果encode和decode方式泄露了很不安全
3.考虑兼容性:向前向后兼容
4.效率(encode和decode时间,序列化出来的bytes长度等)

2.1 json,xml以及二进制变种

json,xml比较常见
xml因为过于详细,和不必要的负责而被批评。
json由于支持浏览器(js的一部分)而流行。
但是依旧有一些问题

  • 编码数字时有歧义,xml无法区分String还是Number

  • json和xml支持Unicode字符集但是不支持binary string,使得data size变大

  • json和xml有一些可选的schema,但是太强大太复杂了


2.1.1 二进制变种

json没有xml那么详细,但是相对二进制format,仍然用了很多空间,
有一些JSON变种如 MessagePack,BSON,BJSON等
还有些XML变种如WBXML等
比如json串如下

{"userName": "Martin","favoriteNumber": 1337,"interests": ["daydreaming", "hacking"]
}

用MessagePack表示如下


webp

image.png


从json的81字节到MessagePack的66字节

是否值得为了这么小的空间压缩而失去可读性,尚不明确


2.2 Thrift和protocal buffers

Thrift是Facebook开发的,pbf是谷歌开发的,都需要interface definition language(IDL),
PBF的例子如下

message Person {
  required string user_name = 1;
  optional int64 favorite_number = 2;
  repeated string interests = 3;
}

都有一个code生成器,根据schema的定义,用不同的语言生成classes来实现这个schema
比如Thrift的CompactProtocol如下,

webp

image.png

和上面MessagePack的区别就是不包含field names,如“userName”,“favoriteNumber”这些内容
只用了field tags(1,2,3)

2.2.1 field tags与schema的变动

之前说schema不可避免的会有改动,称为schema evolution,Thrift和PBF如何处理前后的兼容性呢?

改字段的名字:IDL里面用field tags替代了field names,因此只要filed tags不变,decode方就可以正常处理

添加field:旧的代码忽略掉它不认识的field tags即可(向后兼容),但是要求这个field在IDL定义中不能使required
否则解析旧数据出错。新的代码依然能够处理旧的field,因为旧的tags能够识别(向前兼容)
删除field:同上,旧代码读新数据时忽略掉不认识的tag,新代码读旧数据时处理它知道的tags即可
但是要求删掉的fields的tags以后也不能再用

2.2.2 数据类型与schema的变动

如果改变field的数据类型怎么办,详情看文档,但是有损失精度以及截断的风险
比如32位改成了64位,旧的代码不一定能处理新的数据


2.3 Avro

是Hadoop的一个子项目,IDL如下

record Person {  string userName;  union { null, long } favoriteNumber = null;  array<string> interests;
}

序列化后,内容如下


webp

Avro只有32个字节

首先注意IDL里面没有tag number
序列化后内容没有标识是哪一个字段,数据类型是什么

这样的话,要求数据decode时用的schema和写时的schema完全一样才行。
那么如何处理schema改动的问题呢?


2.3.1 写schema和读schema

encode的时候会有一个写schema
decode的时候会有一个读schema
Avro不要求两个一样,只要两个兼容即可
Avro的包会解决两个schema的差异,通过比较两个schema
解决方法如下


webp

Avro解决writer和reader的差异

如果两个schema顺序不同,会通过匹配field name来解决
如果read schema遇到了只在write schema出现的field,那么会忽略掉
如果read schema遇到了自己希望有但是write schema未出现的field,那么填充默认值


2.3.2 schema改动规则

前后的兼容性要求添加或者删除field时,field需要有一个默认值。
其他一些改动只有向前兼容不具有向后兼容:
如改动field name则只满足向前兼容(类似于field name有一个alias)
等等


2.3.3 read schema怎么知道write schema是怎样的

分几个场景

有很多记录的大文件:如hadoop,所有的记录的schema都是一样的,因此直接把write schema记录在file中即可
有不同write schema写记录的数据库:每一个记录新加一个write schema version number,加一个write schema的表,这样reader获取record时通过version找到对应的write schema
网络发送记录:双方协商好schema,之后进行RPC调用

2.3.4 动态生成schema

Avro对于动态变化的schema更友好,直接生成一个新的schema就好了
而Thrift和PBF面对动态变化的schema的时候,则需要手动控制好field tags(个人感觉类似于自增主键的管理)


2.3.5 代码生成以及动态类型语言

Thrift和PBF依赖代码生成,即根据schema生成对应的class的代码,在静态类性语言中有效,如Java,C++等。默认不支持动态类型语言
Avro则既支持静态语言,也支持动态类型语言(python,js等)


2.4 schema的优点

相比JSON,XML来说,上面的Thrift,PBF,Avro有很多优点:

  • 数据压缩的更小

  • schema能够表示文档,记录的一种组织形式

  • 记录下schema能够更好的保证前后兼容性

  • 对于静态类型语言,schema能够generate code的特性非常有用,能够进行编辑检查


3. 数据流的模式

上面讲了数据的encode和decode,那么是谁进行encode和decode呢,数据的发送方和接收方,是怎样进行数据的传输的呢,最主要有下面几种

数据库
服务调用(REST或者RPC)
异步消息处理

3.1 数据库的数据流

写入db的进程对数据进行encode,从db读数据的进程对数据进行decode
前后兼容性依然需要保证:新老代码都要能够处理新老schema的数据
另外在业务层的代码也要注意保证兼容性
比如


webp

旧的代码执行toJson之后写入db,导致新的字段photoURL被冲掉了


不同时期写下的记录

比如有5s前写下的记录,有5年前写的记录,但是两者的encoding是不一样的(因为不同的schema)
重启应用程序很快,但是重启db也不会改变它们的encoding,重复rewrite。

Rewrite和migrate都是可行的,但是是很重的操作。
一般是加一些有默认值的列进来,而不修改原有的数据。当旧的数据被读的时候,就塞入默认值。


3.2 REST和RPC的数据流

当网络通信时,最常见的是client和server交互。
server把API暴露在网络上,称为服务,并且client能够连接上server去请求API。
当然,server自己也可以作为client去调用其他服务,比如数据库
某种意义上,服务就类似数据库,允许client提交和查询数据,不过数据库只允许特定的sql语言,而应用的server方则暴露API。
所以同样的,client和server之间的数据encode和decode也要用兼容性。


3.2.1 web服务

把http作为底层协议调用的服务称为web服务
主要有两种形式,SOAP和REST
REST不是一个协议,是一个基于HTTP协议上的设计思路
强调用简单的数据形式,用URL来标示资源,以及HTTP的特性来完成控制认证等。相对SOAP更加流行

而SOAP就是用基于XML的如WSDL语言描述,在静态类型语言中有用,但是在动态类型语言中没那么有用。
并且不是人可读的,而且人为构建太复杂。
两者在这里不详细介绍了


3.2.2 RPC遇到的问题

RPC定义参照refer
目前网络请求相对本地调用,有这些问题

1.本地调用要么成功要么失败,而通过网络请求,要考虑丢包,延迟,超时等等。必须考虑这些情况,比如失败重试
2.本地调用要么返回结果,要么抛异常,要么死循环。而网络请求则可能由于超时而无法返回。此时根本不知道发生了什么,不知道请求是否发送过去了
3.网络传输,重试的时候,要求要有幂等性,即第一次请求和第二次同样的请求,得到的结果应该是一样的。而本地操作不用考虑这个问题。
4.网络调用要考虑网路情况,可能快可能慢。本地调用则更快
5.调用方法传递参数时,如果是基础类型还好,如果是自定义的很大的object,就会变得复杂(应该是考虑序列化之类的问题)
6.client和server如果用不同语言实现,那么数据类型从一个语言到另外一个语言可能会有问题,比如js的数字可以比2^53大

3.2.3 目前RPC的方向

尽管上述诸多问题,我们还是会用RPC。比如Thrift和Avro支持RPC,谷歌研发了gRPC等等。
有些框架提供了 服务发现功能,即允许client找到在哪个ip,port上有哪个服务。

而REST方便调试,被主流语言,平台支持,有一些的工具
因此公共API用RESTful
而同组织的内部服务调用,则用RPC好


3.2.4 数据encoding以及RPC的进化

对于进化性来说,RPC需要允许client和server进行改变并且独立部署。
合理假设RPC服务都是server先升级,然后client升级。
因此,只要考虑 request的向前兼容性(server处理)以及response的向后兼容性(client处理)即可

1.Thrift,gRPC(PBF),Avro的RPC都能根据encoding规则达到兼容性
2.SOAP用xml schema,能够达到兼容性但是有一些陷阱
3.RESTful API常用json串进行回复,用json或者URI-encoded/form-encoded进行请求。如果请求加入了可选册数,或者回复加入了新字段都被认为是变动

RPC常用于组织内部,使得服务兼容性更困难。
因为无法强制client升级,要一直保持兼容性。
如果有些破坏兼容性的改动,那么往往会终止对某些旧版本client的支持。


3.3 消息传递数据流

这里简单看看异步消息传递系统(就是消息队列)

它是介于RPC和db之间
像RPC是client发送请求(消息)到另一个处理进程
像DB是消息会间接的暂时存储在broker中(中间件)

broker相对RPC来说有以下优点

1.如果接收方挂了,broker还可以作为buffer
2.自动重发机制处理进程挂掉,避免消息遗失
3.不需要直接知道ip端口(部署在云上方便)
4.允许消息发送给多个接收方
5.逻辑上把sender和receiver分开,两者可以互相不知道

另外一个区别在于消息队列是单向的,发送方不会接收到接收方的回复,所以消息是异步处理的


3.3.1 message broker

最近很多开源实现如RabbitMQ,ActiveMQ以及Kafka等很流行。
通常,broker工作机制如下

一个进程发送消息到特定的queue或者topic。
然后broker保证消息发送到订阅了这个queue或者topic的receiver中。

当然也要考虑到兼容性问题,这里不展开对MQ的讨论


3.3.2 分布式actor框架

这个直接参照refer就好,书中简单介绍了下


4.总结

考虑到数据在网络传输,数据有序列化的需求。然后为了应对数据格式的变化(进化性),数据的encode和decode方需要有前后的兼容性。
讲解了几种序列化(Avro,Proto buff, Thrift)的方法,以及应对兼容性的处理.
再讨论了数据传输的几个场景(数据流):从DB,RPC以及REST API以及消息队列三个场景出发,介绍了背景以及编码以及兼容性的

5.思考

RPC和REST的思考

书中说REST更适合public API而RPC适合公司内部调用(3.2.3)
另外对RPC的兼容性的总结讲的太好了,公司内部server先升级再client升级,而如果作为public API,则往往要考虑client不能升级,终止某些client版本的服务等等(3.2.4)

数据流上

思考数据是如何encode和decode,怎样保证兼容性的,满足变化的需求

6.问题

讲Avro最后对动静态语言的支持(2.3.5),这一小部分不是非常理解,没有深入研究
对于REST我还不是很熟悉,后面再抽空看



作者:赤子心_d709
链接:https://www.jianshu.com/p/21e9a222db03


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消