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

Обновлено: 7 мая 2026 г. в 18:26

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

средний

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

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

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

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

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

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

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

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

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

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

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

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

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

Контекст

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

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

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

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

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

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

HTTP/gRPC по схеме запрос-ответ

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

Агрегация в BFF или отдельном сервисе

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

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

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

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

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

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

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

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

gRPC, REST и GraphQL: конфигурации и мини-бенчмарк

Один регион, внутренний VPC, TLS включён, около 1 KiB.

Сервис на 4 vCPU / 8 GB RAM, 300 одновременных виртуальных пользователей.

Чтение из кэша в памяти, без внешней БД и без тяжёлой бизнес-логики.

Числа ниже - лабораторная точка сравнения, а не универсальный закон.

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 + Protobuf)

Меньше накладных расходов протокола и строгий 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 (BFF/Gateway)

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

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),
  },
};
Подходp50, мсp95, мсПропускная способностьКомментарий
REST (JSON, HTTP/1.1)12 ms41 ms~6.1k req/sПотери на сериализации JSON и больший объём данных в сети.
gRPC unary (Protobuf, HTTP/2)7 ms24 ms~9.8k req/sЛучше использует CPU и сеть при схожей бизнес-логике.
GraphQL-шлюз (persisted queries + DataLoader)15 ms53 ms~4.3k req/sУдобен для UI, но добавляет накладные расходы резолверов и риски .

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

Никогда не переиспользуйте номера полей (`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, синхронно15-60 ms (p95)3k-8k req/s на узелВнешние API и простые интеграцииТяжелее , обычно выше стоимость сериализации на CPU.
gRPC, синхронно8-30 ms (p95)6k-15k req/s на узелВнутренние RPC с малой задержкой и потоковая передачаНужны инструменты, управление IDL-контрактами и готовность к HTTP/2.
GraphQL (BFF/шлюз)25-90 ms (p95)1k-5k req/s на шлюзеАгрегация для UI и продуктовые контрактыРаздача запросов через резолверы, сложнее профилировать и кэшировать.
Очередь, асинхронно40 ms - 2 s10k-120k msg/sФоновые команды и сглаживание пиков трафика и отдельный эксплуатационный контур для очередей.
Pub/Sub события20-300 ms50k-500k msg/s (cluster)Доменные события и независимая реакция нескольких сервисовСложнее контролировать порядок доставки, дубли и .

Контракты событий: 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`/topic (`...v1`) и отдельная схема в .

Документированы SLA доставки: ожидания , и TTL.

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

Reliability

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источники

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

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