Go gRPC

gRPC 是基于 HTTP/2 和 Protocol Buffers 的高性能 RPC 框架,Go 是其一等公民语言,适用于微服务间通信。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

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 服务。
创建于 2026/6/25 更新于 2026/6/25