健康检查
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_total | Counter | 处理的 RPC 总数,按方法和状态码分组 |
grpc_server_handling_seconds | Histogram | RPC 处理延迟分布(p50/p95/p99) |
grpc_server_started_total | Counter | 已开始的 RPC 数(含进行中) |
grpc_server_msg_received_total | Counter | 收到的消息数(流式 RPC 有意义) |
grpc_server_msg_sent_total | Counter | 发送的消息数 |
# 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 文件后,需要遵守严格的兼容性规则,确保新旧客户端/服务端可以混合部署。
- 可以做的 添加新的 message、新的字段(使用新编号)、添加新的 enum 值、添加新的 RPC 方法、更改字段名(编号不变,名称只影响 JSON 格式)。
- 不可以做的 修改字段编号、修改字段类型(某些类型转换除外)、删除字段(要 reserved 保护编号)、修改 RPC 的输入/输出类型、将 optional 改为 required。
-
版本策略
当需要做破坏性变更时,创建新版本 package(如 v2)。v1 和 v2 同时存在,客户端按需迁移。URL 变为
/product.v2.ProductService/GetProduct。
// 安全演进示例
// 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 文件作为契约出发,让工具链自动生成代码,让拦截器处理横切关注点,让业务代码保持简洁。