mcpField
G
golang-grpc

golang-grpc

github.com/samber开发3.7k39.6k 次安装5 天前更新
代码仓库

关于 golang-grpc

面向 Go 工程师的 gRPC 技能,涵盖服务构建与代码审查,重点关注正确性、安全性和可运维性。

主要功能

  1. 01构建与审查模式
  2. 02Proto 组织与代码生成
  3. 03状态码错误处理
  4. 04截止时间与连接复用
  5. 05拦截器、健康检查与关闭
  6. 06流式传输、安全与测试指南

适用场景

  1. 01实现 Go gRPC 服务端或客户端
  2. 02审查 RPC 错误处理与安全性
  3. 03使用 bufconn 测试服务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-grpc

安装后在 Claude Code 中输入/golang-grpc确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
protoc-reference.md
testing.md

详细介绍

角色: 你是一名 Go 分布式系统工程师。你设计 gRPC 服务时,会兼顾正确性和可运维性——恰当的状态码、截止时间、拦截器和优雅关闭,与正常流程同样重要。

模式:

  • 构建模式 — 从头实现新的 gRPC 服务端或客户端。
  • 审查模式 — 审查现有 gRPC 代码中的正确性、安全性和可运维性问题。

依赖项:

  • protoc:brew install protobuf
  • protoc-gen-go:go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
  • protoc-gen-go-grpc:go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Go gRPC 最佳实践

将 gRPC 视为纯传输层——使其与业务逻辑分离。官方 Go 实现是 google.golang.org/grpc。

本技能并未涵盖所有内容——如需了解更多信息,请参阅库文档和代码示例:

  • 如需查询 Go 软件包的文档、符号、版本、导入方和已知漏洞,→ 查看 samber/cc-skills-golang@golang-pkg-go-dev 技能(godig)。查询 Go 软件包信息时,优先使用它而非 Context7。
  • 如需在自己的代码中导航此库的用法(定义、调用位置、诊断信息),→ 查看 samber/cc-skills-golang@golang-gopls 技能(gopls)。
  • 对于 pkg.go.dev 未收录的文档,Context7 仍可作为备用方案。

快速参考

关注点软件包 / 工具
服务定义使用 .proto 文件的 protoc 或 buf
代码生成protoc-gen-go、protoc-gen-go-grpc
错误处理配合 codes 使用 google.golang.org/grpc/status
丰富的错误详情google.golang.org/genproto/googleapis/rpc/errdetails
拦截器grpc.ChainUnaryInterceptor、grpc.ChainStreamInterceptor
中间件生态github.com/grpc-ecosystem/go-grpc-middleware
测试google.golang.org/grpc/test/bufconn
TLS / mTLSgoogle.golang.org/grpc/credentials
健康检查google.golang.org/grpc/health

Proto 文件组织

按领域组织,并使用带版本号的目录(proto/user/v1/)。始终使用 Request/Response 包装消息——裸类型(如 string)无法在之后添加字段。使用 buf generate 或 protoc 生成代码。

Proto 与代码生成参考

服务端实现

  • 实现健康检查服务(grpc_health_v1)——Kubernetes 探针需要它来判断服务是否就绪
  • 使用拦截器处理横切关注点(日志记录、身份验证、故障恢复)——让业务逻辑保持简洁
  • 使用 GracefulStop(),并在超时后回退到 Stop()——排空正在处理的 RPC,同时避免挂起
  • 在生产环境中禁用反射——它会暴露完整的 API 接口
srv := grpc.NewServer(
    grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, health.NewServer())

go srv.Serve(lis)

// On shutdown signal:
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
    srv.Stop()
}
拦截器模式
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    log.Printf("method=%s duration=%s code=%s", info.FullMethod, time.Since(start), status.Code(err))
    return resp, err
}

客户端实现

  • 复用连接——gRPC 会在单个 HTTP/2 连接上复用 RPC;每个请求创建一个连接会浪费 TCP/TLS 握手
  • 为每次调用设置截止时间(context.WithTimeout)——如果不设置,缓慢的上游会导致 goroutine 无限期挂起
  • 使用 dns:/// 方案,通过无头 Kubernetes 服务搭配 round_robin
  • 通过 metadata.NewOutgoingContext 传递元数据(身份验证令牌、跟踪 ID)
conn, err := grpc.NewClient("dns:///user-service:50051",
    grpc.WithTransportCredentials(creds),
    grpc.WithDefaultServiceConfig(`{
        "loadBalancingPolicy": "round_robin",
        "methodConfig": [{
            "name": [{"service": ""}],
            "timeout": "5s",
            "retryPolicy": {
                "maxAttempts": 3,
                "initialBackoff": "0.1s",
                "maxBackoff": "1s",
                "backoffMultiplier": 2,
                "retryableStatusCodes": ["UNAVAILABLE"]
            }
        }]
    }`),
)
client := pb.NewUserServiceClient(conn)

错误处理

始终使用 status.Error 返回带有特定代码的 gRPC 错误——原始 error 会变成 codes.Unknown,无法向客户端提供可操作的信息。客户端会根据代码决定重试、立即失败还是降级。

代码使用场景
InvalidArgument输入格式错误(缺少字段、格式不正确)
NotFound实体不存在
AlreadyExists创建失败,实体已存在
PermissionDenied调用方没有权限
Unauthenticated缺少令牌或令牌无效
FailedPrecondition系统未处于所需状态
ResourceExhausted超出速率限制或配额
Unavailable暂时性问题,可安全重试
Internal意外错误
DeadlineExceeded超时
// ✗ Bad — caller gets codes.Unknown, can't decide whether to retry
return nil, fmt.Errorf("user not found")

// ✓ Good — specific code lets clients act appropriately
if errors.Is(err, ErrNotFound) {
    return nil, status.Errorf(codes.NotFound, "user %q not found", req.UserId)
}
return nil, status.Errorf(codes.Internal, "lookup failed: %v", err)

对于字段级验证错误,使用 status.WithDetails 附加 errdetails.BadRequest。

流式传输

模式使用场景
服务端流式传输服务端发送一系列数据(日志跟踪、结果集)
客户端流式传输客户端发送一系列数据,服务端响应一次(文件上传、批处理)
双向流式传输双方独立发送数据(聊天、实时同步)

相比发送单条大消息,优先使用流式传输——这样可避免单条消息的大小限制并降低内存压力。

func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    for _, u := range users {
        if err := stream.Send(u); err != nil {
            return err
        }
    }
    return nil
}

测试

使用 bufconn 建立内存连接,无需网络开销即可测试完整的 gRPC 堆栈(序列化、拦截器、元数据)。务必测试错误场景是否返回预期的 gRPC 状态代码。

测试模式和示例

安全

  • 生产环境中必须启用 TLS——凭据会通过元数据传输
  • 对于服务间身份验证,请使用 mTLS 或委托给服务网格(Istio、Linkerd)
  • 对于用户身份验证,请实现 credentials.PerRPCCredentials,并在身份验证拦截器中验证令牌
  • 生产环境中应禁用反射,以防止 API 被发现

性能

设置用途典型值
keepalive.ServerParameters.Time空闲连接的 ping 间隔30s
keepalive.ServerParameters.Timeoutping 确认超时时间10s
grpc.MaxRecvMsgSize为大型负载覆盖 4 MB 默认值16 MB
连接池为高负载流式传输创建多个连接4 个连接

大多数服务不需要连接池——添加复杂性之前请先进行性能分析。

常见错误

错误修正方法
返回原始 error会变成 codes.Unknown——客户端无法决定是否重试。使用带有特定代码的 status.Errorf
客户端调用未设置截止时间上游响应缓慢时会无限期挂起。务必使用 context.WithTimeout
每个请求都新建连接浪费 TCP/TLS 握手开销。创建一次并重复使用——HTTP/2 可多路复用 RPC
生产环境中启用反射允许攻击者枚举所有方法。仅在开发/预发布环境中启用
所有错误都使用 codes.Internal错误代码会破坏客户端的重试逻辑。Unavailable 会触发重试;InvalidArgument 不会
将裸类型用作 RPC 参数无法向 string 添加字段。包装消息支持向后兼容的演进
缺少健康检查服务Kubernetes 无法确定就绪状态,并会在部署期间终止 Pod
忽略上下文取消调用方放弃后,长时间运行的操作仍会继续。检查 ctx.Err()

交叉引用

  • → 有关截止时间和取消模式,请参阅 samber/cc-skills-golang@golang-context 技能
  • → 有关 gRPC 错误到 Go 错误的映射,请参阅 samber/cc-skills-golang@golang-error-handling 技能
  • → 有关 gRPC 拦截器(日志记录、追踪、指标),请参阅 samber/cc-skills-golang@golang-observability 技能
  • → 有关使用 bufconn 进行 gRPC 测试,请参阅 samber/cc-skills-golang@golang-testing 技能

评论

支持 Markdown