gRPC 协议基础归纳总结

讲 HSM 封装之前,得先把 gRPC 协议吃透。我接触 gRPC 好几年了,最早是在一个跨语言微服务项目里用的,一开始觉得它不就是个 RPC 框架嘛,后来踩了不少坑才明白——协议层的东西才是决定可靠性的关键。说白了,gRPC 的核心就三样:Protobuf 序列化、HTTP/2 传输、四种调用模式。我们一个一个捋。

Protobuf:接口契约的基石

gRPC 默认用 Protobuf 定义接口和数据。你不需要写一堆 JSON 手撸序列化,而是写一个 .proto 文件,然后生成客户端和服务端代码。

我个人习惯先定义 service 和 message,再写业务逻辑。这样团队协作时,接口就是“合同”,改一下 proto 所有人都看得见。
syntax = "proto3";

package hsm;

// 定义签名请求
message SignRequest {
    bytes data = 1;
    string key_id = 2;
}

message SignResponse {
    bytes signature = 1;
}

service HsmService {
    rpc Sign(SignRequest) returns (SignResponse);
}

定义好了 proto,用 protoc 编译成你需要的语言,比如 Go、Java、C++。我在项目里见过直接手写序列化的,维护起来那叫一个痛苦。用 Protobuf 的好处很明显:体积小、解析快、前后兼容。你想想,HSM 设备返回的签名数据往往是几百字节的二进制,用 JSON 包一层至少膨胀 30%,Protobuf 几乎零开销。

HTTP/2:为什么 gRPC 跑得快

gRPC 底层依赖 HTTP/2,而不是 HTTP/1.1。这可不是随便选的。HTTP/1.1 每次请求都要建一个 TCP 连接,头部是纯文本,并发还得靠多个连接。HTTP/2 引入了多路复用、头部压缩、服务器推送。我去年调一个 HSM 网关的性能瓶颈,发现是 HTTP/1.1 连接数把设备搞崩了,换成 gRPC 后一条 TCP 连接能扛上千个并发调用。

特性 HTTP/1.1 HTTP/2
传输格式 明文文本 二进制帧
多路复用 不支持,需多连接 单连接多流
头部压缩 无 HPACK
流优先级 无 支持
记住:gRPC 要求服务端必须支持 HTTP/2,否则连不上。我在内网环境遇到过代理只支持 HTTP/1.1,折腾了两天才排查出来。

四种 RPC 模式

gRPC 不是只有你发一条我回一条那么简单。它支持四种模式,每种对应不同场景。我画了张图,帮你快速理解。

gRPC 四种 RPC 模式结构图 客户端 服务端 Unary RPC Server Streaming Client Streaming Bidirectional 箭头说明 实线:单次请求/响应 虚线:多次流式传输

四种模式各有各的用途。我简单总结一下:

  • Unary RPC(简单 RPC):客户端发一个请求,服务端回一个响应。最常见,适合签名、验签这种单次操作。
  • Server Streaming(服务端流):客户端发一个请求,服务端连续返回多条消息。比如 HSM 批量签名时,可以一次性下发一批数据哈希,服务端逐条返回签名结果。
  • Client Streaming(客户端流):客户端连续发多条消息,服务端最后汇总回一个响应。比如上传大批量日志让 HSM 计算聚合签名。
  • Bidirectional Streaming(双向流):双方任意时刻都可以收发。我曾在一个密钥协商场景用过,两端来回交换密钥材料,实时性很高。
我曾经在双向流里忘记处理流关闭信号,导致服务端 goroutine 泄漏。记住:gRPC 流必须由客户端或服务端主动关闭,双方都要监听 context.Done()。

gRPC 调用完整流程

一次 gRPC 调用,走过这么几步:

  1. 客户端通过 Stub 发起调用,Protobuf 将参数序列化为二进制。
  2. HTTP/2 将数据封装成帧,带上流 ID,经过多路复用发送到服务端。
  3. 服务端接收帧,根据流 ID 重组消息,反序列化为请求对象。
  4. 业务逻辑处理,构造响应,序列化后经 HTTP/2 返回。
  5. 客户端收到响应,反序列化,拿到结果。

整个过程被框架封装得很干净。你只需关心 .proto 文件和业务代码。但底层涉及到的 Deadline(超时)、Metadata(元数据)、拦截器(Interceptor)这些概念也得熟悉。比如 HSM 调用通常需要带上 认证信息,就可以放在 Metadata 里。

错误处理规范

gRPC 内置了一套错误码,比 HTTP 状态码更贴合 RPC 场景。我整理了一份常用码表:

状态码 含义 典型场景
OK 成功 正常返回
INVALID_ARGUMENT 参数错误 签名数据长度超限
UNAUTHENTICATED 未认证 Metadata 中缺少令牌
DEADLINE_EXCEEDED 超时 HSM 硬件响应过慢
INTERNAL 内部错误 设备驱动崩溃
我个人建议客户端设置 withDeadline,避免因为 HSM 硬件卡死导致整个服务 Hang 住。3 秒超时通常够用,签名密集任务可以适当放大。