This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/MS/06-gRPC/07-最佳实践.md
T

7.2 KiB
Raw Blame History

tags, create time
tags create time
grpc
production-readiness
retries
tls
performance-tuning
2026-05-07 16:00

生产环境最佳实践

概述

本章汇总 gRPC 在生产部署时需要关注的各项配置与策略:重试机制、TLS/mTLS、代码生成工具链、性能调优。这些知识点往往是"知道能解决问题,不知道就是故障"的存在。

[!question] 最后的思考题

如果你的 gRPC 服务 QPS 达到 10 万级别,但仍然发现延迟偏高,你觉得最可能的瓶颈在哪里?是序列化、网络、还是连接管理?带着这个问题去实际压测一遍,答案会比看十篇文章深刻。

重试策略

生产环境不建议无条件重试,但要针对可恢复错误配置自动重试:

// 客户端配置自动重试
retryPolicy := `{
    "retryPolicy": {
        "maxAttempts": 3,
        "initialBackoff": "0.1s",
        "maxBackoff": "1s",
        "backoffMultiplier": 2,
        "retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
    }
}`

conn, err := grpc.Dial(target,
    grpc.WithDefaultServiceConfig(retryPolicy),
)
flowchart LR
    attempt1["第1次调用<br/>UNAVAILABLE"] -->|指数退避 100ms| attempt2["第2次调用<br/>UNAVAILABLE"] -->|指数退避 200ms| attempt3["第3次调用<br/>SUCCESS"]

    attempt1 -.->|INTERNAL → 不重试| final["终止"]

    style attempt1 fill:#fff3e0
    style attempt2 fill:#ffe0b2
    style attempt3 fill:#c8e6c9
    style final fill:#ffcdd2

重试的安全边界

[!warning] 重试的三条红线

  1. 仅幂等操作(GET、DELETE)可以安全重试
  2. POST/create 操作重试可能产生重复数据,必须在业务层加幂等键(如 idempotency-key header)
  3. 重试会增加读放大,特别是涉及 DB 的场景
操作类型 可重试? 注意事项
Query / Get ✅ 安全 本身幂等,可放心重试
Update / Patch ⚠️ 有条件 需要在 DB 层加乐观锁或唯一索引
Create / Insert ❌ 谨慎 必须有幂等键机制,否则可能重复插入
Delete ✅ 安全 幂等操作

TLS / mTLS 配置

tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{cert},
    ClientAuth:   tls.RequireAndVerifyClientCert,
    ClientCertPool: certPool,
    MinVersion:   tls.VersionTLS12,
}

对于大规模微服务,推荐使用 mTLS(双向证书认证)作为服务间信任的基础:

// 服务端:需要客户端证书
conn, _ := grpc.Dial(target,
    grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
)

// 客户端也需要携带自己的证书
creds := credentials.NewTLS(tlsConfig)

[!tip] Istio 集成

在服务网格中,mTLS 由 Sidecar Proxy(Envoy)自动处理,业务代码无需关心 TLS 细节。gRPC 连接经过 Sidecar 时透明加密,业务层仍然使用明文连接(localhost)。

代码生成工具链

graph LR
    Proto["*.proto files"] --> Buf["buf generate"]
    Buf --> Go["go_proto_plugin<br/>生成 Go 代码"]
    Buf --> JS["js_proto_plugin<br/>生成 TS/JS 代码"]
    Buf --> Validate["validate.proto<br/>生成校验代码"]
    Buf --> GRPC["go_grpc_plugin<br/>生成 gRPC 桩"]

    style Proto fill:#e3f2fd
    style Buf fill:#fff3e0
    style Go fill:#c8e6c9
    style JS fill:#fce4ec
    style GRPC fill:#e8f5e9

buf.gen.yaml 示例

version: v2
plugins:
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt: paths=source_relative
  - remote: buf.build/grpc/go
    out: gen/go
    opt: paths=source_relative
  - remote: buf.build/bufbuild/validate-go
    out: gen/go
    opt: paths=source_relative

[!tip] buf.lock —— 锁定依赖版本

像 go.mod 锁定 Go 模块版本一样,buf.lock 锁定 proto 依赖的确切 commit,避免上游变更导致构建不一致。CI 中应加入 buf dep update --lock 的检查步骤。

CI/CD 集成

# GitHub Actions 示例
- name: Generate gRPC code
  uses: bufbuild/buf-action@v1
  with:
    command: generate
    input: proto/

- name: Check generated code is up to date
  run: buf mod update && git diff --exit-code

配置管理

完整的 Dial 配置参考:

conn, err := grpc.DialContext(ctx, target,
    // 基础选项
    grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
    grpc.WithInitialWindowSize(1<<20),                          // 窗口大小 1MB
    grpc.WithInitialConnWindowSize(1<<20),                      // 连接窗口 1MB
    grpc.MaxCallRecvMsgSize(10 << 20),                          // 最大收包 10MB
    grpc.MaxCallSendMsgSize(10 << 20),                          // 最大发包 10MB
    grpc.WithConnectParams(grpc.ConnectParams{                 // 连接参数
        MinConnectTimeout: 5 * time.Second,
        BackoffConfig: backoff.Config{
            BaseDelay:  100 * time.Millisecond,
            Multiplier: 1.6,
            MaxDelay:   3 * time.Second,
        },
    }),
)

Dial 选项速查

配置项 默认值 建议值 作用域
MaxCallRecvMsgSize 4MB 10MB 仅客户端
MaxCallSendMsgSize 4MB 10MB 仅服务端
InitialWindowSize 64KB 1MB(高吞吐) 连接级
BackoffBaseDelay 100ms 100ms 连接断开重连
BackoffMaxDelay 10s 3s 连接断开重连

性能调优要点

优化项 建议值 影响
单个消息大小上限 4MB~10MB 防止 OOM,超出则分块传
Initial Window Size 1MB 吞吐瓶颈时常需调大
Keepalive Time 10~30s 太短增加开销,太长被代理杀
Compressor gzip(按需启用) CPU vs 带宽权衡
Connection Pooling 让 gRPC 自动管理 不要手动开连接

gzip 压缩的取舍

// 客户端对特定大 Payload 调用启用压缩
resp, err := client.GetBigData(
    ctx,
    req,
    grpc.UseCompressor("gzip"),
)

[!summary] 何时启用 gzip

场景 是否建议 gzip 理由
小消息(< 1KB) 否 压缩开销大于节省的带宽
大消息(> 10KB)且 CPU 充裕 是 带宽通常是更大瓶颈
高 QPS 短命请求 否 CPU 反而成为瓶颈
跨数据中心调用 是 网络 RTT 高,减少数据传输量有意义

连接管理与 Keepalive 回顾

连接配置的最佳实践详见 06-连接管理。总结来说,生产环境至少要做到三件事:

  1. 开启 Keepalive,ping 间隔 10~30s,防止被代理切断
  2. 配置 MaxConnectionAge,让旧连接平滑退役,新版本自动接盘
  3. 设置合理 backoff,连接断开时指数退避重连,不要打满服务器

关联笔记