关于 golang-grpc
golang-grpc 是面向 Go 服务开发者的 gRPC 生产实践技能,介绍版本化 Protobuf API、流式调用、错误处理与运行安全。
主要功能
- 01版本化 Protobuf API 与代码生成
- 02截止时间和取消处理
- 03拦截器及状态码错误映射
- 04流式 RPC 模式
- 05健康检查、TLS 与优雅关闭
适用场景
- 01设计可演进的 gRPC API
- 02为 RPC 调用设置超时与错误语义
- 03实现服务端流式结果
- 04用 bufconn 测试 gRPC 处理器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill golang-grpc安装后在 Claude Code 中输入/golang-grpc确认已加载。
附带文件metadata.json
详细介绍
Go gRPC(生产环境)
概述
gRPC 基于 Protocol Buffers 提供强类型 RPC API,原生支持流式传输,并且在服务间通信中性能出色。本技能侧重于生产环境的默认实践:版本化 proto、截止时间、错误代码、拦截器、健康检查、TLS 和可测试性。
快速开始
1) 定义版本化的 protobuf API
✅ 正确:使用版本化 package
// proto/users/v1/users.proto
syntax = "proto3";
package users.v1;
option go_package = "example.com/myapp/gen/users/v1;usersv1";
service UsersService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (stream User);
}
message GetUserRequest { string id = 1; }
message GetUserResponse { User user = 1; }
message ListUsersRequest { int32 page_size = 1; string page_token = 2; }
message User {
string id = 1;
string email = 2;
string display_name = 3;
}
❌ 错误:使用未版本化 package(难以演进)
package users;
2) 生成 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 -I proto \
--go_out=./gen --go_opt=paths=source_relative \
--go-grpc_out=./gen --go-grpc_opt=paths=source_relative \
proto/users/v1/users.proto
3) 使用截止时间和状态代码实现服务端
✅ 正确:验证并将错误映射为 gRPC 代码
package usersvc
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
usersv1 "example.com/myapp/gen/users/v1"
)
type Service struct {
usersv1.UnimplementedUsersServiceServer
Repo Repo
}
type Repo interface {
GetUser(ctx context.Context, id string) (User, error)
}
type User struct {
ID, Email, DisplayName string
}
func (s *Service) GetUser(ctx context.Context, req *usersv1.GetUserRequest) (*usersv1.GetUserResponse, error) {
if req.GetId() == "" {
return nil, status.Error(codes.InvalidArgument, "id is required")
}
u, err := s.Repo.GetUser(ctx, req.GetId())
if err != nil {
if err == ErrNotFound {
return nil, status.Error(codes.NotFound, "user not found")
}
return nil, status.Error(codes.Internal, "internal error")
}
return &usersv1.GetUserResponse{
User: &usersv1.User{
Id: u.ID,
Email: u.Email,
DisplayName: u.DisplayName,
},
}, nil
}
❌ 错误:返回原始错误(客户端会失去代码语义)
return nil, errors.New("user not found")
核心概念
截止时间和取消
为每次调用设置时限;为开销较大的处理程序强制实施服务端超时。
✅ 正确:要求设置截止时间
if _, ok := ctx.Deadline(); !ok {
return nil, status.Error(codes.InvalidArgument, "deadline required")
}
元数据
将元数据用于身份验证和会话关联,而不是用于主要请求数据。
✅ 正确:从元数据中读取身份验证令牌
md, _ := metadata.FromIncomingContext(ctx)
auth := ""
if vals := md.Get("authorization"); len(vals) > 0 {
auth = vals[0]
}
拦截器(中间件)
使用拦截器处理横切关注点:身份验证、日志记录、指标、跟踪和请求 ID。
✅ 正确:使用请求 ID 的一元拦截器
func unaryRequestID() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
id := uuid.NewString()
ctx = context.WithValue(ctx, requestIDKey{}, id)
resp, err := handler(ctx, req)
return resp, err
}
}
流式传输模式
服务端流式传输(分页或流式返回结果)
✅ 正确:在 ctx.Done() 时停止
func (s *Service) ListUsers(req *usersv1.ListUsersRequest, stream usersv1.UsersService_ListUsersServer) error {
users, err := s.Repo.ListUsers(stream.Context(), int(req.GetPageSize()))
if err != nil {
return status.Error(codes.Internal, "internal error")
}
for _, u := range users {
select {
case <-stream.Context().Done():
return stream.Context().Err()
default:
}
if err := stream.Send(&usersv1.User{
Id: u.ID,
Email: u.Email,
DisplayName: u.DisplayName,
}); err != nil {
return err
}
}
return nil
}
一元调用与流式传输的选择
- 单次请求/响应和简单重试使用一元调用。
- 大型结果集或持续更新使用服务端流式传输。
- 批量上传且仅需一个最终响应时使用客户端流式传输。
- 交互式协议使用双向流式传输。
生产环境加固
健康检查和反射
添加健康检查服务;仅在非生产环境中启用反射。
✅ 正确:健康检查 + 有条件地启用反射
hs := health.NewServer()
grpc_health_v1.RegisterHealthServer(s, hs)
if env != "production" {
reflection.Register(s)
}
优雅关闭
优先使用带截止时间的 GracefulStop。
✅ 正确:优雅停止
stopped := make(chan struct{})
go func() {
grpcServer.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
case <-time.After(10 * time.Second):
grpcServer.Stop()
}
TLS
在生产环境中使用 TLS(或 mTLS);在本地开发环境之外避免使用不安全的凭据。
✅ 正确:服务端 TLS
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil { return err }
grpcServer := grpc.NewServer(grpc.Creds(creds))
测试(bufconn)
使用 bufconn 测试 gRPC 处理程序,无需打开真实套接字。
✅ 正确:内存中的 gRPC 测试服务端
const bufSize = 1024 * 1024
lis := bufconn.Listen(bufSize)
srv := grpc.NewServer()
usersv1.RegisterUsersServiceServer(srv, &Service{Repo: repo})
go func() { _ = srv.Serve(lis) }()
ctx := context.Background()
conn, err := grpc.DialContext(
ctx,
"bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return lis.Dial() }),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil { t.Fatal(err) }
defer conn.Close()
client := usersv1.NewUsersServiceClient(conn)
resp, err := client.GetUser(ctx, &usersv1.GetUserRequest{Id: "1"})
_ = resp
_ = err
反模式
-
忽略截止时间:不受限制的处理程序会导致尾延迟和资源耗尽。
-
返回字符串错误:使用
status.Error或status.Errorf将领域错误映射为codes.*。 -
流式传输时不实施背压:在
ctx.Done()时停止,并处理Send错误。 -
在生产环境中暴露反射:将反射视为服务发现接口。
故障排查
症状:客户端收到 UNKNOWN 错误
处理方法:
- 返回
status.Error(codes.X, "..."),而不是原始错误。 - 将领域错误封装为类型化错误,再映射为 gRPC 代码。
症状:请求缓慢或挂起
处理方法:
- 要求设置截止时间,并将
ctx传递给下游调用。 - 在仓储层添加服务端超时和有界并发。
症状:流式传输不稳定
处理方法:
- 在
ctx.Done()时停止流式传输,并处理stream.Send错误。 - 避免在发送前缓冲整个结果集。
资源
- gRPC Go:https://github.com/grpc/grpc-go
- Protobuf Go:https://pkg.go.dev/google.golang.org/protobuf
- gRPC 错误代码:https://grpc.io/docs/guides/error/