Post

Как работает Service Discovery в Spring Cloud и зачем он нужен

Как работает Service Discovery в Spring Cloud и зачем он нужен

Зачем вообще нужен Service Discovery?

Когда мы только начинаем строить микросервисную архитектуру и запускаем сервисы в одной сети Docker (например, через docker-compose), может показаться, что все просто: сервисы могут обращаться друг к другу по имени контейнера. Например:

1
2
3
# application.yml
profiles-service:
  base-url: http://profiles:8080

Но это работает только на раннем этапе. В реальных условиях, как только проект начинает развиваться, появляются сложности:

  • Микросервисы масштабируются — возникает несколько экземпляров одного сервиса;
  • Контейнеры могут перезапускаться — IP-адреса становятся непостоянными;
  • Нужно обеспечить доступность и отказоустойчивость;
  • Возникает необходимость в балансировке нагрузки между экземплярами;
  • Требуется обновляемый реестр сервисов, чтобы не искать их вручную.

И что важно - все это касается внутреннего взаимодействия между сервисами, а не внешнего клиентского трафика.

⚠️ Сложность возникает не только в том, чтобы найти нужный сервис, но и чтобы понять, сколько их сейчас в сети, где они, какие из них работают и как правильно распределить запросы между ними. Сервисы в контейнерах работают недолго. Они запускаются, останавливаются, масштабируются. Мир микросервисов требует динамики — а не статической конфигурации.

Почему классическая архитектура обнаружения не помогает

Когда речь заходит о взаимодействии сервисов, может показаться, что достаточно DNS и load balancing. На первый взгляд это кажется логичным: клиенты обращаются по адресу балансировщика, который сам решает, в какую реплику отправить запрос, обращаясь например в таблицу маршрутизации.

Что здесь не так:

  • Централизация — единая точка. Традиционный балансировщик — это одна точка, через которую проходит весь трафик. Если он перегружен или упал — взаимодействие рушится.
  • Балансировщик не выполняет функции обнаружения. Т.е. не может определить, когда сервис появился, когда исчез, сколько всего их стало. Обычно это решается через ручное добавление IP или интеграцию с health-check системой, что сложно и ненадежно.
  • DNS и IP-адреса не отражают актуальное состояние сервисов. Использование DNS-записей и IP-адресов позволяет обратиться к сервису по имени, однако такой подход имеет ряд ограничений. IP-адреса в контейнеризированной среде нестабильны — при перезапуске контейнера его адрес может измениться. DNS не отслеживает текущее состояние сервиса — он разрешает имя в IP, не проверяя, доступен ли по этому адресу экземпляр. Нет встроенной поддержки проверки доступности или отказов — маршрутизация не учитывает, работает ли сервис, перегружен ли он или уже завершил работу.

Эти ограничения особенно критичны в облачной и микросервисной архитектуре, где сервисы часто масштабируются, запускаются и завершаются динамически. В результате DNS-резолвинг без дополнительной логики может привести к попыткам обращения к неработающим или неактуальным экземплярам сервисов.

Именно для этого вводятся три ключевых концепта:

  • Service Discovery — автоматический поиск других сервисов по имени;
  • Service Registration — регистрация адресов при запуске сервисов;
  • Load Balancing — распределение нагрузки между работающими экземплярами.

Как Service Discovery решает эти проблемы в cloud-native архитектуре

Для облачных и микросервисных приложений использование Service Discovery — это не просто удобство, а необходимость, вытекающая из природы самой архитектуры.

В современных окружениях экземпляры сервисов могут запускаться и завершаться в любой момент, их IP-адреса назначаются динамически, количество реплик может меняться из-за автомасштабирования или отказов. Невозможно поддерживать стабильную сетевую схему вручную в таких условиях.

Service Registry как центральный источник правды

Чтобы обеспечить актуальную информацию о состоянии сервисов, используется централизованный реестр — например, Eureka:

  • Когда сервис запускается, он регистрируется в реестре.
  • Пока онсервис работает, он периодически отправляет heartbeat в реестр — специальный сигнал, подтверждающий, что сервис доступен и готов к обработке запросов. Если heartbeat перестает приходить — например, из-за сбоя или выключения сервиса, — реестр удаляет запись автоматически.
  • При завершении — удаляется из реестра автоматически.
  • В любой момент клиент может выполнить поиск нужного сервиса по имени и получить список его доступных экземпляров.

Это устраняет необходимость прописывать IP вручную, позволяет работать с любым числом реплик и повышает надежность межсервисных вызовов.

Интеграция с балансировкой нагрузки

Service Discovery может использоваться в связке с балансировщиками нагрузки (например, Spring Cloud LoadBalancer):

  • При обращении к сервису, клиент получает список доступных экземпляров от Discovery-сервера;
  • Дальше применяется стратегия (например, Round Robin) — на стороне клиента или через прокси;
  • Таким образом, достигается распределение нагрузки без участия внешнего балансировщика и без центральной точки отказа.

⚠️ Round Robin — это одна из самых простых стратегий балансировки нагрузки. Суть ее в том, что запросы к сервису равномерно распределяются по всем доступным экземплярам по очереди, без учета их загрузки. Например, если у нас 3 экземпляра profiles-service, то запросы будут идти примерно так: instance-1 → instance-2 → instance-3 → instance-1 → ... Такой подход особенно хорошо работает, если все экземпляры сервиса равны по производительности и нет потребности в адаптивной балансировке.

Два подхода к Service Discovery

Существуют два архитектурных стиля, которые мы можем использовать.

  • Client-side discovery. Клиент сам делает запрос в реестр, получает список адресов. Сам выбирает, куда отправить запрос.
  • Server-side discovery. Клиент отправляет запрос в балансировщик (например, API Gateway), который выполняет lookup в реестре и перенаправляет запрос. Пример - Kubernetes Service + Envoy.

Client-side Service discovery

Как работает

В паттерне client-side discovery клиент (то есть вызывающий микросервис) сам отвечает за поиск нужного сервиса в реестре, выбор конкретного экземпляра и балансировку нагрузки между доступными адресами. Такой подход активно используется в Spring Cloud с Eureka и OpenFeign.

Принцип работы (по шагам)

  • 1) Регистрация Сервис, например profiles-service, при запуске регистрируется в Service Registry и начинает отправлять heartbeat, подтверждая свою доступность.
  • 2) Запрос от клиента Когда некий courses-service хочет вызвать profiles-service, он запрашивает адреса у реестра по логическому имени сервиса.
  • 3) Получение списка адресов Реестр возвращает список IP-адресов всех доступных экземпляров profiles-service.
  • 4) Выбор экземпляра и вызов Клиент использует свою локальную стратегию балансировки (например, round-robin) и направляет запрос на конкретный адрес.

Регистрация сервиса происходит автоматически при старте, если он сконфигурирован как клиент Eureka. Поиск сервисов — это обычный REST-запрос к Discovery Server, выполняемый библиотеками Spring Cloud. Балансировка выполняется на клиентской стороне, что снижает нагрузку на центральный компонент и увеличивает отказоустойчивость.

Кэширование на клиенте

Еще одна важная деталь: чтобы не обращаться к Service Registry при каждом вызове, адреса сервисов кэшируются на стороне клиента. Кэш периодически обновляется из реестра.

Если в кэше уже есть актуальный IP — вызов происходит напрямую. Если нет — идет запрос в реестр. Это повышает производительность и снижает нагрузку на реестр.

Поддержка в Spring Cloud

Spring Cloud предоставляет полноценный набор компонентов, упрощающих реализацию Service Discovery на стороне клиента. Основные библиотеки:

  • Eureka (Spring Cloud Netflix Eureka) — централизованный реестр;
  • Spring Cloud LoadBalancer — обеспечивает клиентскую балансировку;
  • OpenFeign — декларативный HTTP-клиент, автоматически интегрируется с Discovery.

⚠️ Ранее использовался Netflix Ribbon, но он официально переведен в режим поддержки и больше не развивается. Вместо него рекомендован Spring Cloud LoadBalancer.

Как это работает вместе

  • 1) Сервис (profiles-service, courses-service) регистрируется в Eureka при старте;
  • 2) Другой сервис (например, content-service) использует Feign-клиент с логическим именем (@FeignClient(name = "profiles-service"));
  • 3) При вызове Feign сам запрашивает из Eureka список инстансов;
  • 4) Spring Cloud LoadBalancer выбирает один из них (например, через round-robin) и перенаправляет запрос.

Server-side Service discovery

До сих пор мы рассматривали Client-side discovery — подход, при котором клиент сам запрашивает адреса из реестра, сам выбирает экземпляр, сам выполняет балансировку.

Но это не единственный вариант.

В server-side подходе клиент не обращается напрямую к реестру, вместо этого он отправляет запрос на централизованный компонент (например, API Gateway или сервисный прокси), который:

  • Выполняет поиск нужного сервиса в реестре;
  • Выбирает экземпляр по своей логике;
  • Перенаправляет туда запрос.

Сравнение подходов

ХарактеристикаClient-side DiscoveryServer-side Discovery
Кто делает lookup?КлиентПрокси или gateway
Где балансировка?На клиентеНа стороне сервера
Зависимость от клиентаВысокая (каждый сервис долженНизкая
 уметь обращаться к реестру)(клиенты не знают о реестре)
ПримерEureka + FeignKubernetes Service + kube-proxy
Используется где?Spring Cloud, ECSKubernetes, Istio, Consul Connect

Режим самосохранения в Eureka

Одна из ключевых особенностей Eureka — механизм самосохранения (Self-Preservation). Он защищает систему от ложных срабатываний в нестабильных сетях.

Как это работает:

  • Каждый экземпляр сервиса отправляет heartbeat на Eureka каждые ~30 секунд;
  • Если heartbeat не поступает, Eureka по умолчанию исключает инстанс из реестра;
  • Но если таких инстансов много одновременно, и есть риск обнулить все — Eureka включает режим самосохранения.

В этом режиме реестр замораживается — никто не удаляется, даже если не приходит heartbeat, что позволяет избежать каскадного удаления всех сервисов. Реестр выходит из этого режима вручную или автоматически, когда восстановится стабильная связь.

⚠️ Это важно для продакшн-сред — иначе при сетевом лаге можно потерять все инстансы.

Например, поднято 5 экземпляров profiles-service. Если случился сбой сети, и Eureka временно не видит 3 из 5 — он не будет удалять их из реестра, а продолжит возвращать список всех 5, пока режим самосохранения активен.

Статьи серии

This post is licensed under CC BY 4.0 by the author.