用 Kubernetes 给 gRPC 扩容:让每个 Pod 都忙起来

张开发
2026/4/6 13:37:43 15 分钟阅读

分享文章

用 Kubernetes 给 gRPC 扩容:让每个 Pod 都忙起来
先来段代码看看问题在哪// 服务端 - 一个简单的 gRPC 服务器packagemainimport(netgoogle.golang.org/grpcpbyour/proto/package)typeserverstruct{pb.UnimplementedYourServiceServer}func(s*server)YourMethod(ctx context.Context,req*pb.Request)(*pb.Response,error){// 处理请求...returnpb.Response{},nil}funcmain(){lis,_:net.Listen(tcp,:9090)s:grpc.NewServer()pb.RegisterYourServiceServer(s,server{})s.Serve(lis)// 开始服务}// 客户端 - 看起来没问题对吧conn,err:grpc.NewClient(server:9090,grpc.WithTransportCredentials(insecure.NewCredentials()),)deferconn.Close()问题来了这段代码在开发环境跑得好好的一上 Kubernetes 扩容就翻车 问题的根源长连接是个痴情种REST API 之所以扩容简单是因为它像个渣男——每次请求都是新的 TCP 连接来去自由Kubernetes 的负载均衡器可以随意分配。但 gRPC 不一样它是个痴情种。客户端一旦建立连接就会长期持有这个 TCP 连接。问题来了Kubernetes 的默认负载均衡器是按连接数分配而不是按请求数分配。尴尬场景Pod 在摸鱼 想象一下你有10个服务器 Pod但只有3个客户端。结果就是——7个 Pod 在idle摸鱼资源白白浪费。更糟的是自动扩容假设你设置了内存使用率90%触发扩容一个客户端把某个 Pod 用到90%系统新增一个 Pod… 但这个客户端根本不会用新 Pod因为它已经爱上了原来的连接。解决方案三步走 第一步Headless Service无头服务关键技巧不用Kubernetes 内置的负载均衡器。apiVersion:v1kind:Servicemetadata:name:serverspec:clusterIP:None# 关键设为 None 变成 headlessports:-name:grpcport:9090targetPort:9090selector:app.kubernetes.io/name:serverHeadless Service 没有固定 IP而是通过DNS 暴露所有 Pod 的 IP让客户端自己决定怎么负载均衡。第二步客户端负载均衡conn,err:grpc.NewClient(target,grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin}),)设置round_robin策略让请求轮询分发到各个 Pod。第三步DNS 解析器关键等等还没完客户端启动时获取一次 IP 列表后默认不会再更新。新扩容的 Pod 它根本不知道。funcinit(){resolver.Register(resolver.Get(dns))}funcmain(){conn,err:grpc.NewClient(fmt.Sprintf(dns:///%s,target),// 注意 dns:/// 前缀grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin}),)}注册 DNS resolver让客户端定期刷新可用 Pod 列表。总结问题解决方案gRPC 长连接导致负载不均客户端侧负载均衡Kubernetes 默认按连接分配使用 Headless Service新 Pod 无法被发现注册 DNS Resolver这样一来你的 gRPC 服务就能像 REST API 一样优雅扩容了。每个 Pod 都能忙起来资源不再浪费老板看了都开心

更多文章