🏗️ Раздел 17 · Вопрос #8

В чём разница между client-side и server-side discovery

В микросервисной архитектуре Client-Side и Server-Side Discovery — это два фундаментальных способа обнаружения сетевых координат (IP:Port) и балансировки нагрузки между динамиче...


🟢 Junior Level

В микросервисной архитектуре Client-Side и Server-Side Discovery — это два фундаментальных способа обнаружения сетевых координат (IP:Port) и балансировки нагрузки между динамическими инстансами сервисов.

Client-Side Discovery (Клиент знает всё):
                  ┌────────────────────┐
                  │  Service Registry  │
                  └─────────▲──────────┘
             1. Query IP    │
             ┌──────────────┘
             ▼
      [Service A] ────────2. Direct Call (1 Hop)────────► [Service B (Pod 2)]
  (Client Balancer)

Server-Side Discovery (Клиент глуп, балансировщик умён):
                                    ┌────────────────────┐
                                    │  Service Registry  │
                                    └─────────▲──────────┘
                                              │ Query IP
                                              ▼
      [Service A] ──1. Call──► [Load Balancer / Proxy] ──2. Forward (2 Hops)──► [Service B]

Главное отличие:

  • Client-Side Discovery: Клиент (вызывающий сервис) сам обращается в реестр, получает список доступных IP-адресов, применяет алгоритм балансировки (Round Robin, Least Connections) и отправляет запрос напрямую в целевой инстанс.
  • Server-Side Discovery: Клиент ничего не знает о реестре и отправляет запрос на промежуточный балансировщик (Load Balancer / Reverse Proxy). Балансировщик сам опрашивает реестр и перенаправляет запрос на конкретный инстанс.

🟡 Middle Level

1. Архитектурное и техническое сравнение

Критерий Client-Side Discovery Server-Side Discovery
Количество сетевых переходов (Hops) 1 Hop: Клиент $\to$ Целевой инстанс (минимальная задержка) 2 Hops: Клиент $\to$ Балансировщик $\to$ Целевой инстанс
Сложность клиента Высокая: в каждый сервис нужно внедрять SDK (Spring Cloud LoadBalancer, gRPC channel) Нулевая: сервис делает обычный HTTP-запрос на статический DNS/IP
Полиглотность (Polyglot) Плохая: библиотеку обнаружения и балансировки нужно реализовать для Java, Go, Node.js, Python Идеальная: работает с любым языком и стеком технологий «из коробки»
Единая точка отказа (SPOF) Отсутствует: балансировка децентрализована по всем клиентам Балансировщик является критическим компонентом (требует HA-кластера)
Осведомленность о контексте Высокая: клиент может учитывать локальную зону доступности (AZ-Affinity) Балансировщик видит только входящий сетевой пакет

2. Реализация Client-Side в Java (Spring Cloud LoadBalancer)

В экосистеме Java исторический Netflix Ribbon был заменён на Spring Cloud LoadBalancer:

@Configuration
public class ClientConfig {

    @Bean
    @LoadBalanced // Включает клиентскую балансировку через реестр (Consul / Eureka)
    public RestClient.Builder loadBalancedRestClientBuilder() {
        return RestClient.builder();
    }
}

@Service
@RequiredArgsConstructor
public class OrderService {

    private final RestClient.Builder restClientBuilder;

    public void invokePayment(PaymentDto dto) {
        // Имя "payment-service" резолвится локально клиентом без обращения к внешнему LB
        restClientBuilder.build()
            .post()
            .uri("http://payment-service/api/v1/charge")
            .body(dto)
            .retrieve()
            .toBodilessEntity();
    }
}

3. Server-Side: Nginx / AWS ALB / Kubernetes Service

В Server-Side подходе клиент вызывает фиксированный адрес:

curl http://payment-internal.company.com/api/v1/charge

Маршрутизатор (Nginx Plus, AWS Application Load Balancer или K8s Ingress) динамически обновляет свой upstream из реестра и распределяет запросы.


🔴 Senior Level

1. Зональная маршрутизация (AZ-Affinity) и оптимизация расходов в облаке

В облаках (AWS, GCP, Azure) дата-центры разделены на зоны доступности (Availability Zones: az-a, az-b, az-c).

  • Передача трафика между подами внутри одной AZ бесплатна и имеет задержку $< 0.5$ мс.
  • Передача трафика между разными AZ (Cross-AZ Traffic) тарифицируется провайдером (например, $0.01 за гигабайт в AWS) и добавляет $1\text{–}2$ мс latency.

Преимущество Client-Side: Клиентский балансировщик знает, в какой зоне находится сам клиент. Он фильтрует список инстансов из реестра и отдает приоритет подам из той же зоны доступности (Zone-Preference / Zone-Affinity). При использовании централизованного Server-Side балансировщика запросы случайным образом пересекают границы зон, генерируя огромные счета за межзональный трафик в крупных высоконагруженных системах.

2. Service Mesh (Envoy Sidecar) — гибридный прорыв

Паттерн Service Mesh (Istio / Linkerd) объединил лучшие качества обоих миров, устранив их недостатки:

Архитектура Service Mesh:
[App Container (Java/Go/Node)] 
       │ (1. HTTP-вызов на localhost:15001 — выглядит как Server-Side для приложения)
       ▼
[Envoy Sidecar Proxy] ──(2. xDS Discovery + mTLS + Client-Side Routing: 1 Hop)──► [Target Envoy Sidecar]
                                                                                            │
                                                                                            ▼
                                                                                   [Target App Container]
  1. Для приложения это выглядит как Server-Side Discovery: код пишет простой запрос http://payment-service/api, никаких тяжелых библиотек Eureka/Spring Cloud подключать не нужно (полная полиглотность).
  2. На уровне сети это работает как Client-Side Discovery: локальный прокси Envoy вычитывает актуальный список подов по протоколу xDS, выполняет балансировку с учетом зон (AZ) и шлет трафик напрямую в целевой под (без центрального узла балансировки и лишних hops).

3. Kubernetes ClusterIP: Server-Side или Client-Side под капотом?

Архитектурный парадокс на собеседованиях:

  • Внешне (концептуально): Kubernetes Service (ClusterIP) позиционируется как Server-Side Discovery — клиент отправляет запрос на единый виртуальный IP-адрес сервиса.
  • Под капотом (реализация): На самом деле это децентрализованная клиентская балансировка! Компонент kube-proxy настраивает таблицы маршрутизации (iptables или IPVS) прямо на той рабочей ноде, где запущен клиент. Запрос транслируется в реальный IP пода-получателя прямо в сетевом стеке ядра Linux (DNAT) без прохождения через выделенный физический балансировщик.

4 Tricky Questions

1. Почему Client-Side балансировка критически важна для протокола HTTP/2 и gRPC?

Ответ: Протокол HTTP/2 мультиплексирует сотни логических запросов в одно постоянное TCP-соединение. Если перед gRPC-сервисом поставить классический L4-балансировщик (Server-Side TCP LB), клиент откроет ровно одно TCP-соединение к балансировщику, а тот пробросит его к одному поду. Все последующие миллионы gRPC-запросов пойдут в один единственный под, перегрузив его, пока остальные поды простаивают. Для gRPC необходим либо L7-балансировщик (разбирающий HTTP/2 фреймы с высокой нагрузкой на CPU), либо Client-Side балансировка (gRPC NameResolver + Subchannel balancing), где клиент сам открывает пул TCP-соединений ко всем подам и балансирует отдельные RPC-вызовы.

2. В чём опасность Client-Side Discovery при масштабном сбое сети (Thundering Herd на Registry)?

Ответ: Если в кластере из 5000 микросервисов происходит ребалансировка нод, сервисы начинают массово перезапускаться. При Client-Side подходе тысячи подов одновременно начинают бомбардировать Service Registry HTTP-запросами на получение обновленного списка адресов. Это приводит к отказу в обслуживании (DDoS) самого реестра. Защита:

  1. Использование Push-модели вместо Polling (WebSocket, HTTP/2 Server Push, gRPC streaming, как в Consul Watches или Envoy xDS).
  2. Экспоненциальный джиттер (Jitter) при повторных запросах.
  3. Локальное долговременное кэширование списка инстансов на клиенте.

3. Как Server-Side балансировщик может стать «бутылочным горлышком» (Bottleneck) по TLS-сертификатам и шифрованию?

Ответ: В Server-Side архитектуре центральный балансировщик выполняет TLS Termination: он расшифровывает входящий клиентский трафик и заново шифрует его перед отправкой в целевой под (mTLS). При пиковом трафике в сотни тысяч RPS центральный балансировщик исчерпывает ресурсы CPU на операциях асимметричной криптографии. В Client-Side подходе (или Service Mesh) криптографическая нагрузка mTLS равномерно распределена по всем клиентским машинам кластера.

4. Что произойдет, если в Client-Side подходе один из клиентов имеет забагованный алгоритм балансировки?

Ответ: При Client-Side подходе логика распределения трафика размазана по тысячам распределённых клиентов. Если одна команда выкатит релиз со сломанным алгоритмом (например, случайный выбор падает всегда на первый инстанс списка), эта группа клиентов «зальет» один под трафиком, вызвав локальную аварию. В Server-Side подходе алгоритм балансировки централизован и управляется командой инфраструктуры/SRE, что исключает влияние клиентских багов на распределение нагрузки.


🎯 Шпаргалка для интервью

Главное:

  • Client-Side: Клиент сам ходит в Registry и выбирает инстанс. Плюсы: 1 сетевой hop (минимальный latency), нет центрального SPOF, поддержка зональной маршрутизации (AZ-affinity). Минусы: привязка к клиентским библиотекам на каждом языке программирования.
  • Server-Side: Клиент шлет запрос на общий балансировщик (Load Balancer). Плюсы: полная полиглотность (клиенты просты), централизованное управление. Минусы: +1 hop по сети, риск образования bottleneck и единой точки отказа.
  • Service Mesh (Istio / Envoy): Современный стандарт, сочетающий плюсы обоих подходов (для приложения — Server-Side по простоте, по факту — Client-Side Sidecar с минимальным latency).
  • gRPC требует Client-Side балансировки из-за мультиплексирования HTTP/2 соединений.

Сравнительная матрица:

\(\begin{array}{|l|c|c|} \hline \textbf{Характеристика} & \textbf{Client-Side} & \textbf{Server-Side} \\ \hline \text{Сетевые Hops} & 1 & 2 \\ \text{Полиглотность} & \text{Низкая (требует SDK)} & \text{Идеальная} \\ \text{AZ-Affinity (экономия \$)} & \text{Отличная} & \text{Затруднена} \\ \text{gRPC балансировка} & \text{Нативная} & \text{Требует дорогой L7 LB} \\ \hline \end{array}\)

Красные флаги (Чего говорить НЕЛЬЗЯ):

  • ❌ «В микросервисах всегда лучше использовать внешний аппаратный F5/Nginx балансировщик для каждого вызова между сервисами» (Порождает колоссальный оверхед по latency и стоимости).
  • ❌ «В Kubernetes балансировка чисто Server-Side через отдельный сервер» (Виртуальный ClusterIP транслируется локально на ноде через iptables/IPVS компонентом kube-proxy).
  • ❌ «Netflix Ribbon — это современное решение» (Ribbon мертв много лет, в Spring Cloud используется Spring Cloud LoadBalancer).

Связанные темы: