gRPC-GoSeniorSystem design

Как 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 и опирается на реальные контракты официальной документации.

Sources

Related topics