上一篇看到一条消息是 5 字节前缀加 protobuf。这篇继续追问:消息大了会怎样,多条消息怎么排,压缩标志怎么用,超过大小限制时线上发生了什么?
payload 大小 protobuf 消息总长 客户端 DATA 帧负载长度 5 字节前缀里的长度 0 3 8 [8] (标志 0,长度 3)(剩余字节 0) 1 6 11 [11] (标志 0,长度 6)(剩余字节 0) 300 306 311 [311] (标志 0,长度 306)(剩余字节 0) 16000 16006 16011 [16011] (标志 0,长度 16006)(剩余字节 0) 20000 20007 20012 [16384 3628] (标志 0,长度 20007)(剩余字节 0) 70000 70007 70012 [16384 16384 16384 12755 3628 4477] (标志 0,长度 70007)(剩余字节 0) 1048576 1048583 1048588 [16384 16384 16384 16384]…共 65 帧,最后一帧 22 (标志 0,长度 1048583)(剩余字节 0)
一条 gRPC 消息 = 1 字节压缩标志 + 4 字节大端长度 + 载荷。HTTP/2 单个 DATA 帧最多 16384 字节(默认),大消息被切成多个 DATA 帧,接收方把负载拼起来再按前缀切。消息边界与帧边界无关。
服务端 DATA 帧 3 个,负载长度 [9 11 11];按前缀切出 3 条消息:(标志 0,长度 4) (标志 0,长度 6) (标志 0,长度 6),剩余 0 字节
一条流里可以有任意多条消息(流式 RPC)。grpc-go 每条消息一个 DATA 帧,但协议不要求。
请求头 grpc-encoding: gzip 请求头 grpc-accept-encoding: gzip 请求头 grpc-timeout: 9999931u 原始 payload 5500 字节;线上这一条消息:压缩标志 1,长度字段 73(压缩后的 gzip 字节数);剩余 0 响应头 :status: 200; content-type: application/grpc; grpc-encoding: gzip
压缩标志位是 1 时,载荷是压缩后的字节,长度字段是压缩后的长度,编码方式写在请求头 grpc-encoding 里。压缩是逐条消息的,所以也可以同一条流里有的压缩有的不压缩。
DATA 帧负载 5 字节:0000000000,标志 END_STREAM=true
空消息仍然要 5 字节前缀:标志 0、长度 0。
客户端得到 code=ResourceExhausted message="grpc: received message larger than max (5242890 vs. 4194304)",用时 2.5 ms 线上客户端→服务端 786960 字节(消息本身 5242880 字节);服务端→客户端 172 字节 S→C 流 23 HEADERS :status: 200; content-type: application/grpc; grpc-status: 8; grpc-message: grpc: received message larger than max (5242890 vs. 4194304) S→C 流 23 RST_STREAM 错误码 NO_ERROR
服务端默认只接收 4 MiB 以内的消息。读到前缀里的长度大于限制就立刻回 ResourceExhausted 并结束流,不会把 5 MiB 全收完;客户端是否已发出大部分字节,取决于时机。