Связь между сервисами ломается не на happy path, а в таймаутах, ретраях и неявных ожиданиях сторон.
Для реального проектирования глава помогает увидеть, как синхронные и асинхронные паттерны надо выбирать по SLA, допустимой задержке бизнес-потока, coupling и правилам работы с timeout, retry, backoff и idempotency.
Для интервью и инженерных разборов она полезна тем, что помогает обсуждать backpressure, queue build-up и partial failure как свойства контракта, а не как случайные детали реализации.
Практическая польза главы
Практика проектирования
Подбирайте sync/async взаимодействие по SLA, coupling и допустимой задержке бизнес-потока.
Качество решений
Формализуйте timeout, retry, backoff и idempotency в контракте, а не в ad-hoc коде.
Interview articulation
Объясняйте выбор паттерна через последствия для latency, reliability и developer productivity.
Failure framing
Предусматривайте backpressure и queue build-up до появления продовых инцидентов.
Контекст
Стратегии декомпозиции
Способ декомпозиции системы определяет типы и количество межсервисных взаимодействий.
Паттерны межсервисной коммуникации нужно выбирать не по моде, а по latency budget, критичности операции и операционным ограничениям. Главная цель - предсказуемое поведение системы под нагрузкой и при отказах.
Синхронные паттерны
HTTP/gRPC request-response
Подходит для low-latency запросов, когда клиенту нужен немедленный ответ. Для прода почти всегда нужны timeout budgets, retry budget и явная политика деградации.
Aggregator/BFF composition
Отдельный сервис собирает данные из нескольких upstream. Удобно для UI, но при fan-out без cache и параллелизма быстро превращается в latency bottleneck.
Асинхронные паттерны
Queue-based async
Producer и consumer развязаны по времени; удобно для выравнивания spikes и фоновых процессов. Хорошо работает для команд, где нужен контроль ретраев и DLQ.
Pub/Sub events
Один emitter публикует событие, несколько подписчиков реагируют независимо. Хорошо для расширяемости и минимизации связности между командами.
Event-carried state transfer
Событие несет достаточно контекста, чтобы снизить синхронные callback-запросы между сервисами. Цена - более строгие требования к versioning и размеру payload.
gRPC vs REST vs GraphQL: конфигурации и мини-бенчмарк
Один регион, внутренний VPC, TLS включён, payload ~1 KiB.
Сервис на 4 vCPU / 8 GB RAM, 300 одновременных виртуальных пользователей.
Чтение из in-memory cache, без внешней БД и без тяжелой бизнес-логики.
Числа ниже - пример лабораторного baseline, не universal truth.
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)
Более низкий overhead и строгий 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 latency | p95 latency | Throughput | Комментарий |
|---|---|---|---|---|
| REST (JSON, HTTP/1.1) | 12 ms | 41 ms | ~6.1k req/s | Потери на сериализации JSON и более высокий wire size. |
| gRPC unary (Protobuf, HTTP/2) | 7 ms | 24 ms | ~9.8k req/s | Лучше CPU/network efficiency при схожей бизнес-логике. |
| GraphQL gateway (persisted queries + DataLoader) | 15 ms | 53 ms | ~4.3k req/s | Удобен для UI, но цена - resolver overhead и fan-out риски. |
Protobuf schema evolution без боли
Никогда не переиспользуйте номера полей (`field numbers`) после удаления.
Удалённые поля помечайте как `reserved` (и по номеру, и по имени).
Добавляйте новые поля только как optional/nullable и с безопасным default behavior.
Для 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;
}| Изменение | Backward | Forward | Комментарий |
|---|---|---|---|
| Добавили новое поле | Да | Да | Старые consumer игнорируют незнакомое поле. |
| Удалили поле + reserved | Условно | Нет | Если старые producer ещё пишут поле, новый consumer теряет значение. |
| Сменили тип поля (int32 -> string) | Нет | Нет | Wire-формат меняется, декодирование становится небезопасным. |
| Добавили значение enum | Да | Условно | Старый код должен иметь fallback на unknown enum value. |
Performance
Performance Engineering
Latency и throughput нужно измерять на своей нагрузке и на реалистичных payload.
Сравнительная таблица latency/throughput
| Подход | Типичная latency | Типичный throughput | Где чаще подходит | Ключевой trade-off |
|---|---|---|---|---|
| REST sync | 15-60 ms (p95) | 3k-8k req/s на узел | External/public API, simple integrations | Тяжелее payload, обычно больше CPU на serialization. |
| gRPC sync | 8-30 ms (p95) | 6k-15k req/s на узел | Internal low-latency RPC, streaming | Нужны tooling/IDL governance и HTTP/2 readiness. |
| GraphQL (BFF/Gateway) | 25-90 ms (p95) | 1k-5k req/s gateway | UI aggregation, product-driven contracts | Resolver fan-out, сложнее профилировать и кэшировать. |
| Queue-based async | 40 ms - 2 s | 10k-120k msg/s | Background commands, smoothing traffic spikes | Eventual consistency и очереди как отдельный operational контур. |
| Pub/Sub events | 20-300 ms | 50k-500k msg/s (cluster) | Domain events и независимая реакция нескольких сервисов | Сложнее контроль ordering/duplication и эволюция contracts. |
Реальные event contracts: 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 (контракт канала и payload)
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 }Событие содержит business key (`orderId`) и технический id (`id`) для deduplication.
Есть явная версия в `type`/topic (`...v1`) и отдельная схема в registry.
Документированы SLA доставки: at-least-once/exactly-once expectations и TTL.
У каждого события есть owner-команда и политика deprecation для версии контракта.
Reliability
Паттерны отказоустойчивости
Коммуникация без resilience-политик в распределенной среде обычно неустойчива.
Как выбирать паттерн
Нужен ответ пользователю в рамках одного HTTP-запроса -> чаще sync path.
Нужны устойчивость к пикам и loose coupling -> async через queue/topic.
Если операция критична для денег/заказов, проверяйте idempotency и ordering до выбора паттерна.
Если у вас много cross-service hops, снижайте глубину синхронных цепочек и внедряйте cache/materialized views.
Timeout budgets на каждый hop и общая end-to-end дедлайн-политика.
Retry с jitter и retry budget, чтобы не создавать retry storm при деградации зависимости.
Circuit breaker/bulkhead для изоляции отказов и контроля конкурентности.
Idempotency keys для команд и deduplication для event-consumers.
DLQ/parking lot для невалидных или проблемных сообщений.
Практический чеклист
- Для каждого интеграционного канала задан owner, SLO и error budget.
- Контракты версионируются и проверяются contract-тестами в CI.
- Есть стратегия деградации при недоступности downstream сервиса.
- Трассировка покрывает end-to-end path через sync и async сегменты.
- Критичные команды и события обрабатываются идемпотентно.
References
Связанные главы
- Event-Driven Architecture - Глубже про модели событий и проектирование асинхронных flows.
- Паттерны отказоустойчивости - Timeout/retry/circuit breaker как обязательный слой inter-service коммуникации.
- Консистентность и идемпотентность - Как обеспечить корректность данных при retries и повторной доставке событий.
- Service Discovery - Коммуникации между сервисами опираются на корректное обнаружение endpoint-ов.
- Стратегии декомпозиции - Границы сервисов напрямую влияют на интенсивность и сложность коммуникаций.
