Go gRPC
gRPC 是基于 HTTP/2 和 Protocol Buffers 的高性能 RPC 框架,Go 是其一等公民语言,适用于微服务间通信。
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go Web 后端
- 前置概念: Go io.Reader 与 io.Writer
- 并列概念: Go 路由模式
Go gRPC
一句话定义
gRPC 是 Google 开源的远程过程调用框架,使用 Protocol Buffers 定义接口契约,基于 HTTP/2 实现多路复用和双向流式通信,是微服务架构中服务间通信的主流选择。
核心机制 / 工作原理
Protocol Buffers 接口定义:
// user.proto
syntax = "proto3";
package user;
option go_package = "example/user";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (stream UserResponse); // 服务端流
rpc Chat(stream ChatMessage) returns (stream ChatMessage); // 双向流
}
message GetUserRequest {
int64 id = 1;
}
message GetUserResponse {
User user = 1;
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
}
代码生成:
# 安装 protoc 和 Go 插件
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc --go_out=. --go-grpc_out=. user.proto
生成两个文件:user.pb.go(消息类型)和 user_grpc.pb.go(gRPC 接口)。
服务端实现:
type server struct {
pb.UnimplementedUserServiceServer // 嵌入未实现的接口以满足编译
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user, err := db.FindUser(req.GetId())
if err != nil {
// gRPC 错误处理:使用 status 包
return nil, status.Errorf(codes.NotFound, "user %d not found", req.GetId())
}
return &pb.GetUserResponse{User: user}, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
s.Serve(lis)
}
客户端调用:
conn, _ := grpc.NewClient("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewUserServiceClient(conn)
resp, _ := client.GetUser(context.Background(), &pb.GetUserRequest{Id: 42})
fmt.Println(resp.GetUser().GetName())
四种 RPC 类型:
| 类型 | 请求 | 响应 | 场景 |
|---|---|---|---|
| Unary | 单条 | 单条 | 普通请求/响应 |
| Server streaming | 单条 | 流 | 实时推送、大数据分页 |
| Client streaming | 流 | 单条 | 批量上传、日志收集 |
| Bidirectional streaming | 流 | 流 | 聊天、实时协作 |
拦截器(Interceptor)——gRPC 的中间件:
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("method=%s duration=%v err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
s := grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))
最小例子 / 最小场景
// greeter.proto
syntax = "proto3";
package greeter;
option go_package = "example/greeter";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply);
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }
// server.go
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello, " + req.GetName()}, nil
}
这是 gRPC 的最简形式:一个 Unary RPC,客户端发送名字,服务端返回问候语。
为什么重要
- 强类型契约:Protobuf 定义是跨语言的接口规范,前后端、不同语言的服务共享同一份
.proto文件,消除了”文档和实现不一致”的问题。 - 性能:Protobuf 序列化比 JSON 小 3-10 倍、快 20-100 倍;HTTP/2 的多路复用减少了连接开销。
- 流式通信:原生支持双向流,适合实时数据推送、聊天、大文件传输等场景。
- 生态成熟:gRPC-Web 支持浏览器调用,gRPC-Gateway 可将 gRPC 服务转为 REST API,服务网格(Istio/Linkerd)原生支持 gRPC。
边界与易混淆点
- gRPC 默认不适合浏览器直接调用——浏览器不支持 HTTP/2 trailers。需要 gRPC-Web 或 gRPC-Gateway 作为桥接。
.proto文件变更时必须重新生成代码并向后兼容——删除字段时必须使用reserved保留字段号,否则会导致数据损坏。UnimplementedXxxServer的嵌入是必须的——它让服务端结构体满足接口,未来 proto 新增方法时不会破坏编译。- gRPC 的错误使用
status.Error(codes.Xxx, "message"),不是 Go 的error——客户端需要通过status.FromError()提取 code。 - 拦截器分为 Unary 和 Stream 两种,需要分别注册。
- gRPC 的健康检查、反射、负载均衡等需要额外配置,不是开箱即用的。
grpcurl工具利用反射来调试 gRPC 服务。