golang-grpc

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
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服务兼顾正确性与可操作性——恰当的状态码、超时时间、拦截器以及优雅关闭,与正常流程同样重要。
模式:
  • 构建模式——从零开始实现新的gRPC服务端或客户端。
  • 审查模式——审核现有gRPC代码的正确性、安全性和可操作性问题。

Go gRPC Best Practices

Go gRPC最佳实践

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 informations. Context7 can help as a discoverability platform.
将gRPC视为纯传输层——与业务逻辑分离。官方Go实现为
google.golang.org/grpc
本指南并非详尽无遗。如需更多信息,请参考库文档和代码示例。Context7可作为发现平台提供帮助。

Quick Reference

快速参考

ConcernPackage / Tool
Service definition
protoc
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
关注点包/工具
服务定义
protoc
buf
搭配
.proto
文件
代码生成
protoc-gen-go
,
protoc-gen-go-grpc
错误处理
google.golang.org/grpc/status
搭配
codes
丰富错误详情
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 File Organization

Proto文件组织

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
按领域组织并使用版本化目录(
proto/user/v1/
)。务必使用
Request
/
Response
包装消息——像
string
这样的裸类型后续无法添加字段。使用
buf generate
protoc
生成代码。
Proto与代码生成参考

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
go
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()
}
  • 实现健康检查服务(
    grpc_health_v1
    )——Kubernetes探针需要它来判断服务就绪状态
  • 使用拦截器处理横切关注点(日志、认证、恢复)——保持业务逻辑简洁
  • 使用
    GracefulStop()
    并搭配超时回退到
    Stop()
    ——在防止挂起的同时处理正在进行的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()
}

Interceptor Pattern

拦截器模式

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
}
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
}

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
    metadata.NewOutgoingContext
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)
  • 复用连接——gRPC在单个HTTP/2连接上多路复用RPC请求;每次请求新建连接会浪费TCP/TLS握手资源
  • 为每个调用设置超时时间(
    context.WithTimeout
    )——如果不设置,上游服务响应缓慢会导致goroutine无限挂起
  • 通过
    dns:///
    scheme搭配无头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)

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.
CodeWhen to Use
InvalidArgument
Malformed input (missing field, bad format)
NotFound
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
go
// ✗ 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)
For field-level validation errors, attach
errdetails.BadRequest
via
status.WithDetails
.
始终使用
status.Error
返回带有特定状态码的gRPC错误——原生
error
会被转为
codes.Unknown
,无法为客户端提供可操作的信息。客户端会根据状态码决定是重试、快速失败还是降级处理。
状态码使用场景
InvalidArgument
输入格式错误(缺少字段、格式不正确)
NotFound
实体不存在
AlreadyExists
创建失败,实体已存在
PermissionDenied
调用者缺少权限
Unauthenticated
缺少或无效的令牌
FailedPrecondition
系统未处于所需状态
ResourceExhausted
超出速率限制或配额
Unavailable
临时问题,可安全重试
Internal
意外bug
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

Streaming

流式处理

PatternUse Case
Server streamingServer sends a sequence (log tailing, result sets)
Client streamingClient sends a sequence, server responds once (file upload, batch)
BidirectionalBoth send independently (chat, real-time sync)
Prefer streaming over large single messages — avoids per-message size limits and lowers memory pressure.
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
}
模式使用场景
服务端流式服务端发送序列数据(日志追踪、结果集)
客户端流式客户端发送序列数据,服务端一次性响应(文件上传、批量处理)
双向流式双方独立发送数据(聊天、实时同步)
优先使用流式处理而非大尺寸单条消息——避免单消息大小限制并降低内存压力。
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
}

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
使用
bufconn
创建内存连接,可在不产生网络开销的情况下测试完整的gRPC栈(序列化、拦截器、元数据)。务必测试错误场景是否返回预期的gRPC状态码。
测试模式与示例

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
  • 生产环境中必须启用TLS——凭证通过元数据传输
  • 服务间认证使用mTLS或委托给服务网格(Istio、Linkerd)
  • 用户认证实现
    credentials.PerRPCCredentials
    并在认证拦截器中验证令牌
  • 生产环境中应禁用反射以防止API被发现

Performance

性能

SettingPurposeTypical Value
keepalive.ServerParameters.Time
Ping interval for idle connections30s
keepalive.ServerParameters.Timeout
Ping ack timeout10s
grpc.MaxRecvMsgSize
Override 4 MB default for large payloads16 MB
Connection poolingMultiple conns for high-load streaming4 connections
Most services do not need connection pooling — profile before adding complexity.
设置项用途典型值
keepalive.ServerParameters.Time
空闲连接的Ping间隔30s
keepalive.ServerParameters.Timeout
Ping响应超时10s
grpc.MaxRecvMsgSize
覆盖默认4MB限制以支持大负载16 MB
连接池高负载流式处理的多连接4个连接
大多数服务不需要连接池——在增加复杂度前先做性能分析。

Common Mistakes

常见错误

MistakeFix
Returning raw
error
Becomes
codes.Unknown
— client can't decide whether to retry. Use
status.Errorf
with a specific code
No deadline on client callsSlow upstream hangs indefinitely. Always
context.WithTimeout
New connection per requestWastes TCP/TLS handshakes. Create once, reuse — HTTP/2 multiplexes RPCs
Reflection enabled in productionLets 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 argumentsCan't add fields to
string
. Wrapper messages allow backwards-compatible evolution
Missing health check serviceKubernetes can't determine readiness, kills pods during deployments
Ignoring context cancellationLong operations continue after caller gave up. Check
ctx.Err()
错误修复方案
返回原生
error
会被转为
codes.Unknown
——客户端无法决定是否重试。使用
status.Errorf
返回特定状态码
客户端调用未设置超时上游服务响应缓慢会导致无限挂起。务必使用
context.WithTimeout
每次请求新建连接浪费TCP/TLS握手资源。创建一次连接并复用——HTTP/2支持多路复用RPC请求
生产环境启用反射允许攻击者枚举所有方法。仅在开发/预发布环境启用
所有错误都返回
codes.Internal
错误的状态码会破坏客户端重试逻辑。
Unavailable
触发重试;
InvalidArgument
则不会
使用裸类型作为RPC参数
string
类型无法添加字段。包装消息允许向后兼容的演进
缺少健康检查服务Kubernetes无法判断服务就绪状态,部署期间会杀死Pod
忽略上下文取消调用者放弃后长时间操作仍在继续。检查
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
  • → 参考
    samber/cc-skills-golang@golang-context
    技能了解超时和取消模式
  • → 参考
    samber/cc-skills-golang@golang-error-handling
    技能了解gRPC错误与Go错误的映射
  • → 参考
    samber/cc-skills-golang@golang-observability
    技能了解gRPC拦截器(日志、追踪、指标)
  • → 参考
    samber/cc-skills-golang@golang-testing
    技能了解使用bufconn进行gRPC测试