4、gRPC高级实践:拦截器、负载均衡、健康检查、TLS安全通信、与网关集成
好,咱们接着聊。前面几章我们把gRPC的基础通信模式讲透了,包括Unary、Server Streaming、Client Streaming、Bidirectional Streaming。但说实话,那些只是「能用」的层面。真正到了生产环境,你会发现光会调接口远远不够。这一章,我带你看看gRPC在实际项目中那些「不得不做」的高级实践。
我个人习惯把这一章叫做「gRPC的护城河工程」。为什么?因为拦截器、负载均衡、健康检查、TLS、网关集成,这些东西单独拎出来每一个都不难,但组合在一起,就是一套完整的微服务通信保障体系。你想想看,没有这些,你的服务就像没穿盔甲上战场——能跑,但随时可能倒下。
4.1 拦截器:给通信加点「中间件」
拦截器,说白了就是gRPC的中间件。它可以在请求发出前或响应返回后,插入一段自定义逻辑。我在项目中遇到过最典型的场景:统一打印请求日志、注入Trace ID、做权限校验、统计接口耗时。
gRPC的拦截器分两种:客户端拦截器和服务端拦截器。每种又分一元拦截器(Unary Interceptor)和流式拦截器(Stream Interceptor)。
先看一个最简单的服务端一元拦截器:
// 服务端一元拦截器
func LoggingInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("收到请求: %s", info.FullMethod)
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("请求完成: %s, 耗时: %v", info.FullMethod, time.Since(start))
return resp, err
}
// 注册拦截器
s := grpc.NewServer(grpc.UnaryInterceptor(LoggingInterceptor))
嗯,这里要注意:如果你有多个拦截器,gRPC原生只支持一个UnaryInterceptor。怎么办?用grpc_middleware库,或者自己写一个链式调用。我个人习惯用go-grpc-middleware,它提供了现成的链式支持。
客户端拦截器写法类似,只是注册方式不同:
conn, err := grpc.Dial(address,
grpc.WithUnaryInterceptor(ClientLoggingInterceptor))
4.2 负载均衡:别让一台机器累死
gRPC的负载均衡和HTTP不太一样。HTTP通常是客户端通过DNS轮询或反向代理(如Nginx)来做。但gRPC是长连接,如果还用Nginx做代理,所有请求都会打到同一台后端,负载均衡就失效了。
gRPC官方推荐的做法是「客户端负载均衡」。什么意思?客户端自己维护一个后端地址列表,然后根据策略选一个来建连接。
gRPC内置了几种负载均衡策略:
| 策略名称 | 说明 | 适用场景 |
|---|---|---|
| pick_first | 默认策略,只连第一个地址 | 开发调试 |
| round_robin | 轮询,依次连接 | 通用场景 |
| grpclb | 外部负载均衡器 | 大规模集群 |
使用轮询策略很简单:
conn, err := grpc.Dial(
"dns:///service-name:50051",
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`),
grpc.WithInsecure(),
)
我曾经踩过一个坑:客户端缓存了DNS解析结果,服务扩容后新节点一直没流量。后来发现是gRPC默认的DNS解析刷新时间太长。解决方案是手动设置grpc.WithBlock和grpc.WithTimeout,或者用Consul、etcd做服务发现。
4.3 健康检查:服务活着吗?
微服务架构里,服务随时可能挂掉。如果客户端还在往挂掉的服务发请求,那体验就很糟糕了。gRPC提供了标准的健康检查协议(Health Checking Protocol),定义在grpc.health.v1中。
服务端实现健康检查:
import "google.golang.org/grpc/health/grpc_health_v1"
type HealthImpl struct{}
func (h *HealthImpl) Check(ctx context.Context,
req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest,
stream grpc_health_v1.Health_WatchServer) error {
// 流式健康检查,可以实时推送状态变化
for {
stream.Send(&grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
})
time.Sleep(time.Second)
}
}
客户端可以主动调用健康检查接口,或者让gRPC内置的健康检查器自动处理。我个人建议用后者,省心:
import "google.golang.org/grpc/health"
// 客户端自动健康检查
conn, err := grpc.Dial(address,
grpc.WithHealthCheck(),
grpc.WithDefaultServiceConfig(`{"healthCheckConfig": {"serviceName": ""}}`),
)
嗯,这里有个细节:健康检查的serviceName可以留空,表示检查整个服务。如果你有多个子服务,可以分别注册不同的健康检查状态。
4.4 TLS安全通信:别裸奔
说实话,我见过不少团队在开发环境用grpc.WithInsecure(),然后直接搬到生产环境。这是非常危险的。gRPC默认不加密,所有数据都是明文传输。你想想看,如果有人在中间抓包,用户的密码、订单信息全暴露了。
配置TLS其实不复杂,分三步:生成证书、服务端加载、客户端加载。
服务端:
// 加载服务端证书和私钥
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil {
log.Fatalf("加载证书失败: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))
客户端:
// 加载CA证书,验证服务端身份
creds, err := credentials.NewClientTLSFromFile("ca.crt", "server.example.com")
if err != nil {
log.Fatalf("加载CA证书失败: %v", err)
}
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
我曾经在项目中遇到过证书过期导致服务中断的事故。那天凌晨三点,告警电话响了,所有服务间通信全部失败。排查了半天,发现是证书过期了。从那以后,我养成了一个习惯:证书有效期设置成10年,并且在监控里加上证书过期告警。
4.5 与网关集成:统一入口
微服务多了,客户端不可能直接调用每个服务。这时候就需要一个API网关,统一对外暴露HTTP接口,内部转成gRPC调用。
常见的方案有两种:
- gRPC-Gateway:Google官方方案,根据proto文件自动生成HTTP转gRPC的代码。
- Envoy:高性能代理,原生支持gRPC,可以做负载均衡、限流、熔断。
我个人更倾向于Envoy,因为它功能更全,而且和Istio天然集成。但如果你团队小、不想引入额外组件,gRPC-Gateway是个轻量级选择。
gRPC-Gateway的使用方式:
// 在proto文件中添加HTTP绑定
service UserService {
rpc GetUser (GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/users/{user_id}"
};
}
}
// 生成代码后,启动HTTP服务
mux := runtime.NewServeMux()
err := pb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
这样,客户端就可以通过GET /v1/users/123来调用gRPC服务了。
好了,这一章的内容就到这里。拦截器、负载均衡、健康检查、TLS、网关集成,这五个点单独看都不难,但组合起来就是一套完整的微服务通信基础设施。下一章,我们会聊聊事件驱动架构,那是另一个有意思的话题。