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 的设计可以拆成 三件事:
-
HTTP/2 当地基:一个 TCP 连接上同时跑多个 stream,每个 RPC 调用 = 一个 stream。类比:一条高速公路开多条车道,不用给每个请求重新铺路(不用每次新 TCP 握手)。
-
Protobuf 当合同:
.proto文件描述方法名 + 参数 + 返回值,protoc生成两侧代码。类比:两家公司用同一份格式合同,对方寄来的信里少一个字段、字段类型不对,立刻拒收。 -
四种调用模式:一元(请求 → 响应)/ 服务端流(一个请求 → N 个响应)/ 客户端流(N 个请求 → 一个响应)/ 双向流(两边都流)。类比:打电话只问一句、电台广播、连续上传、双方对讲机——四种通信形态都能直接表达。
中间还有一层 interceptor(拦截器),相当于给每个 RPC 加可装可卸的中间件:日志 / 鉴权 / 重试 / metrics 都从这里插。
实践案例
案例 1:最小一元调用
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 serverRegisterGreeterServer把服务实现注册到路由表,方法名/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 进出都经过它。日志、鉴权、重试、链路追踪全靠这层往上摞,不用污染业务代码。
踩过的坑
-
grpc.WithInsecure()上线没改——开发期方便不带 TLS,正式环境忘了改成credentials.NewTLS(...),所有 RPC 明文走公网,账号密码都裸奔。代码 review 必须扫这一行。 -
客户端 keepalive 太激进——client 设
Time: 5s,server 端MinTime: 10s,server 觉得”你心跳太频繁是攻击”,直接 GOAWAY 踢掉。规则:client 的 keepalive Time 必须 ≥ server 允许的 MinTime。 -
context 没传 deadline——上游接口一卡,下游 RPC 一直等,goroutine 越堆越多,最后 OOM。每个调用前
ctx, cancel := context.WithTimeout(parent, 2*time.Second),并把ctx一路传给所有下游 stub。 -
该用 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
学到什么
- schema 优先 + 二进制传输是高频内部通信的天然选择——比”约定俗成的 REST 字段”省错率好几个量级
- HTTP/2 多路复用是 gRPC 性能的物理基础——一个连接打一切,省握手省带宽
- 四种调用模式统一了”一问一答 / 推送 / 上传 / 对讲”的通信形态——再不用 WebSocket 自己搭
- 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