先来段代码,看看问题在哪
// 服务端 - 一个简单的 gRPC 服务器
package main
import (
"net"
"google.golang.org/grpc"
pb "your/proto/package"
)
type server struct {
pb.UnimplementedYourServiceServer
}
func (s *server) YourMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
// 处理请求...
return &pb.Response{
}, nil
}
func main() {
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()),
)
defer conn.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: v1
kind: Service
metadata:
name: server
spec:
clusterIP: None # 关键!设为 None 变成 headless
ports:
- name: grpc
port: 9090
targetPort: 9090
selector:
app.kubernetes.io/name: server
Headless 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 它根本不知道。
func init() {
resolver.Register(resolver.Get("dns"))
}
func main() {
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 都能忙起来,资源不再浪费,老板看了都开心!🎉