第二章:gRPC通信模型与协议
聊透明加密改造之前,得先把gRPC的底子摸透。说白了,加密改的是数据在“路上”的形态。你不清楚数据怎么打包、怎么发、怎么流,那加密点都找不对。
这一章,我带你拆开gRPC的通信模型。从数据编码到传输协议,再到三种通信模式。都是实战里绕不开的硬知识。
2.1 Protobuf 编码原理
gRPC默认用Protobuf做序列化。这不是偶然。我的项目经验告诉我,选对序列化方式,性能差一个数量级。
2.1.1 为什么是Protobuf?
你想想看,相比JSON,Protobuf有什么好处?
- 体积小——二进制编码,没有冗余的字段名
- 速度快——解析时几乎零拷贝,直接读偏移量
- 强类型——.proto文件就是契约,服务端和客户端必须对齐
2.1.2 编码细节:Varint 与 Tag-Len-Val
Protobuf的编码原理,说白了就两句话:小整数用Varint压缩,大字段用TLV打包。
拿一段proto定义举例:
message UserRequest {
int32 user_id = 1;
string name = 2;
}
假设user_id = 150,name = "alice”。序列化后长什么样?
| 字段 | Key (Tag) | Value |
|---|---|---|
| user_id | 0x08 | 0x96 0x01 |
| name | 0x12 | 0x05 0x61 0x6c 0x69 0x63 0x65 |
2.2 HTTP/2 多路复用
gRPC跑在HTTP/2上。这不是偶然。HTTP/2的多路复用,是gRPC高性能的核心秘诀。
2.2.1 为什么不是HTTP/1.1?
HTTP/1.1有队头阻塞的问题。一个请求卡住了,后面的都得排队。我刚开始做微服务时,就因为这个问题吃了大亏。
HTTP/2怎么解决的?它引入了一个概念——流。
- 一个TCP连接上可以有多个流
- 流之间完全并行,互不阻塞
- 每个流有唯一的流ID
2.2.2 帧、流、多路复用的关系
HTTP/2的数据被切分成帧。帧是传输的最小单位。多个帧组成一个流。
你想想看,一个TCP连接里同时跑多个请求。每个请求的数据被切成帧,帧与帧混杂着传输。接收端通过流ID把数据重新拼回去。
2.3 流式通信模式详解
gRPC支持四种通信模式。我个人强烈建议,在写加密中间件之前,先把这四种模式搞明白。因为不同模式的加密点和解密时机完全不同。
2.3.1 四种模式对比
| 模式 | 描述 | 典型场景 |
|---|---|---|
| Unary (一元) | 客户端发一个请求,服务器回一个响应 | 查询用户信息 |
| Server Streaming | 客户端发一个请求,服务器持续回数据 | 实时新闻推送 |
| Client Streaming | 客户端持续发送,服务器最后回一个 | 批量上传日志 |
| Bidirectional Streaming | 双方独立发送,可同时读写 | 实时语音对话 |
2.3.2 Unary 模式
这是最简单的模式。我说句实话,80%的业务场景用Unary就够了。
请求和响应各一条消息。加密改造也很直接:在客户端发送前加密消息体,在服务端收到后解密。
2.3.3 Server Streaming 模式
这种模式下,客户端只发一次请求。服务器持续返回多条消息。
加密的难点在哪?服务端每发一条都要加密,不能等所有消息都攒在一起再处理。
Send方法中做加密操作。我见过有人在Send之前统一加密,结果流已经关闭了。2.3.4 Client Streaming 与 Bidirectional
Client Streaming里,客户端持续发送。加密必须在Send之前做。服务端收到后立即解密。
Bidirectional更复杂。双方互不等待,都在独立读写。加密必须同时处理Send和Recv。
Send和Recv之间加了超时控制,问题才解决。
学完这一章,你应该对gRPC底层有了清晰的认识。Protobuf负责高效打包,HTTP/2负责并行传输,流式模式决定了加密时机。有了这个基础,第三章我们开始动手做真正的加密改造。