Chapter 10

生产化与服务治理

从调试工具到 Prometheus 监控,将 gRPC 服务打磨为生产可观测、高可用的微服务

健康检查

gRPC 定义了标准的健康检查协议(grpc_health_v1),Kubernetes 的 liveness/readiness probe 和负载均衡器都依赖它。

# 安装健康检查库
go get google.golang.org/grpc/health/grpc_health_v1
go get google.golang.org/grpc/health
import (
  "google.golang.org/grpc/health"
  "google.golang.org/grpc/health/grpc_health_v1"
)

func main() {
  s := grpc.NewServer()

  // 注册业务服务
  pb.RegisterProductServiceServer(s, NewProductServer())

  // 注册健康检查服务
  healthSrv := health.NewServer()
  grpc_health_v1.RegisterHealthServer(s, healthSrv)

  // 设置服务状态(随业务逻辑动态更新)
  healthSrv.SetServingStatus(
    "product.v1.ProductService",
    grpc_health_v1.HealthCheckResponse_SERVING,
  )

  // 数据库断开时标记为不可用
  go monitorDB(func(connected bool) {
    status := grpc_health_v1.HealthCheckResponse_NOT_SERVING
    if connected {
      status = grpc_health_v1.HealthCheckResponse_SERVING
    }
    healthSrv.SetServingStatus("product.v1.ProductService", status)
  })

  s.Serve(lis)
}

反射 API 与 grpcurl 调试

gRPC 服务器反射(Server Reflection)允许客户端在运行时查询服务定义,不需要提前知道 .proto 文件。grpcurl 是基于反射的命令行调试工具,相当于 gRPC 的 curl。

启用反射(仅开发/调试环境)

import "google.golang.org/grpc/reflection"

func main() {
  s := grpc.NewServer()
  pb.RegisterProductServiceServer(s, NewProductServer())

  // 注册反射服务(生产环境可根据环境变量决定是否启用)
  if os.Getenv("GRPC_REFLECTION") == "true" {
    reflection.Register(s)
  }
}

grpcurl 常用命令

# 安装 grpcurl
brew install grpcurl

# 列出所有服务
grpcurl -plaintext localhost:50051 list

# 列出服务的所有方法
grpcurl -plaintext localhost:50051 list product.v1.ProductService

# 描述方法签名
grpcurl -plaintext localhost:50051 describe product.v1.ProductService.GetProduct

# 调用 Unary RPC(JSON 格式请求体)
grpcurl -plaintext \
  -d '{"id": "prod-123"}' \
  localhost:50051 \
  product.v1.ProductService/GetProduct

# 携带 Metadata(Bearer Token)
grpcurl -plaintext \
  -H "authorization: Bearer eyJhbGci..." \
  -d '{"id": "prod-123"}' \
  localhost:50051 \
  product.v1.ProductService/GetProduct

# 调用服务端流 RPC
grpcurl -plaintext \
  -d '{"service_name": "order-service", "level": "error"}' \
  localhost:50051 \
  log.v1.LogService/WatchLogs

服务发现与负载均衡

与 Kubernetes 集成

// K8s 环境:使用 headless service + DNS 客户端负载均衡
// K8s headless service (ClusterIP: None) 会返回所有 Pod IP
conn, err := grpc.NewClient(
  // dns:/// 前缀触发 DNS 解析,返回多个 IP
  "dns:///product-service.production.svc.cluster.local:50051",
  grpc.WithTransportCredentials(insecure.NewCredentials()),
  // 客户端负载均衡策略:轮询
  grpc.WithDefaultServiceConfig(`{
    "loadBalancingPolicy": "round_robin"
  }`),
)
# Kubernetes headless service 配置
apiVersion: v1
kind: Service
metadata:
  name: product-service
spec:
  clusterIP: None   # headless:DNS 返回所有 Pod IP
  selector:
    app: product-service
  ports:
  - port: 50051
    targetPort: 50051

Prometheus 指标导出

# 安装 gRPC Prometheus 中间件
go get github.com/grpc-ecosystem/go-grpc-prometheus
import (
  grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
  "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
  // 注册 Prometheus 拦截器
  s := grpc.NewServer(
    grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
    grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
  )

  pb.RegisterProductServiceServer(s, NewProductServer())

  // 初始化指标(必须在注册服务后调用)
  grpc_prometheus.Register(s)

  // 暴露 /metrics 端点
  go func() {
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":9090", nil)
  }()

  lis, _ := net.Listen("tcp", ":50051")
  s.Serve(lis)
}

关键 Prometheus 指标

指标名称类型说明
grpc_server_handled_totalCounter处理的 RPC 总数,按方法和状态码分组
grpc_server_handling_secondsHistogramRPC 处理延迟分布(p50/p95/p99)
grpc_server_started_totalCounter已开始的 RPC 数(含进行中)
grpc_server_msg_received_totalCounter收到的消息数(流式 RPC 有意义)
grpc_server_msg_sent_totalCounter发送的消息数
# PromQL 查询示例

# P99 延迟(按方法)
histogram_quantile(0.99,
  rate(grpc_server_handling_seconds_bucket[5m])
) by (grpc_method)

# 错误率(非 OK 状态码)
rate(grpc_server_handled_total{grpc_code!="OK"}[5m])
  / rate(grpc_server_handled_total[5m])

# QPS
rate(grpc_server_handled_total[1m]) by (grpc_method)

Protobuf 向后兼容演进原则

发布 .proto 文件后,需要遵守严格的兼容性规则,确保新旧客户端/服务端可以混合部署。

// 安全演进示例

// v1(原始版本)
message User {
  string id    = 1;
  string name  = 2;
  string email = 3;
}

// v1(演进后)— 向后兼容
message User {
  string id      = 1;
  string name    = 2;
  string email   = 3;
  string phone   = 4;  // 新增字段,OK
  UserRole role  = 5;  // 新增字段,OK
  // string username = 2; 不可以:修改编号 2 的字段名(影响 JSON 反序列化)
  // int32 id = 1;        不可以:修改字段类型
}

grpc-gateway:与 REST 共存

grpc-gateway 读取 .proto 文件中的 HTTP 注解,自动生成将 REST/JSON 请求转换为 gRPC 请求的反向代理。

import "google/api/annotations.proto";

service ProductService {
  rpc GetProduct(GetProductRequest) returns (ProductResponse) {
    // HTTP 注解:定义 REST 映射
    option (google.api.http) = {
      get: "/v1/products/{id}"
    };
  }

  rpc CreateProduct(CreateProductRequest) returns (ProductResponse) {
    option (google.api.http) = {
      post: "/v1/products"
      body: "*"
    };
  }
}
// 同时运行 gRPC 服务端和 REST 网关
func main() {
  // 启动 gRPC 服务
  go runGRPCServer()

  // 启动 REST 网关
  ctx := context.Background()
  mux := runtime.NewServeMux()

  opts := []grpc.DialOption{grpc.WithInsecure()}
  pb.RegisterProductServiceHandlerFromEndpoint(
    ctx, mux, "localhost:50051", opts)

  // gRPC: :50051  REST: :8080
  http.ListenAndServe(":8080", mux)
}

生产化检查清单

必须 (P0)
  • TLS 加密传输
  • 健康检查(grpc_health_v1)
  • Context 超时与取消
  • Panic Recovery 拦截器
  • 结构化日志记录
  • Prometheus 指标导出
推荐 (P1)
  • OpenTelemetry 链路追踪
  • 客户端重试策略
  • 服务端反射(仅非生产)
  • 限流拦截器
  • 请求验证(buf validate)
  • Graceful shutdown

优雅关闭

func main() {
  s := grpc.NewServer()
  // ... 注册服务

  // 监听系统信号,优雅关闭
  quit := make(chan os.Signal, 1)
  signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)

  go func() {
    <-quit
    log.Println("Shutting down gRPC server...")
    // GracefulStop 等待进行中的 RPC 完成后再关闭
    s.GracefulStop()
  }()

  lis, _ := net.Listen("tcp", ":50051")
  if err := s.Serve(lis); err != nil {
    log.Fatalf("serve: %v", err)
  }
  log.Println("Server exited")
}

全篇总结:gRPC 通过 Protobuf + HTTP/2 为微服务提供了高性能、强类型、跨语言的通信框架。从最基本的 Unary RPC 到复杂的双向流,从拦截器到 TLS 认证,再到 Prometheus 可观测性——每个环节都有成熟的解决方案。关键是从 .proto 文件作为契约出发,让工具链自动生成代码,让拦截器处理横切关注点,让业务代码保持简洁。