mcpField
G
golang-grpc

golang-grpc

github.com/bobmatnyc开发78512 次安装15 周前更新
代码仓库

关于 golang-grpc

golang-grpc 是面向 Go 服务开发者的 gRPC 生产实践技能,介绍版本化 Protobuf API、流式调用、错误处理与运行安全。

主要功能

  1. 01版本化 Protobuf API 与代码生成
  2. 02截止时间和取消处理
  3. 03拦截器及状态码错误映射
  4. 04流式 RPC 模式
  5. 05健康检查、TLS 与优雅关闭

适用场景

  1. 01设计可演进的 gRPC API
  2. 02为 RPC 调用设置超时与错误语义
  3. 03实现服务端流式结果
  4. 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 错误。
  • 避免在发送前缓冲整个结果集。

资源

评论

支持 Markdown