跳转到内容

gRPC-Go — Google RPC 框架的官方 Go 实现

是什么

gRPC-Go 是 Google 开源的 gRPC 框架在 Go 语言上的官方实现——它把”两个服务之间打远程电话”这件事,标准化成一份 .proto 接口文件加生成的 Go 代码。日常类比:像两家公司签合同寄文件——先约定好”信封长什么样、字段怎么填”(Protobuf),再共用同一条专线(HTTP/2),收发都按合同走,少了 REST/JSON 那种”字段名拼错运行时才发现”的尴尬。

你写一份 .proto 文件描述接口:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

跑一句 protoc 命令,gRPC-Go 自动生成两侧代码:服务端实现接口,客户端拿着 stub 像调本地函数一样调远端方法。整个调用走 HTTP/2 二进制传输,比 REST/JSON 省带宽、省连接、字段类型错了编译期就报。

为什么重要

不理解 gRPC-Go,下面这些事都没法解释:

  • 为什么 Kubernetes / etcd / TiDB 这些 Go 基础设施内部通信全用它,不用 REST
  • 为什么微服务之间的”高频小调用”场景里 gRPC 性能远好于 HTTP/JSON
  • 为什么实时行情推送、上传大文件分片,用 gRPC stream 比 WebSocket 更好搭
  • 为什么 service mesh(Envoy / Istio)原生支持的协议第一个就是 gRPC

核心要点

gRPC-Go 的设计可以拆成 三件事

  1. HTTP/2 当地基:一个 TCP 连接上同时跑多个 stream,每个 RPC 调用 = 一个 stream。类比:一条高速公路开多条车道,不用给每个请求重新铺路(不用每次新 TCP 握手)。

  2. Protobuf 当合同.proto 文件描述方法名 + 参数 + 返回值,protoc 生成两侧代码。类比:两家公司用同一份格式合同,对方寄来的信里少一个字段、字段类型不对,立刻拒收。

  3. 四种调用模式:一元(请求 → 响应)/ 服务端流(一个请求 → N 个响应)/ 客户端流(N 个请求 → 一个响应)/ 双向流(两边都流)。类比:打电话只问一句、电台广播、连续上传、双方对讲机——四种通信形态都能直接表达。

中间还有一层 interceptor(拦截器),相当于给每个 RPC 加可装可卸的中间件:日志 / 鉴权 / 重试 / metrics 都从这里插。

实践案例

案例 1:最小一元调用

server.go
type server struct{ pb.UnimplementedGreeterServer }
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "hi " + in.Name}, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
}

逐部分解释

  • grpc.NewServer() 起一个 gRPC 服务器,底层就是个 HTTP/2 server
  • RegisterGreeterServer 把服务实现注册到路由表,方法名 /Greeter/SayHello 自动绑定
  • 客户端用 grpc.NewClient("localhost:50051") 拿 ClientConn,再 pb.NewGreeterClient(conn).SayHello(ctx, req),看起来像调本地函数

案例 2:服务端流推送行情

rpc SubscribePrice (Symbol) returns (stream Price);
func (s *server) SubscribePrice(req *pb.Symbol, stream pb.Quote_SubscribePriceServer) error {
ticker := time.NewTicker(time.Second)
for range ticker.C {
if err := stream.Send(&pb.Price{Value: rand.Float64()}); err != nil { return err }
}
return nil
}

服务端在一个 RPC 内多次 stream.Send 推数据,客户端循环 stream.Recv() 收。一次连接可以推上千条,没有”轮询 + 短连接”的开销——比拿 WebSocket 自己手搭轻多了。

案例 3:拦截器加全局日志

func loggingInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("%s took %v err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
s := grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))

类比:interceptor 是”过路收费站”——所有 RPC 进出都经过它。日志、鉴权、重试、链路追踪全靠这层往上摞,不用污染业务代码。

踩过的坑

  1. grpc.WithInsecure() 上线没改——开发期方便不带 TLS,正式环境忘了改成 credentials.NewTLS(...),所有 RPC 明文走公网,账号密码都裸奔。代码 review 必须扫这一行。

  2. 客户端 keepalive 太激进——client 设 Time: 5s,server 端 MinTime: 10s,server 觉得”你心跳太频繁是攻击”,直接 GOAWAY 踢掉。规则:client 的 keepalive Time 必须 ≥ server 允许的 MinTime。

  3. context 没传 deadline——上游接口一卡,下游 RPC 一直等,goroutine 越堆越多,最后 OOM。每个调用前 ctx, cancel := context.WithTimeout(parent, 2*time.Second),并把 ctx 一路传给所有下游 stub。

  4. 该用 stream 的地方用一元循环——本来 server-stream 一次连接推 1000 条报价,被写成 1000 次独立 RPC 调用,HTTP/2 多路复用的好处全浪费。看到”批量推 / 实时订阅 / 大文件分片”,优先选 stream。

适用 vs 不适用场景

适用

  • 内部微服务之间高频低延迟通信(Kubernetes / etcd / TiDB 都这么用)
  • 跨语言调用(Go 服务给 Python / Java 客户端用,一份 .proto 各生成一份)
  • 流式数据:行情推送 / 实时通知 / 大文件分片上传 / 双向对讲
  • service mesh 场景(Envoy / Istio 对 gRPC 有原生支持)

不适用

  • 浏览器直连 → 浏览器没暴露 HTTP/2 trailer,需要 grpc-web 或 Connect-RPC 桥接
  • 公开 OpenAPI 给第三方 → REST/JSON 文档生态成熟、调试工具(curl / Postman)友好
  • 简单的 CRUD 单体应用 → schema 维护成本高于收益,不如 REST + JSON Schema
  • 极弱网移动端首选 → HTTP/2 在 3G 信号差时表现一般,移动端要看场景权衡

历史小故事(可跳过)

  • 2001 年:Google 内部诞生 RPC 框架 Stubby,跑了十几年内部上千个服务
  • 2015 年:Google 把 Stubby 通用化、剥掉内部依赖,开源成 gRPC,捐给 CNCF
  • 2016 年:grpc-go 第一个稳定版发布,Go 1.6 起官方使用
  • 2018-2020 年:Kubernetes / etcd / TiDB 全栈采用,gRPC 成为 Go 后端事实标准
  • 2023 年起:HTTP/3 实验分支推进,但绝大多数生产仍用 HTTP/2

学到什么

  1. schema 优先 + 二进制传输是高频内部通信的天然选择——比”约定俗成的 REST 字段”省错率好几个量级
  2. HTTP/2 多路复用是 gRPC 性能的物理基础——一个连接打一切,省握手省带宽
  3. 四种调用模式统一了”一问一答 / 推送 / 上传 / 对讲”的通信形态——再不用 WebSocket 自己搭
  4. interceptor 模型让横切关注点(日志 / 鉴权 / 重试)可装可卸,业务代码保持纯净

延伸阅读

  • 官方教程:gRPC Go Quick Start(半小时跑通 hello world)
  • 视频:gRPC vs REST(讲清楚为什么 RPC 不等于 REST)
  • 进阶书:《gRPC: Up and Running》(O’Reilly,覆盖四种模式 + 部署)
  • http-2 —— gRPC 的传输层地基
  • envoy —— service mesh 数据面,原生理解 gRPC 协议
  • trpc —— Tencent 同类 RPC 框架,对照看设计差异

关联

  • http-2 —— gRPC 全部 RPC 都跑在 HTTP/2 上,多路复用是性能命脉
  • envoy —— service mesh 对 gRPC 协议有原生路由 / 重试 / 限流支持
  • kratos —— B 站 Go 微服务框架,把 grpc-go + 治理套件打包成开箱即用
  • go-zero —— 国内 Go 微服务框架,goctl 工具生成 grpc-go 代码
  • trpc —— Tencent 自研 RPC,思想接近但走自定义协议
  • etcd —— 强一致 KV 存储,节点间通信全用 grpc-go
  • kafka —— 消息队列,常和 gRPC 互补:RPC 同步调用 + Kafka 异步事件

反向链接

  • apollo-server —— Apollo Server — Node 端 GraphQL 服务端的事实标准
  • capnproto —— Capn Proto — 数据布局即 wire format 的零拷贝序列化 + RPC
  • centrifugo —— Centrifugo — Go 写的开源实时消息服务器
  • connect-rpc —— ConnectRPC — 让 gRPC 在浏览器里裸跑的 RPC 协议
  • dendrite —— Dendrite — Go 写的第二代 Matrix homeserver,组件可拆可合
  • envoy —— Envoy — 把网络通信从业务代码里抠出来的代理进程
  • etcd —— etcd — 分布式键值数据库
  • go-zero —— go-zero — 一份契约文件生成整套 Go 微服务
  • gqlgen —— gqlgen — Go 用 schema 先写好再让编译器生成 GraphQL server
  • graphql-yoga —— GraphQL Yoga — 跨运行时的轻量 GraphQL 服务器
  • http-2 —— HTTP/2 — 把 HTTP 从文本协议改造成二进制多路复用
  • kratos —— kratos — Go 微服务一锅出 HTTP 和 gRPC 两份服务
  • thrift —— Thrift — 写一份 IDL 自动生成 28 种语言的 RPC 代码
  • trpc —— tRPC — TS 端到端类型安全 RPC
  • twirp —— Twirp — 用 protobuf 定义服务,但只走 HTTP/1.1 + JSON