gRPC是一款RPC框架,也是本系列的主角,在性能和版本兼容上做了提升和让步:
- Protobuf进行数据编码,提高数据压缩率
- 使用HTTP2.0弥补了HTTP1.1的不足
- 同样在调用方和服务方使用协议约定文件,提供参数可选,为版本兼容留下缓冲空间
protobuf是一款用C++开发的跨语言、二进制编码的数据序列化协议,以超高的压缩率著称。它和早期的RPC方案一样,需要双方维护一个协议约束文件,以.proto结尾,使用proto命令对文件进行解析,会生成对应的Stub程序,客户端和服务端都需要保存这份Stub程序用来进行编解码。对于这种协议文件导致的升级困难问题,protobuf 3 中定义的字段默认都是可选的(可以不传),在接口升级时,部分客户端不需要升级自己的Stub程序。
userInfo.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.hualala.aleenjava.grpc.userInfo";
service UserInfoService {
rpc queryUserInfo(UserInfoReq) returns (UserInfoResponse) {}
rpc queryUserInfo2(UserInfoReq) returns (UserInfoResponse) {}
rpc queryUserInfo3(UserStr) returns (UserStr) {}
}
message UserStr{
string str = 1;
}
message UserInfoReq {
string name = 1;
int64 id = 2;
}
message UserInfoResponse {
int32 code = 1;
string msg = 2;
bool success = 3;
message Data {
UserInfo userInfo = 1;
}
Data data = 4;
}
message UserInfo {
int64 id = 1;
string name = 2;
string sex = 3;
string addr = 4;
}
对于JSON等文本形式的序列化协议来说,protobuf能有几十倍空间和性能提升, 比如传输123,文本类的需要3个字节(ascii 31 32 33)来传输,而二进制类只需要一个字节(01111011)就可以表示。
同时protobuf会维护.proto文件,这样在解析文件生成Stub程序时,可以对方法名等进行编号,传输时只传编号,而不用传方法的名字,这又可以节省大量字节,还有其他更多的精巧压缩方法,比如TLV 。
解决了数据体积的问题后,gRPC使用HTTP2来改善传输性能。 HTTP2是在HTTP1.1的基础上做了大量的改进,HTTP1.1虽然引入了KeepAlive复用TCP连接,但仍然有很多问题:
- 使用KeepAlive的请求是串行执行(非pipeline时),pipeline时有队首阻塞问题
- 每次都需要发送不必要的Header
- 不能双向通信
简单补充一下pipeline,HTTP1.1中允许多个请求复用连接,同时可以一口气将请求全部发出去,不用一个返回后再发送第二个,提升并发性。而服务端需要将请求的结果,按照pipeline中发送的顺序进行顺序返回,如果靠前的请求阻塞了,那么靠后请求返回就会被动等待。
HTTP2解决了这些问题,引入了新的机制:
- 在两端建立Header索引表,每次只发送索引,减小header体积
- 建立虚拟通道,将数据拆分成多个流,每个流有自己的ID和优先级,并且流可以双向传输,每个流可以进一步拆成多个帧。可以将多个请求切成不同的流发送,每个流可以独立返回,避开1.1的串行或队首阻塞问题。
同时,基于HTTP2的数据流机制,gRPC客户端和服务端可以实现批量操作优化,客户端可以攒一些请求,一口气发给服务端,服务端也可以批量返回结果,借此实现流式rpc。