Раздел 9 · Backend

Топология микросервисов: gateway, discovery, mesh

senior~30 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Топология микросервисов: gateway, discovery, mesh

Четыре урока подряд мы смотрели на связи между сервисами по одной: вызов и его таймаут, событие и его доставка, очередь и её DLQ. Пора подняться на высоту, с которой видна вся карта: как запрос с телефона находит нужный из тридцати сервисов, кто проверяет его токен, кто повторяет упавший вызов, и как потом восстановить маршрут запроса по журналу. Это урок про инфраструктурный этаж микросервисов: API gateway и BFF на входе, service discovery внутри, sidecar и mesh как способ вынести сетевую логику из кода, и distributed tracing как зрение во всём этом хозяйстве. Урок обзорный: каждая из четырёх тем руками собирается в Go-практикуме раздела 24 и в уроках про инфраструктуру, здесь же мы строим карту, чтобы было куда класть детали.

Service discovery: как сервисы находят друг друга

Начнём с вопроса, который в монолите не существовал: по какому адресу звать соседа? Пока сервис был модулем, ответом был импорт. Теперь tickets живёт в трёх экземплярах, которые пересоздаются при каждом деплое и автоскейле, и у каждого новый IP. Конфиг с прописанными адресами мёртв с рождения: он устаревает быстрее, чем доезжает до прода.

Ответ индустрии: адрес это не конфигурация, а ответ на запрос к живому источнику. Источник называется service registry, и вокруг него два решения, которые надо принять.

Первое: кто регистрирует. Либо сервис сам сообщает о себе при старте и шлёт heartbeat (self-registration, так работают клиенты Consul и Eureka), либо это делает платформа, которая и так знает, что и где запустила (Kubernetes регистрирует поды сама). Второй вариант снимает заботу с кода, и это один из главных бытовых аргументов за K8s.

Второе: кто выбирает инстанс. Здесь классическая развилка:

  • Client-side discovery. Клиент сам спрашивает реестр, получает список живых инстансов и сам выбирает, к кому пойти. Плюс: балансировка в клиенте, можно умную (меньше всего висящих запросов, ближайшая зона). Минус: логика discovery повторяется в каждом языке и каждом сервисе, и её версии расползаются.
  • Server-side discovery. Клиент ходит на стабильное имя, а реестр спрятан за балансировщиком или DNS. Плюс: клиент туп и прост. Минус: ещё один прыжок и ещё один компонент на пути.

Сегодняшний дефолт выглядит обыденно: Kubernetes Service. Сервис tickets доступен по DNS-имени tickets.default.svc, kube-proxy раскидывает соединения по живым подам, состав реестра определяется health check-ами. Это server-side discovery из коробки, и для большинства систем вопрос закрыт до того, как был задан. Имена Consul (полноценный registry с health-чеками и key-value) и Eureka (исторический client-side registry из стека Netflix) стоит знать, чтобы читать архитектурные статьи, но тянуть их в систему поверх K8s нужно редко.

Деталь, которая связывает главу с уроком 18: health check как условие членства в реестре. Вспомни медленный сервис рекомендаций: процесс жив, порт открыт, health check зелёный, пользы ноль. Хороший check проверяет способность работать, а не способность ответить «я жив»: глубина очереди, доступность базы, доля ошибок. Иначе discovery честно балансирует трафик между тремя инстансами, два из которых в коме.

API Gateway и BFF

Снаружи системы тот же вопрос звучит острее: телефону пользователя нельзя выдать список из тридцати сервисов и пожелать удачи. Клиент не должен знать топологию вовсе: она меняется каждый квартал, а приложение в сторах живёт годами.

Точка входа, которая прячет топологию, называется API gateway. Кроме маршрутизации на ней селятся все сквозные заботы входа: TLS, аутентификация (проверить токен один раз на входе, внутрь передать проверенную identity), rate limiting, версии API, кеш. Знакомая логика: это фасад из урока 17, только постоянный, а не миграционный, и от reverse proxy или балансировщика его отличает именно осведомлённость об API: proxy гоняет байты, gateway знает про маршруты, токены и контракты.

У gateway есть собственный гравитационный колодец: в него протекает бизнес-логика. Сначала «давайте склеим два ответа», потом «и отфильтруем по правам», потом «и посчитаем скидку», и вот на входе системы стоит новый монолит, который меняется при каждой фиче и принадлежит всем сразу, то есть никому. Правило гигиены: gateway занимается транспортом и доступом, доменная логика живёт в сервисах.

Отдельная история начинается, когда клиентов становится много и они разные. Вебу нужна страница сеанса одним толстым ответом, мобильному тот же экран в три раза тоньше (трафик и батарея), смарт-ТВ нужен другой формат картинок, а партнёрскому API вообще другая модель. Один gateway на всех превращается в поле боя: команды клиентов конкурируют за его роадмап и ломают друг другу форматы. Решение Сэма Ньюмана: BFF. У каждого фронтенда свой тонкий бэкенд, которым владеет команда этого фронтенда: мобильная команда сама решает, какой формат нужен её экранам, и не согласовывает это ни с кем. Это снова закон Конвея из урока 17, применённый к точке входа: граница по команде.

Главная работа BFF: агрегация. Экран сеанса собирается из трёх сервисов, и делать это лучше в одном месте за один сетевой прыжок от клиента, чем тремя запросами с телефона через мобильную сеть. Набросок на Fastify, с дисциплиной из урока 18:

app.get('/bff/sessions/:id', async (request) => {
  const { id } = request.params;
  // бюджет всего экрана: 800 мс, дальше клиент уже страдает
  const signal = AbortSignal.any([
    AbortSignal.timeout(800),
    request.raw.signal,
  ]);

  const [session, seats, recs] = await Promise.all([
    fetchJson(`${CATALOG}/sessions/${id}`, { signal }),
    fetchJson(`${TICKETS}/sessions/${id}/availability`, { signal }),
    // рекомендации не стоят того, чтобы ронять экран: деградируем
    fetchJson(`${RECS}/for-session/${id}`, { signal }).catch(() => []),
  ]);

  return { session, seats, recommendations: recs };
});

Три вызова едут параллельно (латентность экрана равна худшему из трёх, а не сумме), у всех общий дедлайн, а необязательный блок рекомендаций деградирует в пустой список вместо ошибки. Где агрегировать, на BFF или на фронте, мы уже обсуждали со стороны клиента в уроке про состояние и формы; ответ BFF «экран одним запросом» особенно хорош для мобильных сетей с их дорогими раундтрипами.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.