2018年4月-8月的项目里面用到了protobuf+gRPC技术,本文对这两种技术进行了总结,参考来源于网络,具体链接在文中。
protobuf
定义:是与json,XML功能相似的一种结构化数据格式,是一种google定义的结构化数据格式,用于在网络通讯间的数据序列化和反序列化,以用于网络传输。序列化:将数据结构或对象转换成二进制串的过程;反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
特点:相对于其他格式,protobuf解析速度快(即序列化反序列化速度快),占用空间小,以及兼容性好,很适合做数据存储或网络通讯间的数据传输。
原理:
序列化 & 反序列化简单 & 速度快的原因是:
a. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等,Varint、Zigzag编码)
b. 采用protobuf自身的框架代码和编译器共同完成
即序列化后的数据量体积小的原因是:
a. 采用了独特的编码方式,如Varint、Zigzag编码方式等等
b. 采用T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑
![bacabe22f1bc79ad9529a46a3febda38.png](https://i-blog.csdnimg.cn/blog_migrate/ffc03c25706ad366553219d2d57ed68a.jpeg)
Varint
中每个 字节 的最高位 都有特殊含义:
- 如果是 1,表示后续的 字节 也是该数字的一部分
- 如果是 0,表示这是最后一个字节,且剩余 7位 都用来表示数字
为了更好地减少 表示负数时 的字节数,protobuf在varint编码上又增加了zigzag编码方式进行编码(移位操作)
T-L-V数据存储方式:
![57454d36150ba66cae426e480a39ba57.png](https://i-blog.csdnimg.cn/blog_migrate/b1dff5754e282b59c80641360d34c597.png)
T - L - V
存储方式的优点是
- 不需要分隔符 就能 分隔开字段,减少了 分隔符 的使用
- 各字段 存储得非常紧凑,存储空间利用率非常高
- 若 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要编码
在使用varint编码后,只需要用T-V来表示,大大节省了空间。
原文链接:Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?
总结:protobuf使用varint编码和T-V表示方式,大大节省序列化后的数据空间,并且编码方式简单,序列化速度很快,并且protobuf直接对数据进行处理,与语言平台无关,可以供任意语言使用。
在项目中,使用基于protobuf的gRPC,接下来介绍一下RPC.
RPC远程过程调用,是一种封装了各层网络协议,并包含序列化和反序列化功能的一种通讯框架,基于protobuf的gRPC相当于序列化和反序列化功能是用protobuf实现的。
![3c8c350bed0286ad77d165222c9134a6.png](https://i-blog.csdnimg.cn/blog_migrate/1690cba0eeef84bf6f5e3dd8302b3050.jpeg)
![fcd03ca38b967fc42cdd30342e0201c2.png](https://i-blog.csdnimg.cn/blog_migrate/a08e39c51a474bdbd6bbabbd052f7e20.jpeg)
![b694e5252d7189841bb3b5d7bd9f15d6.png](https://i-blog.csdnimg.cn/blog_migrate/df50644d9a0cbce528f84f267d722073.jpeg)
![0afc6afbc536e1080055a02500222dfa.png](https://i-blog.csdnimg.cn/blog_migrate/4df3e9dd08af1fbdf5d4ed28ae1d2784.jpeg)
![35ea05f83646ca21bddeece2b94628f8.png](https://i-blog.csdnimg.cn/blog_migrate/345bd3e1e75faed9099bb9f7296f123a.png)
- Client A的应用层代码中,调用了Calculator的一个实现类的add方法,希望执行一个加法运算;
- 这个Calculator实现类,内部并不是直接实现计算器的加减乘除逻辑,而是通过远程调用Service B的RPC接口,来获取运算结果,因此称之为Stub;
- Stub怎么和Service B建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,这个工具将帮你实现远程通讯的功能,比如Java的Socket,就是这样一个库,当然,你也可以用基于Http协议的HttpClient,或者其他通讯工具类,都可以,RPC并没有规定说你要用何种协议进行通讯;
- Stub通过调用通讯工具提供的方法,和Service B建立起了通讯,然后将请求数据发给Service B。需要注意的是,由于底层的网络通讯是基于二进制格式的,因此这里Stub传给通讯工具类的数据也必须是二进制,比如calculator.add(1,2),你必须把参数值1和2放到一个Request对象里头(这个Request对象当然不只这些信息,还包括要调用哪个服务的哪个RPC接口等其他信息),然后序列化为二进制,再传给通讯工具类,这一点也将在下面的代码实现中体现;
- 二进制的数据传到Service B这一边了,Service B当然也有自己的通讯工具,通过这个通讯工具接收二进制的请求;
- 既然数据是二进制的,那么自然要进行反序列化了,将二进制的数据反序列化为请求对象,然后将这个请求对象交给Service B的Stub处理;
- 和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析请求对象,知道调用方要调的是哪个RPC接口,传进来的参数又是什么,然后再把这些参数传给对应的RPC接口,也就是Calculator的实际实现类去执行。很明显,如果是Java,那这里肯定用到了反射。
- RPC接口执行完毕,返回执行结果,现在轮到Service B要把数据发给Service A了,怎么发?一样的道理,一样的流程,只是现在Service B变成了Client,Service A变成了Server而已:Service B反序列化执行结果->传输给Service A->Service A反序列化执行结果 -> 将结果返回给Application,完毕。
总结:RPC包含了网络传输,序列化和反序列化,call id映射等。
参考链接:https://waylau.com/remote-procedure-c alls/
https://www.jianshu.com/p/2accc2840a1b
https://www.jianshu.com/p/5b90a4e70783