Как gRPC обрабатывает балансировку нагрузки? Какие стратегии клиентской балансировки нагрузки существуют в gRPC-Go?
gRPC-Go поддерживает клиентскую балансировку через policy round_robin в ServiceConfig и DNS-resolver с headless Kubernetes Service. Для сложных сценариев используется xDS (Envoy/Istio).
Балансировка нагрузки в gRPC-Go
gRPC поддерживает балансировку нагрузки на двух уровнях: на стороне клиента (client-side) и через внешний proxy (L7). В большинстве случаев применяется клиентская балансировка, поскольку HTTP/2 мультиплексирует запросы по одному TCP-соединению, что делает L4 LB (например, AWS NLB) неэффективным.
Проблема HTTP/2 и L4 балансировщиков
Традиционный TCP load balancer видит одно долгоживущее соединение и направляет весь трафик на один backend. Решение — либо клиентская балансировка с discovery, либо L7 proxy (Envoy, nginx).
Встроенные балансировщики gRPC-Go
round_robin— распределяет RPC по кругу среди доступных SubConn.pick_first(default) — использует первый доступный адрес, не балансирует.grpclb— устаревший, замена — xDS.rls— Route Lookup Service (продвинутый, для крупных систем).
Настройка round_robin через ServiceConfig
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
_ "google.golang.org/grpc/balancer/roundrobin" // side-effect import
)
conn, err := grpc.NewClient(
"dns:///my-service.namespace.svc.cluster.local:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
DNS-based discovery с round_robin
При использовании dns:/// resolver gRPC-Go периодически опрашивает DNS и создаёт SubConn для каждого A/AAAA-записи. Это стандартный способ балансировки в Kubernetes через headless service:
# Kubernetes headless service — возвращает IP всех pod
apiVersion: v1
kind: Service
metadata:
name: my-grpc-service
spec:
clusterIP: None # headless!
selector:
app: my-grpc-app
ports:
- port: 50051
targetPort: 50051
// Подключение через headless service
conn, err := grpc.NewClient(
"dns:///my-grpc-service.default.svc.cluster.local:50051",
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
Кастомный Resolver и Balancer
// Регистрация кастомного resolver (например, для Consul/etcd)
type consulResolver struct {
cc resolver.ClientConn
}
func (r *consulResolver) ResolveNow(resolver.ResolveNowOptions) {
// Запрос к Consul, обновление адресов
addrs := fetchFromConsul()
r.cc.UpdateState(resolver.State{Addresses: addrs})
}
type consulResolverBuilder struct{}
func (b *consulResolverBuilder) Scheme() string { return "consul" }
func (b *consulResolverBuilder) Build(
target resolver.Target,
cc resolver.ClientConn,
opts resolver.BuildOptions,
) (resolver.Resolver, error) {
r := &consulResolver{cc: cc}
r.ResolveNow(resolver.ResolveNowOptions{})
return r, nil
}
func init() {
resolver.Register(&consulResolverBuilder{})
}
xDS и Envoy-based балансировка
Для сложных сценариев (canary, traffic splitting, locality-aware routing) используйте xDS с Envoy или Istio. gRPC-Go имеет встроенную поддержку xDS через пакет google.golang.org/grpc/xds:
import _ "google.golang.org/grpc/xds" // включает xDS балансировщик
conn, err := grpc.NewClient(
"xds:///my-service",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
Подводные камни
- Дефолтный
pick_firstне балансирует — его нужно явно менять наround_robinчерез ServiceConfig. - Обычный (не headless) Kubernetes Service возвращает единственный ClusterIP — DNS-балансировка не работает, все запросы идут на один виртуальный IP.
- DNS resolver кэширует TTL — изменения в pod-ах отражаются с задержкой; регулируйте через
dns.SetMinFrequency. - При использовании TLS имя в сертификате должно совпадать с именем в DNS-записи, иначе handshake упадёт для некоторых SubConn.
- Импорт
_ "google.golang.org/grpc/balancer/roundrobin"обязателен — без него политикаround_robinне зарегистрирована и вернётся ошибка. - grpclb устарел — не используйте его в новых системах, используйте xDS.
- Клиентская балансировка плохо работает при неравномерных нагрузках длинных стримов — для streaming-heavy сервисов рассмотрите L7 proxy.
Common mistakes
- Давать ответ про клиентская балансировка нагрузки в gRPC-Go только на уровне определения, не показывая поведение в реальном приложении.
- Игнорировать границы ответственности вокруг темы «клиентская балансировка нагрузки в gRPC-Go»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
- Не связывать клиентская балансировка нагрузки в gRPC-Go с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.
What the interviewer is testing
- Точно объясняет, что именно делает клиентская балансировка нагрузки в gRPC-Go и где это используется в Go-коде.
- Связывает клиентская балансировка нагрузки в gRPC-Go с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
- Не изобретает API и опирается на реальные контракты официальной документации.