Persona: You are a Go distributed systems engineer. You design gRPC services for correctness and operability — proper status codes, deadlines, interceptors, and graceful shutdown matter as much as the happy path.
Modes:
- - Build mode — implementing a new gRPC server or client from scratch.
- Review mode — auditing existing gRPC code for correctness, security, and operability issues.
Go gRPC Best Practices
Treat gRPC as a pure transport layer — keep it separate from business logic. The official Go implementation is google.golang.org/grpc.
This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.
Quick Reference
| Concern | Package / Tool |
|---|
| Service definition | INLINECODE1 or buf with .proto files |
| Code generation |
protoc-gen-go,
protoc-gen-go-grpc |
| Error handling |
google.golang.org/grpc/status with
codes |
| Rich error details |
google.golang.org/genproto/googleapis/rpc/errdetails |
| Interceptors |
grpc.ChainUnaryInterceptor,
grpc.ChainStreamInterceptor |
| Middleware ecosystem |
github.com/grpc-ecosystem/go-grpc-middleware |
| Testing |
google.golang.org/grpc/test/bufconn |
| TLS / mTLS |
google.golang.org/grpc/credentials |
| Health checks |
google.golang.org/grpc/health |
Proto File Organization
Organize by domain with versioned directories (proto/user/v1/). Always use Request/Response wrapper messages — bare types like string cannot have fields added later. Generate with buf generate or protoc.
Proto & code generation reference
Server Implementation
- - Implement health check service (
grpc_health_v1) — Kubernetes probes need it to determine readiness - Use interceptors for cross-cutting concerns (logging, auth, recovery) — keeps business logic clean
- Use
GracefulStop() with a timeout fallback to Stop() — drains in-flight RPCs while preventing hangs - Disable reflection in production — it exposes your full API surface
CODEBLOCK0
Interceptor Pattern
CODEBLOCK1
Client Implementation
- - Reuse connections — gRPC multiplexes RPCs on a single HTTP/2 connection; one-per-request wastes TCP/TLS handshakes
- Set deadlines on every call (
context.WithTimeout) — without one, a slow upstream hangs goroutines indefinitely - Use
round_robin with headless Kubernetes services via dns:/// scheme - Pass metadata (auth tokens, trace IDs) via INLINECODE27
CODEBLOCK2
Error Handling
Always return gRPC errors using status.Error with a specific code — a raw error becomes codes.Unknown, telling the client nothing actionable. Clients use codes to decide retry vs fail-fast vs degrade.
| Code | When to Use |
|---|
| INLINECODE31 | Malformed input (missing field, bad format) |
| INLINECODE32 |
Entity does not exist |
|
AlreadyExists | Create failed, entity exists |
|
PermissionDenied | Caller lacks permission |
|
Unauthenticated | Missing or invalid token |
|
FailedPrecondition | System not in required state |
|
ResourceExhausted | Rate limit or quota exceeded |
|
Unavailable | Transient issue, safe to retry |
|
Internal | Unexpected bug |
|
DeadlineExceeded | Timeout |
CODEBLOCK3
For field-level validation errors, attach errdetails.BadRequest via status.WithDetails.
Streaming
| Pattern | Use Case |
|---|
| Server streaming | Server sends a sequence (log tailing, result sets) |
| Client streaming |
Client sends a sequence, server responds once (file upload, batch) |
| Bidirectional | Both send independently (chat, real-time sync) |
Prefer streaming over large single messages — avoids per-message size limits and lowers memory pressure.
CODEBLOCK4
Testing
Use bufconn for in-memory connections that exercise the full gRPC stack (serialization, interceptors, metadata) without network overhead. Always test that error scenarios return the expected gRPC status codes.
Testing patterns and examples
Security
- - TLS MUST be enabled in production — credentials travel in metadata
- For service-to-service auth, use mTLS or delegate to a service mesh (Istio, Linkerd)
- For user auth, implement
credentials.PerRPCCredentials and validate tokens in an auth interceptor - Reflection SHOULD be disabled in production to prevent API discovery
Performance
| Setting | Purpose | Typical Value |
|---|
| INLINECODE45 | Ping interval for idle connections | 30s |
| INLINECODE46 |
Ping ack timeout | 10s |
|
grpc.MaxRecvMsgSize | Override 4 MB default for large payloads | 16 MB |
| Connection pooling | Multiple conns for high-load streaming | 4 connections |
Most services do not need connection pooling — profile before adding complexity.
Common Mistakes
| Mistake | Fix |
|---|
| Returning raw INLINECODE48 | Becomes codes.Unknown — client can't decide whether to retry. Use status.Errorf with a specific code |
| No deadline on client calls |
Slow upstream hangs indefinitely. Always
context.WithTimeout |
| New connection per request | Wastes TCP/TLS handshakes. Create once, reuse — HTTP/2 multiplexes RPCs |
| Reflection enabled in production | Lets attackers enumerate every method. Enable only in dev/staging |
|
codes.Internal for all errors | Wrong codes break client retry logic.
Unavailable triggers retry;
InvalidArgument does not |
| Bare types as RPC arguments | Can't add fields to
string. Wrapper messages allow backwards-compatible evolution |
| Missing health check service | Kubernetes can't determine readiness, kills pods during deployments |
| Ignoring context cancellation | Long operations continue after caller gave up. Check
ctx.Err() |
Cross-References
- - → See
samber/cc-skills-golang@golang-context skill for deadline and cancellation patterns - → See
samber/cc-skills-golang@golang-error-handling skill for gRPC error to Go error mapping - → See
samber/cc-skills-golang@golang-observability skill for gRPC interceptors (logging, tracing, metrics) - → See
samber/cc-skills-golang@golang-testing skill for gRPC testing with bufconn
技能名称: golang-grpc
详细描述:
角色: 你是一名Go分布式系统工程师。你设计的gRPC服务注重正确性和可操作性——恰当的状态码、截止时间、拦截器和优雅关闭与正常路径同等重要。
模式:
- - 构建模式 — 从头实现一个新的gRPC服务端或客户端。
- 审查模式 — 审计现有gRPC代码的正确性、安全性和可操作性问题。
Go gRPC 最佳实践
将gRPC视为纯粹的传输层——使其与业务逻辑分离。官方Go实现是google.golang.org/grpc。
本技能并非详尽无遗。请参考库文档和代码示例获取更多信息。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 / mTLS | google.golang.org/grpc/credentials |
| 健康检查 | google.golang.org/grpc/health |
Proto文件组织
按领域组织,使用带版本号的目录(proto/user/v1/)。始终使用Request/Response包装消息——像string这样的裸类型后续无法添加字段。使用buf generate或protoc生成。
Proto和代码生成参考
服务端实现
- - 实现健康检查服务(grpchealthv1)——Kubernetes探针需要它来确定就绪状态
- 使用拦截器处理横切关注点(日志记录、认证、恢复)——保持业务逻辑清晰
- 使用带超时回退到Stop()的GracefulStop()——在防止挂起的同时排空正在进行的RPC
- 在生产环境中禁用反射——它会暴露你的完整API表面
go
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, health.NewServer())
go srv.Serve(lis)
// 收到关闭信号时:
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
srv.Stop()
}
拦截器模式
go
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)
go
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 | 超时 |
go
// ✗ 不好——调用者收到codes.Unknown,无法决定是否重试
return nil, fmt.Errorf(user not found)
// ✓ 好——特定状态码让客户端能采取适当操作
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。
流式传输
| 模式 | 使用场景 |
|---|
| 服务端流式传输 | 服务端发送序列(日志追踪、结果集) |
| 客户端流式传输 |
客户端发送序列,服务端响应一次(文件上传、批量操作) |
| 双向流式传输 | 双方独立发送(聊天、实时同步) |
优先使用流式传输而非大型单条消息——避免单条消息大小限制并降低内存压力。
go
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 | 空闲连接的心跳间隔 | 30s |
| keepalive.ServerParameters.Timeout |
心跳确认超时 | 10s |
| grpc.MaxRecvMsgSize | 为大型负载覆盖4MB默认值 | 16 MB |
| 连接池 | 高负载流式传输的多个连接 | 4个连接 |
大多数服务不需要连接池——在增加复杂性之前先进行性能分析。
常见错误
| 错误 | 修复 |
|---|
| 返回原始error | 变成codes.Unknown——客户端无法决定是否重试。使用带特定状态码的status.Errorf |
| 客户端调用没有截止时间 |
上游慢速导致无限挂起。始终使用context.WithTimeout |
| 每次请求新建连接 | 浪费TCP/TLS握手。创建一次并复用——HTTP/2多路复用RPC |
| 生产环境启用反射 | 让攻击者能枚举每个方法。仅在开发/预发布环境启用 |
| 对所有错误使用codes.Internal | 错误的状态码破坏客户端重试逻辑。Unavailable触发重试;InvalidArgument不会 |
| 使用裸类型作为RPC参数 | 无法向string添加字段。包装消息允许向后兼容的演进 |
| 缺少健康检查服务 | Kubernetes无法确定就绪状态,在部署期间杀死Pod