System Design Space
Граф знанийНастройки

Обновлено: 24 июня 2026 г. в 19:41

Паттерны межсервисной коммуникации

средний

Синхронное и асинхронное взаимодействие между сервисами: REST, gRPC, GraphQL, очереди, модель публикации и подписки, контракты, тайм-ауты, повторные попытки, идемпотентность и обратное давление.

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

Для реального проектирования глава помогает увидеть, как синхронные и асинхронные паттерны выбирать по соглашению об уровне сервиса (SLA), допустимой задержке бизнес-потока, связанности и правилам работы с тайм-аутами, повторами, экспоненциальной паузой и идемпотентностью.

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

Практическая польза главы

Практика проектирования

Подбирайте способ взаимодействия по соглашению об уровне сервиса (SLA), связанности сервисов и допустимой задержке бизнес-потока.

Качество решений

Формализуйте тайм-ауты, повторные попытки, экспоненциальную паузу и идемпотентность в контракте, а не в случайном коде.

Аргументация на интервью

Объясняйте выбор паттерна через влияние на задержку, надёжность и скорость разработки.

Анализ отказов

Предусматривайте обратное давление и накопление очереди до появления инцидентов в рабочей среде.

Контекст

Стратегии декомпозиции

Способ декомпозиции системы определяет типы и количество межсервисных взаимодействий.

Открыть главу

Паттерны межсервисной коммуникации выбирают не по моде, а по трём вещам: бюджету задержки, критичности операции и эксплуатационным ограничениям. От этого выбора зависит, останется ли поведение системы предсказуемым под нагрузкой и при отказах. Глава охватывает весь спектр — и синхронные запросы, и асинхронные очереди и события; детальное сравнение именно синхронных стилей API (REST, gRPC, GraphQL) вынесено в главу «Удалённые вызовы API».

В этой главе означает, что вызывающий сервис ждёт ответ здесь и сейчас. переносит работу в очередь, топик или поток событий. Архитектурное решение начинается с , политики , правил и . REST, gRPC, GraphQL, очередь или модель публикации и подписки выбирают уже после этого.

Синхронные способы взаимодействия

Протокол HTTP и gRPC по схеме запрос-ответ

Подходит для запросов с малой задержкой, когда клиенту нужен немедленный ответ. В рабочей среде почти всегда нужны , и явная политика деградации.

Агрегация в бэкенде для фронтенда (BFF) или отдельном сервисе

Отдельный сервис собирает данные из нескольких . Это удобно для UI, но без кэша и параллелизма быстро превращается в узкое место по задержке.

Асинхронные способы взаимодействия

Асинхронность через очередь

и развязаны по времени; это удобно для сглаживания пиков нагрузки и фоновых процессов. Хорошо работает для команд, где нужен контроль повторных попыток и .

События по модели публикации и подписки

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

Передача состояния в событии

Событие несёт достаточно контекста, чтобы сократить синхронные между сервисами. Цена такого подхода - более строгая дисциплина версионирования и контроль размера .

gRPC, REST и GraphQL: конфигурации и сравнение накладных расходов

Абсолютные p50/ и запросы в секунду зависят от размера и формы , сети, шифрования , языка и настроек пула соединений - чужой на вашу систему не переносится.

Сравнивать подходы честнее по структуре накладных расходов: , число соединений и дополнительные фазы обработки запроса.

Нужны количественные ориентиры - измеряйте на своём профиле нагрузки: с реальными сообщениями, реальной сетью и целевыми перцентилями.

Публичные замеры (например, раунды TechEmpower) сравнивают конкретные фреймворки и конфигурации, а не «REST против gRPC вообще»: переносите выводы, а не цифры.

REST (HTTP/1.1 + JSON)

Простая интеграция и совместимость с внешними клиентами

# NGINX upstream + keep-alive
upstream user_api {
  server user-api:8080;
  keepalive 256;
}

server {
  listen 443 ssl http2;
  location /v1/ {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header X-Request-Id $request_id;
    proxy_read_timeout 300ms;
    proxy_connect_timeout 80ms;
    proxy_pass http://user_api;
  }
}

gRPC (HTTP/2 + Protocol Buffers)

Меньше накладных расходов протокола и строгий контракт на языке описания интерфейса (IDL)

// service.proto
syntax = "proto3";
package catalog.v1;

service CatalogService {
  rpc GetItem(GetItemRequest) returns (GetItemResponse);
}

// envoy cluster (fragment)
clusters:
  - name: catalog_grpc
    connect_timeout: 0.08s
    type: STRICT_DNS
    http2_protocol_options:
      max_concurrent_streams: 512
    load_assignment:
      cluster_name: catalog_grpc
      endpoints: ...

GraphQL (бэкенд для фронтенда / шлюз)

Клиент-ориентированный контракт и сборка данных из нескольких доменов

const server = new ApolloServer({
  schema,
  persistedQueries: {
    cache: redisCache,
  },
  plugins: [responseCachePlugin()],
});

// resolver guardrails
const resolvers = {
  Query: {
    dashboard: async (_, args, ctx) =>
      ctx.loaders.dashboardByUser.load(args.userId),
  },
};
ПодходСериализация и объём данныхТранспорт и параллелизмКогда обычно выигрывает
REST (JSON, HTTP/1.1)Текстовый JSON: больше байтов в сети и заметная стоимость разбора на CPU.Одно соединение - один запрос за раз; параллелизм даёт пул соединений с , а очередь внутри соединения упирается в .Внешние API и интеграции, где важны совместимость, читаемость и простота отладки.
gRPC unary (Protobuf, HTTP/2)Бинарный компактнее и дешевле в разборе, чем JSON; выигрыш заметнее на мелких структурированных сообщениях.Протокол параллельные вызовы в одном соединении и поддерживает потоковую передачу.Внутренние вызовы с частыми мелкими сообщениями, жёстким бюджетом задержки и стримингом.
GraphQL-шлюз (сохранённые запросы + пакетная загрузка данных)Ответ - тот же JSON; сверху добавляются парсинг и валидация запроса плюс исполнение .Работает поверх обычного протокола ; узкое место не транспорт, а обращения резолверов к нижестоящим сервисам - вплоть до .Агрегация для UI: меньше обращений с клиента и точная выборка полей ценой накладных расходов шлюза.

Где смотреть реальные замеры

  • gRPC Benchmarking Guide - методика непрерывных замеров gRPC по языкам; сравнения с REST там нет
  • Protocol Buffers Overview - почему бинарный формат компактнее и быстрее JSON и где он не подходит
  • GraphQL Performance - , батчинг через и контроль сложности запросов
  • TechEmpower Web Framework Benchmarks - раунды сравнения фреймворков; цифры привязаны к конкретному раунду и железу

Эволюция схем Protocol Buffers без боли

Никогда не переиспользуйте номера полей (`field numbers`) после удаления.

Удалённые поля помечайте как `reserved` (и по номеру, и по имени).

Добавляйте новые поля только как optional/nullable и с безопасным поведением по умолчанию.

Для enum всегда оставляйте `*_UNSPECIFIED = 0` и обрабатывайте неизвестные значения.

Ломающее изменение - смена типа, перенос в `oneof` или удаление обязательного поведения - требует новой .

Было (v1)

syntax = "proto3";
message UserProfile {
  string user_id = 1;
  string email = 2;
  string phone = 3;
}

Стало (v2, безопасная эволюция)

syntax = "proto3";
message UserProfile {
  string user_id = 1;
  string email = 2;
  reserved 3;
  reserved "phone";
  optional string telegram = 4;
}
ИзменениеОбратная совместимостьПрямая совместимостьКомментарий
Добавили новое полеДаДаСтарые потребители игнорируют незнакомое поле.
Удалили поле + reservedДаДаФормат совместим в обе стороны: значение от старых производителей попадает в неизвестные поля, старые потребители читают значение по умолчанию. Логику, зависящую от поля, выводите заранее.
Сменили тип поля (int32 -> string)НетНетФормат данных в сети меняется, декодирование становится небезопасным.
Добавили значение enumДаУсловноСтарый код должен иметь резервный сценарий для неизвестного значения enum.

Performance

Performance Engineering

Задержку и пропускную способность нужно измерять на своей нагрузке и с реалистичной полезной нагрузкой.

Открыть главу

Сравнение задержки и пропускной способности

ПодходПрофиль задержкиПрофиль пропускной способностиГде чаще подходитКлючевой компромисс
REST, синхронноНизкая внутри дата-центра; растёт с объёмом JSON и глубиной цепочки вызововУпирается в стоимость сериализации и размер пула соединенийВнешние API и простые интеграцииТяжелее , обычно выше стоимость сериализации на CPU.
gRPC, синхронноОбычно ниже, чем у REST на том же пути: компактная сериализация и протокол Выше на частых мелких вызовах: мультиплексирование снимает лимит числа соединенийВнутренние удалённые вызовы процедур (RPC) с малой задержкой и потоковая передачаНужны инструменты, управление контрактами на языке описания интерфейса и готовность к протоколу HTTP/2.
GraphQL (бэкенд для фронтенда или клиентский шлюз)Определяется самым медленным резолвером и глубиной запросаОграничена исполнением запросов на шлюзе, а не транспортомАгрегация для UI и продуктовые контрактыРаздача запросов через резолверы, сложнее профилировать и кэшировать.
Очередь, асинхронноОпределяется глубиной очереди и темпом потребителей, а не сетьюМасштабируется горизонтально: партициями и числом потребителейФоновые команды и сглаживание пиков трафика и отдельный эксплуатационный контур для очередей.
События по модели публикации и подпискиЗависит от батчинга, настроек надёжности брокера и скорости подписчиковВысокая: лог-ориентированные брокеры оптимизированы под последовательную запись и батчиДоменные события и независимая реакция нескольких сервисовСложнее контролировать порядок доставки, дубли и .

Обратное давление и управление потоком

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

Сигнал перегрузки: отставание потребителя

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

Ограничение конкурентности и буферов

Ограниченные очереди и явный лимит конкурентности - размер пула обработчиков, семафоры, - превращают перегрузку в управляемое замедление, а не в исчерпание памяти и каскадный отказ.

Предварительная загрузка (prefetch) и кредитные схемы

Потребитель сам сообщает, сколько готов принять: в RabbitMQ (`basic.qos`), и `max.poll.records` в Kafka, кредитные окна протокола , на которые опирается gRPC, и `request(n)` в Reactive Streams.

Реакция на переполнение

Когда буфер всё же полон, нужна явная политика: притормозить производителя, ответить 429 с , включить для некритичного трафика или увести сообщения в . Худший вариант - неограниченный буфер «до лучших времён».

Контракты событий: CloudEvents и AsyncAPI

CloudEvents (пример доменного события)

{
  "specversion": "1.0",
  "type": "com.shop.order.paid.v1",
  "source": "urn:shop:payments",
  "id": "evt-01HQ7V0R4Z6A0G3T95S1ZQ6B9N",
  "time": "2026-03-03T14:23:44Z",
  "subject": "order/938475",
  "datacontenttype": "application/json",
  "dataschema": "https://events.shop.dev/schemas/order-paid-v1.json",
  "data": {
    "orderId": "938475",
    "userId": "u-1821",
    "amount": 149.90,
    "currency": "USD",
    "paymentMethod": "card"
  }
}

AsyncAPI (контракт канала и полезной нагрузки)

asyncapi: 3.0.0
info:
  title: Order Events API
  version: 1.4.0
channels:
  order.paid.v1:
    address: order.paid.v1
    messages:
      orderPaid:
        $ref: '#/components/messages/OrderPaid'
operations:
  onOrderPaid:
    action: receive
    channel:
      $ref: '#/channels/order.paid.v1'
    messages:
      - $ref: '#/channels/order.paid.v1/messages/orderPaid'
components:
  messages:
    OrderPaid:
      payload:
        type: object
        required: [orderId, userId, amount, currency]
        properties:
          orderId: { type: string }
          userId: { type: string }
          amount: { type: number }
          currency: { type: string }

Событие содержит бизнес-ключ (`orderId`) и технический идентификатор (`id`) для .

Есть явная версия в `type`/топике Kafka (`...v1`) и отдельная схема в .

Документировано соглашение об уровне сервиса доставки (SLA): ожидания , и время жизни сообщения.

У каждого события есть команда-владелец и политика вывода из эксплуатации.

Reliability

Паттерны отказоустойчивости

Коммуникация без политик отказоустойчивости в распределённой среде обычно нестабильна.

Открыть главу

Как выбирать способ взаимодействия

Нужен ответ пользователю в рамках одного запроса по протоколу HTTP - чаще подходит .

Когда на первом месте устойчивость к пикам и слабая связанность, выбирайте через очередь или топик.

Если операция критична для денег или заказов, проверяйте и порядок доставки до выбора паттерна.

Чем больше в пути, тем дороже синхронные цепочки: снижайте их глубину и подставляйте кэш или .

Бюджет тайм-аутов на каждый и общий срок выполнения для всего пути.

с и , чтобы не создать при деградации зависимости.

и для локализации отказов и контроля конкурентности.

для команд и для потребителей событий.

и , которые нельзя обработать автоматически.

Практический чек-лист

  • Для каждого интеграционного канала задан владелец, целевой уровень сервиса (SLO) и .
  • Контракты версионируются и проверяются в CI.
  • Есть стратегия деградации при недоступности .
  • Трассировка покрывает сквозной путь через синхронные и асинхронные сегменты.
  • Критичные команды и события обрабатываются идемпотентно.

Источники

Связанные главы

  • Event-Driven Architecture - Когда асинхронных потоков становится много, событиями нужно осознанно управлять: модели событий и проектирование потоков разобраны здесь.
  • Паттерны отказоустойчивости - Тайм-ауты, повторные попытки и предохранители как обязательный слой межсервисной связи.
  • Консистентность и идемпотентность - Как обеспечить корректность данных при повторных попытках и повторной доставке событий.
  • Обнаружение сервисов - Коммуникации между сервисами опираются на корректное обнаружение актуальных адресов.
  • Стратегии декомпозиции - Границы сервисов напрямую влияют на интенсивность и сложность коммуникаций.

Чтобы отмечать прохождение, включи трекинг в Настройки