grpc

RPC :数据编码内存对象可传输的字节流 转化),请求映射

数据编码

XML 日薄西山,JSON 风头正盛,Protobuf 方兴未艾

why Protobuf ?谷歌出品,某些场景下效率比 JSON 高

所有的优化都是有代价的。思考选择什么和放弃什么

JSON 缺点 & Protobuf 选择

1
2
{ "int":12345, "str": "hello", "bool": true }
{ "int":67890, "str": "hello", "bool": false }
  • 非字符串的编码低效

    int value 12345,内存表示只占两个字节,转成 JSON 却要五个字节。 bool 字段则占了四或五个字节。

  • 信息冗余

    同一个接口同一个对像,只是 int 字段的值不同,每次都还要传输"int"这个字段名

JSON 在可读性和编码效率之间选择了可读性,所以效率方面做了一定的牺牲

Protobuf 一方面用 VarInts 对数字进行编码,解决效率问题;另一方面给每个字段指定一个整数编号,只传字段编号,解决冗余问题

接收方如何知道各个编号对应哪个字段?只能事先约定。Protobuf 用 .proto 文件记录字段和编号的对应关系

1
2
3
4
5
message Demo {
  int32 i = 1;
  string s = 2;
  bool b = 3;
}

Protobuf 提供了一系列工具,为 proto 描述的 message 生成各种语言的代码

传输效率上去了,工具链也更加复杂了。gRPC 抓过时一定会怀念 JSON 的

请求映射

.proto 作为 IDL,Protobuf 可做很多 JSON 不方便做的事情。其中最重的就是 RPC 描述!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package demo.hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

gRPC 就是用了 Protobuf 的 service 来描述 RPC 接口

proto 文件定义了 Greeter 服务, SayHello 方法,接受 HelloRequest 消息并返回 HelloReply 消息。如何实现这个 Greeter 则是语言无关的,所以叫 IDL

1
/{包名}.{服务名}/{接口名}

Greeter 服务对应的路径 /demo.hello.Greeter/SayHello

gRPC 协议规定Content-Typeheader 取值 application/grpc,也可application/grpc+proto

想用 JSON 编码,也可application/grpc+json

gRPC 通信(非流式调用,unary)内容

请求内容

1
2
3
4
5
6
POST /demo.hello.Greeter/SayHello HTTP/1.1
Host: grpc.demo.com
Content-Type: application/grpc
Content-Length: 1234

<Length-Prefixed Message>

响应内容

1
2
3
4
5
HTTP/1.1 200 OK
Content-Length: 5678
Content-Type: application/grpc

<Length-Prefixed Message>

stream rpc

gRPC 持三种流式接口,参数前加上 stream 关键字

  • 请求流(推送或者短信)
  • 响应流(订阅消息通知)
  • 双向流(实时语音转字幕)
1
2
3
4
5
6
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayHello (stream HelloRequest) returns (HelloReply) {}
  rpc SayHello (HelloRequest) returns (stream HelloReply) {}
  rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}

引入Length-Prefixed Message,同一个 gRPC 请求的不同消息共用 HTTP 头信息,给每个消息单独加五字节前缀来表示压缩和长度信息

HTTP/1.1 也支持复用 TCP 连接,但所有请求必须排队(请求、等待、响应)顺序进行。先到先服务。而在实际的业务场景中肯定会有一些请求响应时间很长,客户端在收到响应之前会一直霸占着TCP连接。在这段时间里别的请求要么等待,要么发起新的 TCP 连接。一言以蔽之,HTTP/1.1 不能充分地复用 TCP 连接

HTTP/2 引入 stream概念 ,解决 TCP 连接复用的问题(同样有取舍问题)。可简单理解为逻辑上的 TCP 连接,在一条 TCP 连接上并行收发 HTTP 消息,而无需像 HTTP/1.1 那样等待