RPC and gRPC Internals
What RPC solves, IDL and Protobuf, gRPC over HTTP/2, streaming, interceptors, and gRPC vs REST benchmarks.
Why RPC at All
A remote call should feel like a local function call: an Interface Definition Language describes the service, a stub generator produces clients and servers, serialization is binary-compact, and the transport is multiplexed HTTP/2. The cost is opaqueness — you cannot curl it.
IDL and Code Generation
syntax = "proto3";
package shop.v1;
option go_package = "shop/v1;shopv1";
message OrderRequest {
int64 user_id = 1; // 字段编号:升级全靠它
repeated string skus = 2;
map<string, int32> qty = 3;
}
message OrderReply {
int64 order_id = 1;
string status = 2;
}
service OrderService {
rpc Create (OrderRequest) returns (OrderReply);
rpc Stream (stream OrderRequest) returns (stream OrderReply); // 双向流
}
# 生成:protoc --go_out=. --go-grpc_out=. order.proto
# 各语言插件生成存根:Java/Python/Node/Rust 均成熟
HTTP/2 Multiplexing
- One TCP connection carries hundreds of concurrent streams — no head-of-line blocking from multiple connections.
- Binary framing: headers are HPACK-compressed (clients send static table + differential encoding).
- Beware the real-world limit: per-connection stream caps and LB (Nginx/Envoy) settings matter at high QPS.
- Protobuf wire format is field-number-based: adding fields is safe, changing a field number is not.
Interceptors, Metadata, Deadline
拦截器 = 各语言版本的「中间件」,gRPC 里通常分一元(Unary)与流(Stream)拦截器
客户端:注入鉴权 token、traceId、监控埋点、重试/超时
服务端:鉴权、限流、日志、panic-recovery
Metadata(对应 HTTP 头):
metadata.New(map[string]string{"authorization": token, "x-trace-id": id})
服务端从 ctx 取 md, ok := metadata.FromIncomingContext(ctx)
Deadline / Cancellation(必用):
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() // 服务端据此中断,不会空转
链路上逐级缩短,保证端到端超时收敛
健康检查与服务治理:
gRPC Health Checking Protocol:health/v1
黄金信号:QPS、延迟分位、错误码分布(grpc_status)
gRPC vs REST
| 维度 | gRPC | REST/HTTP |
|---|---|---|
| 序列化 | Protobuf 二进制(小、快) | JSON(可读) |
| 流 | 原生四种流模式 | SSE/WebSocket 另行实现 |
| 契约 | IDL 强类型生成 | OpenAPI 可选 |
| 浏览器 | 需 gRPC-Web 网关 | 原生支持 |
| 生态 | K8s/Envoy 原生(xDS 就是 gRPC) | 通用 |
| 典型用法 | 内部服务间调用 | 对前端/第三方开放 API |
Common hybrid: gRPC inside, REST or GraphQL at the edge through a gateway that does the translation (grpc-gateway, Kratos, or a custom gateway).
为什么需要 RPC
让远程调用像本地函数调用:IDL 描述接口,存根生成器产出客户端/服务端,序列化二进制紧凑,传输走 HTTP/2 多路复用。代价是不可调试——curl 不动它。
IDL 与代码生成
syntax = "proto3";
package shop.v1;
option go_package = "shop/v1;shopv1";
message OrderRequest {
int64 user_id = 1; // 字段编号:升级全靠它
repeated string skus = 2;
map<string, int32> qty = 3;
}
message OrderReply {
int64 order_id = 1;
string status = 2;
}
service OrderService {
rpc Create (OrderRequest) returns (OrderReply);
rpc Stream (stream OrderRequest) returns (stream OrderReply); // 双向流
}
# 生成:protoc --go_out=. --go-grpc_out=. order.proto
# 各语言插件生成存根:Java/Python/Node/Rust 均成熟
HTTP/2 多路复用
- 一条 TCP 跑数百条并发流——没有多连接的队头阻塞。
- 二进制帧:头部经 HPACK 压缩(静态表 + 增量编码)。
- 注意高 QPS 下连接流上限与负载均衡(Nginx/Envoy)的调参。
- Protobuf 按字段编号编码:加字段安全,改字段编号就是破坏性变更。
拦截器、元数据与截止时间
拦截器 = 各语言版本的「中间件」,gRPC 里通常分一元(Unary)与流(Stream)拦截器
客户端:注入鉴权 token、traceId、监控埋点、重试/超时
服务端:鉴权、限流、日志、panic-recovery
Metadata(对应 HTTP 头):
metadata.New(map[string]string{"authorization": token, "x-trace-id": id})
服务端从 ctx 取 md, ok := metadata.FromIncomingContext(ctx)
Deadline / Cancellation(必用):
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() // 服务端据此中断,不会空转
链路上逐级缩短,保证端到端超时收敛
健康检查与服务治理:
gRPC Health Checking Protocol:health/v1
黄金信号:QPS、延迟分位、错误码分布(grpc_status)
gRPC 与 REST
| 维度 | gRPC | REST/HTTP |
|---|---|---|
| 序列化 | Protobuf 二进制(小、快) | JSON(可读) |
| 流 | 原生四种流模式 | SSE/WebSocket 另行实现 |
| 契约 | IDL 强类型生成 | OpenAPI 可选 |
| 浏览器 | 需 gRPC-Web 网关 | 原生支持 |
| 生态 | K8s/Envoy 原生(xDS 就是 gRPC) | 通用 |
| 典型用法 | 内部服务间调用 | 对前端/第三方开放 API |
常见折中:内部 gRPC,边缘网关转 REST/GraphQL(grpc-gateway、Kratos 或自研网关)。