三个特点
广义上来说,所有本地应用程序外的调用都可以归类到rpc,不管是分布式服务,第三方服务的http接口,还是redis的一次请求。从抽象的角度来讲,它们都一样上RPC,由于不在本地执行,都有如下三个特点。所有的RPC框架都是围绕这几个点做优化:
- 需要实现约定调用的语义,接口语法
- 需要网路传输
-
如何用Protobuf组织内容
要以更优的方案,达到更低的成本,更快的速度,内容编码方式就是一个非常重要的点,rpc调用的request和response内容在调用中有着不小的开销:
内容的序列化,反序列化,如果效率更高,则对cpu的消耗更小
- 内容会在网络中传输,有协议栈拷贝成本,带宽成本,GC等。体积越小,效率越高
在这方面,RPC框架都有一下几个目标:
- 能快速地序列化,反序列化
- 序列化体积越小越好
- 跨语言,和语言栈无关
- 简单,类型明确
- 易拓展,可以简单迭代,向后兼容
grpc对此的解决方案是丢弃json, xml这种传统策略,使用protobuf buffers。是google开发的一种跨语言,跨平台,可扩展的用于序列化数据的协议
// XXXX.proto
service Test {
rpc HowRpcDefine (Request) returns (Response) ; // 定义一个RPC方法
}
message Request {
//类型 | 字段名字| 标号
int64 user_id = 1;
string name = 2;
}
message Response {
repeated int64 ids = 1; // repeated 表示数组
Value info = 2; // 可嵌套对象
map<int, Value> values = 3; // 可输出map映射
}
message Value {
bool is_man = 1;
int age = 2;
}
以上是一个使用样例,包含方法定义,入参,出参,可以看出有几个明确的特点:
- 有明确的类型,支持的类型有多种
- 每个field会有名字
- 每个field有一个数字标号,一般按顺序排列。下文编解码会用到这个点
- 能表达数组,map映射等类型
- 通过嵌套message可以表达复杂的对象
- 方法,参数的定义落到一个proto文件中,依赖双方同时持有这个文件,并依此进行编码
但是proto文件并不是代码,不能执行。要想直接跨语言是不行的,需要要有对应语言的中间代码才行,中间代码需要有一下能力:
- 将message转换为struct, 例如golang里面的struct,需要各自语言表达后,才能被理解
- 需要有进行编解码的代码,能解码的内容为自己语言的对象,能将对象编码为对应的数据
这些代码手写是不行的,protobuf对此的解决方案是,提供一个统一的protoc工具,这是一个C++翻译工具,可以通过proto文件,生成某特性语言的中间代码,实现上面说的两个能力。
// 依赖目录 生成golang中间代码 对应proto文件地址
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/XXX.proto
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/XXX.proto // 生成java中间代码
直观对比
为什么选择做protobuf, 而不是普及最广的json作为编码方案呢?可以做一个直观对比,以上文proto中的response为例,一次输出json的结果是:
"{\"ids\":[123,456],\"info\":{\"is_man\":true,\"age\":20},\"values\":{\"110\":{\"is_man\":false,\"age\":18}}}"
所有内容被打包成了一个字符串,里面包含字段名,value,response很大时,体积消耗很大。主要浪费在三方面:
- 字段名,例如上面的ids, info等,如果json体积太大,则重复会更多
- 数字勇字符串表达了,例如123数字变成了“123”,这在编码后由一个字节变为三个字节
- 类型字符,如[],{}
但如果是protobuf呢?输出是一段人眼无法理解的字符串,里面
- 去掉了字段名,转而以字段标号替代,通过标号可以在proto中找到字段名
- 没有类型字符串
- 用二进制表达内容,不会将数字转为字符串
- 字段值按顺序依次排列
这使得protobuf的编码结果体积,通常是json编码后的十分之一。同时由于排列简单,其解析算法的时空复杂度远小于json,对cpu的消耗也小很多。这使得protobuf在大数据量,高频率的数据下,远胜于json,在大规模分布式RPC场景中广泛使用。
优缺点
首先,我们可以总结出pb的优点:
- 没有打包无用的数据,排列紧凑,体积小,利于传输
- 解析策略简单,序列化/反序列化速度快
- 能较好的兼容字段,不能解析到的会跳过
但是缺点也非常明显:
- 肉眼看不出value是什么,无法自描述,难以debug
- 需要proto文件才能知道如何解析,否则是天书,这在灵活性上不如json
- 其实本质上起差别在于,json是设计给别人看的,protobuf则是利于机器
- 适合的场景不同,各有利弊。作为工具,讨论其快,好,差是没有意义的,在合适的地方,用合适的工具才是最重要的