资讯动态

gRPC服务治理:健康检查与负载均衡

发布时间:2026/8/17 21:54:31 来源:尧图企业网站定制
gRPC服务治理:健康检查与负载均衡摘要: 本篇讲解gRPC服务治理实战实现gRPC Health Check Protocol健康检查配置客户端负载均衡(round_robin和pick_first策略)使用服务端拦截器统一处理日志和错误分享pick_first策略导致请求集中到单实例的踩坑经验对比客户端负载均衡、服务端负载均衡和服务网格三种方案。开篇故事去年我们订单服务从2个实例扩到4个实例加完机器发现QPS没涨。看监控发现4个实例里只有1个有流量另外3个闲着。排查了半天发现gRPC客户端用的是默认的pick_first策略它连上第一个实例后就不再切换新加的实例根本收不到请求。改成round_robin策略后流量均匀分布到4个实例QPS翻了一倍。gRPC的服务治理比HTTP更隐蔽HTTP好歹有个nginx做负载均衡gRPC的负载均衡在客户端配置不对就白扩容。这篇把健康检查和负载均衡讲清楚。一、gRPC健康检查gRPC有标准的Health Check Protocol客户端通过调用grpc.health.v1.Health服务来检查服务端是否可用。packagemainimport(contextlognettimegoogle.golang.org/grpcgoogle.golang.org/grpc/healthhealthpbgoogle.golang.org/grpc/health/grpc_health_v1)// OrderService 订单服务实现typeOrderServicestruct{// 嵌入UnimplementedServer以实现前向兼容// pb.UnimplementedOrderServiceServer}funcmain(){lis,err:net.Listen(tcp,:50051)iferr!nil{log.Fatalf(监听失败: %v,err)}grpcServer:grpc.NewServer()// 注册业务服务// pb.RegisterOrderServiceServer(grpcServer, OrderService{})// 注册健康检查服务// health.NewServer返回标准健康检查服务healthServer:health.NewServer()// 设置服务状态为SERVING表示可接受请求// 第一个参数是服务名空字符串表示整体健康状态healthServer.SetServingStatus(order.OrderService,// 服务名和proto里定义的一致healthpb.HealthCheckResponse_SERVING,)// 注册健康检查服务到gRPC serverhealthpb.RegisterHealthServer(grpcServer,healthServer)log.Println(gRPC服务启动监听:50051)iferr:grpcServer.Serve(lis);err!nil{log.Fatalf(服务启动失败: %v,err)}}健康检查服务还可以结合实际状态动态调整。比如数据库连不上时把状态改成NOT_SERVING客户端探测到后自动摘除流量。// HealthChecker 动态健康检查器typeHealthCheckerstruct{healthServer*health.Server dbHealthybool}// SetDBStatus 数据库状态变化时调用func(hc*HealthChecker)SetDBStatus(healthybool){ifhealthy{// 数据库恢复服务可用hc.healthServer.SetServingStatus(order.OrderService,healthpb.HealthCheckResponse_SERVING,)}else{// 数据库异常服务不可用hc.healthServer.SetServingStatus(order.OrderService,healthpb.HealthCheckResponse_NOT_SERVING,)}hc.dbHealthyhealthy}// StartPeriodicCheck 定期检查依赖服务状态func(hc*HealthChecker)StartPeriodicCheck(){ticker:time.NewTicker(10*time.Second)gofunc(){forrangeticker.C{// 这里写实际的健康检查逻辑// 比如ping数据库、检查Redis连接等err:checkDatabase()hc.SetDBStatus(errnil)}}()}// checkDatabase 模拟数据库健康检查funccheckDatabase()error{// 实际实现: db.Ping()returnnil}二、客户端负载均衡gRPC的负载均衡在客户端实现。客户端通过DNS或xDS获取服务端地址列表然后按策略选择一个发请求。packagemainimport(contextlogtimegoogle.golang.org/grpcgoogle.golang.org/grpc/balancer/roundrobingoogle.golang.org/grpc/credentials/insecure_google.golang.org/grpc/health// 注册健康检查客户端)funcmain(){// 方式1: 使用round_robin策略// dns:///表示用DNS解析服务地址// 多个地址用逗号分隔conn,err:grpc.NewClient(dns:///order-service:50051,grpc.WithTransportCredentials(insecure.NewCredentials()),// 关键: 指定负载均衡策略为round_robingrpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin},),)iferr!nil{log.Fatalf(连接失败: %v,err)}deferconn.Close()// 创建客户端stub// client : pb.NewOrderServiceClient(conn)// 发送请求round_robin会轮询各个实例ctx,cancel:context.WithTimeout(context.Background(),5*time.Second)defercancel()// resp, err : client.CreateOrder(ctx, pb.CreateOrderRequest{...})// round_robin策略: 请求1-实例A, 请求2-实例B, 请求3-实例C..._ctx}round_robin和pick_first是gRPC内置的两种策略行为差别很大。// pick_first策略(默认)// 客户端连上第一个可达的实例后所有请求都发到这个实例// 只有当这个实例挂了才会切换到下一个// 问题: 流量集中在单个实例扩容无效// round_robin策略// 客户端轮询所有实例请求均匀分布// 请求1-实例A, 请求2-实例B, 请求3-实例C, 请求4-实例A...// 适合无状态服务扩容后流量自动重新分布// 配置round_robin的两种方式// 方式1: WithDefaultServiceConfig(推荐)conn,_grpc.NewClient(dns:///order-service:50051,grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin},),)// 方式2: 使用balancer注册名// 需要导入roundrobin包_roundrobin.Name// round_robinconn,_grpc.NewClient(dns:///order-service:50051,grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithDefaultServiceConfig({loadBalancingPolicy:roundrobin.Name},),)生产环境用round_robin配合健康检查。客户端定期探测实例健康状态不健康的实例自动从负载均衡池中摘除。三、服务端拦截器统一处理拦截器是gRPC的中间件机制。日志、认证、限流、错误处理这些横切逻辑都可以放在拦截器里业务代码保持干净。packagemainimport(contextlogtimegoogle.golang.org/grpcgoogle.golang.org/grpc/codesgoogle.golang.org/grpc/status)// LoggingInterceptor 日志拦截器// 记录每个请求的方法、耗时、错误信息funcLoggingInterceptor(ctx context.Context,reqinterface{},info*grpc.UnaryServerInfo,handler grpc.UnaryHandler,)(interface{},error){start:time.Now()// 执行业务handlerresp,err:handler(ctx,req)// 记录请求日志duration:time.Since(start)code:status.Code(err)iferr!nil{log.Printf(method%s duration%s code%s error%v,info.FullMethod,duration,code,err,)}else{log.Printf(method%s duration%s code%s,info.FullMethod,duration,code,)}returnresp,err}// RecoveryInterceptor panic恢复拦截器// 防止单个请求panic导致整个服务崩溃funcRecoveryInterceptor(ctx context.Context,reqinterface{},info*grpc.UnaryServerInfo,handler grpc.UnaryHandler,)(respinterface{},errerror){// defer recover捕获panicdeferfunc(){ifr:recover();r!nil{// 记录panic堆栈log.Printf(panic recovered: method%s error%v,info.FullMethod,r,)// 返回Internal错误码errstatus.Errorf(codes.Internal,服务内部错误,)}}()// 执行业务handlerreturnhandler(ctx,req)}// ChainInterceptors 链式组合多个拦截器// 执行顺序: Recovery - Logging - handlerfuncmain(){grpcServer:grpc.NewServer(// 链式拦截器按顺序执行grpc.ChainUnaryInterceptor(RecoveryInterceptor,// 最外层先执行LoggingInterceptor,// 第二层// 可以继续添加: 认证、限流、trace等),)_grpcServer}拦截器的执行顺序很重要。Recovery放最外层确保即使内层拦截器panic也能捕获。Logging放第二层记录经过认证过滤后的请求。限流放在认证之后避免对未认证请求浪费限流配额。四、独家踩坑:pick_first策略导致请求集中这个坑前面提过展开讲一下排查过程。现象: 订单服务从2个实例扩到4个监控显示只有实例A有流量B、C、D三个实例CPU利用率接近0。gRPC客户端配置没有显式指定负载均衡策略用的默认pick_first。排查过程:第一步看DNS解析。nslookup order-service返回4个IPDNS没问题。第二步看gRPC连接。在客户端日志里加了GRPC_GO_LOG_SEVERITY_LEVELinfo环境变量发现gRPC只建了一个连接连到实例A。第三步查gRPC文档。pick_first策略的行为是: 按顺序尝试地址列表里的每个地址连上第一个就用它后续所有请求都发到这个地址。只有这个连接断了才尝试下一个。解决方案: 加上round_robin策略配置。// 修复前: 默认pick_firstconn,_:grpc.NewClient(dns:///order-service:50051,grpc.WithTransportCredentials(insecure.NewCredentials()),// 没有指定LB策略默认pick_first// 所有请求集中到第一个连上的实例)// 修复后: 显式指定round_robinconn,_grpc.NewClient(dns:///order-service:50051,grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin},),// round_robin轮询所有实例流量均匀分布)改完后4个实例流量均匀分布每个实例约25%的请求。pick_first也不是没用它适合有状态服务或者需要连接复用的场景。但大部分无状态微服务应该用round_robin。五、对比分析负载均衡方案适用场景优缺点运维复杂度客户端LB(round_robin)无状态微服务简单高效无额外组件低客户端LB(pick_first)有状态服务连接复用但流量集中低服务端LB(Nginx/LVS)HTTP为主对gRPC支持有限多一跳中服务网格(Istio)大规模微服务功能全自动mTLS但复杂高客户端负载均衡最轻量gRPC原生支持适合中小规模。服务端负载均衡对gRPC支持不好HTTP/2的长连接让传统的LVS/Nginx负载均衡效果打折。服务网格功能最全自动重试、熔断、流量划分都能做但引入Istio的运维成本很高。总结与预告gRPC服务治理记住三件事。健康检查用标准Health Check Protocol让客户端自动摘除不健康实例。负载均衡显式配置round_robin别用默认pick_first。拦截器做横切逻辑业务代码保持干净。下一篇我们聊Docker化Go应用多阶段构建和镜像优化。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价