字段编号永不复用
编号是线上的身份,不是展示顺序。删除字段后用 reserved 保留编号和名称。
gRPC 是一套基于 HTTP/2 的高性能 RPC 框架。你定义服务契约,工具生成客户端与服务端骨架,业务代码只关注调用与实现。
内部微服务调用:接口稳定、语言多样、追求低延迟。
实时与流式场景:推送、聊天、IoT、持续数据处理。
公开 Web API:浏览器支持与调试体验通常不如 REST。
弱网络与简单 CRUD:协议复杂度可能超过收益。
| 维度 | gRPC | REST | GraphQL |
|---|---|---|---|
| 契约 | 强 | 弱 / OpenAPI | 强 |
| 传输 | HTTP/2 | HTTP/1.1+ | HTTP |
| 流式 | 原生四模式 | 需扩展 | Subscription |
| 浏览器 | 需 gRPC-Web | 原生 | 原生 |
| 可读性 | 二进制 | JSON | JSON |
gRPC = 强类型服务契约 + 代码生成 + HTTP/2 传输 + Protobuf 序列化。
Protobuf 不只是序列化格式。它定义字段、消息、服务与演进规则,是客户端和服务端共享的唯一事实来源。
syntax = "proto3";
package order.v1;
option go_package = "example/gen/order/v1;orderv1";
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc WatchOrders(WatchRequest) returns (stream Order);
}
message Order {
string id = 1;
int64 amount_cents = 2;
OrderStatus status = 3;
reserved 4, 5;
}
ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
defer cancel()
order, err := client.GetOrder(ctx, &orderv1.GetOrderRequest{
Id: orderID,
})
if err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.NotFound { ... }
}
func (s *Server) GetOrder(
ctx context.Context,
req *orderv1.GetOrderRequest,
) (*orderv1.Order, error) {
order, err := s.repo.Find(ctx, req.GetId())
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "order not found")
}
return order, nil
}
编号是线上的身份,不是展示顺序。删除字段后用 reserved 保留编号和名称。
旧客户端会忽略未知字段;新客户端读取旧消息时得到默认值。
即使 wire type 相同,也可能发生精度、语义或语言映射问题。
order.v1 让破坏性升级拥有清晰边界,不依赖路径约定猜测。
字段名不会上网线,只传递紧凑的 tag + value。整数用 Varint 编码,小值占用更少字节。
选择模式时先问两个问题:请求是一条还是一串?响应是一条还是一串?答案直接对应四种 RPC。
适合查询详情、创建订单等一次请求对应一次结果的操作。先从 Unary 开始,只有明确需要持续传输时再用 Stream。
| 模式 | Proto 签名 | 生命周期重点 | 常见场景 |
|---|---|---|---|
| Unary | Req → Res | Deadline、重试、幂等 | 大多数服务调用 |
| Server Stream | Req → stream Res | 客户端消费速度、取消 | 订阅、批量下载 |
| Client Stream | stream Req → Res | 服务端聚合、半关闭 | 上传、指标汇总 |
| Bidi Stream | stream Req → stream Res | 并发读写、背压、断线 | 聊天、协作、游戏 |
调用看起来像本地函数,实际上经过序列化、HTTP/2 帧、连接复用、服务端分发,再沿原路返回。
生成代码完成方法名、路径、消息类型的绑定。你仍需显式传入 Context,让取消、Deadline 与 Metadata 穿过调用链。
多个 RPC Stream 共享 TCP 连接,避免连接爆炸与 HTTP/1.1 队头阻塞。
客户端决定最多等多久;服务端应检查 Context,并把剩余预算传给下游。
适合 Authorization、Trace ID、Locale。业务数据仍应放在消息体中。
authorization: Bearer ...
traceparent: 00-...
x-request-id: req_72f
没有设置 Deadline。下游变慢时,请求会持续占用连接、goroutine 与内存,最终形成级联故障。
gRPC 错误由 Status Code、可读 Message 和可选 Details 组成。关键不是“返回错误”,而是给调用方稳定的处理语义。
| 状态码 | 何时使用 | 可否重试 |
|---|---|---|
INVALID_ARGUMENT | 参数本身无效 | 否 |
NOT_FOUND | 资源不存在 | 通常否 |
ALREADY_EXISTS | 创建发生冲突 | 否 |
UNAUTHENTICATED | 身份凭证缺失或无效 | 刷新凭证后 |
PERMISSION_DENIED | 身份有效但无权限 | 否 |
RESOURCE_EXHAUSTED | 限流或配额耗尽 | 退避后 |
UNAVAILABLE | 服务暂时不可用 | 是 |
DEADLINE_EXCEEDED | 超过时间预算 | 谨慎 |
INTERNAL | 服务端内部不变量破坏 | 谨慎 |
推荐公式:min(cap, base × 2ⁿ) × random(0.8, 1.2)
少量、退避、带抖动、受 Deadline 约束,且只用于幂等操作和瞬态错误。
吞吐、延迟、CPU 和内存互相制约。下面的模型用于理解趋势,真实参数必须用你的消息、链路与部署环境压测。
当前组合适合多数内部服务。连接复用可避免握手与连接建立成本。
从上到下做,越靠前通常收益越稳定。
不要每次请求新建连接。连接池化通常由框架内部处理。
限制尾延迟与资源占用,让过期工作尽早取消。
避免巨大 message;批量接口设置上限,必要时用流式分片。
高频小消息可以通过 streaming 减少重复 Header 与调度成本。
文本与大消息通常受益;小型 Protobuf 可能得不偿失。
过于频繁的 PING 会浪费网络和服务端资源,还可能触发代理限制。只为探测失效连接配置合理分钟级周期。
HTTP/2 有连接级与 Stream 级窗口。消费端处理过慢时,发送端会被限制,避免无限堆积。
平均值会掩盖抖动。结合排队时间、GC、下游耗时和重试次数定位尾延迟。
生产可用性来自一组共同工作的机制:服务发现、负载均衡、健康检查、TLS、可观测性与容量保护。
下一步:选一个熟悉的业务,用 Unary + Deadline + Status Code 实现最小服务,再用压测验证你的性能假设。