🏗️ Раздел 17 · Вопрос #16

В чём разница между синхронной и асинхронной коммуникацией

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


🟢 Junior Level

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

Синхронная (Телефонный звонок):
[Сервис A] ──Запрос (HTTP/gRPC)──► [Сервис B]
     │ (Поток Сервиса A заблокирован / ждёт ответа)
     ◄──────────Ответ (200 OK)────────┘

Асинхронная (Отправка письма / Email):
[Сервис A] ──Публикация события──► [Брокер: Kafka / RabbitMQ]
     │ (Сразу продолжает работу!)           │
                                            ▼ (Обработка позже)
                                       [Сервис B]

Основные понятия:

  1. Синхронная коммуникация (Point-to-Point): Клиент отправляет запрос (REST, gRPC) и блокирует поток исполнения до получения ответа (или истечения таймаута). Если целевой сервис недоступен, запрос немедленно завершается ошибкой.
  2. Асинхронная коммуникация (Message-Driven / Event-Driven): Отправитель публикует сообщение в промежуточный буфер (брокер сообщений) и сразу возвращает управление. Получатель вычитывает сообщение из очереди тогда, когда у него появятся свободные ресурсы.

🟡 Middle Level

1. Детальное сравнительное сопоставление

Критерий Синхронная коммуникация (REST, gRPC) Асинхронная коммуникация (Kafka, RabbitMQ)
Временная связанность (Temporal Coupling) Высокая: Оба сервиса обязаны быть онлайн одновременно в момент вызова. Нулевая: Сервис-получатель может быть выключен; он обработает сообщения после запуска.
Пространственная связанность (Spatial Coupling) Высокая: Клиент обязан знать сетевой адрес, DNS или имя сервиса. Низкая: Продюсер знает только имя топика; он ничего не знает о потребителях.
Модель согласованности Немедленная консистентность (Immediate Consistency). Согласованность в конечном счете (Eventual Consistency).
Сглаживание пиков нагрузки (Peak Smoothing) Нет: всплеск входящего трафика передается напрямую на бэкенд и базу данных. Да: Брокер буферизует миллионы сообщений на диске; консьюмер читает их в своем темпе.
Сложность отладки и трассировки Низкая: стек-трейс и логи линейны; легко тестировать в Postman. Высокая: распределённые события, гонки сообщений, дубликаты, необходимость DLQ.
Каскадные сбои (Cascading Failures) Высокий риск: зависание одного сервиса блокирует вызывающие сервисы. Низкий риск: отказ консьюмера изолирован внутри очереди.

2. Примеры в коде

А. Синхронный вызов (Spring WebClient):

@Service
@RequiredArgsConstructor
public class InventoryClient {

    private final WebClient webClient;

    public Mono<InventoryResponse> checkStock(String productId) {
        return webClient.get()
            .uri("/inventory/{id}", productId)
            .retrieve()
            .bodyToMono(InventoryResponse.class)
            .timeout(Duration.ofSeconds(2)); // Таймаут критически важен!
    }
}

Б. Асинхронный вызов (Spring Kafka):

@Service
@RequiredArgsConstructor
public class NotificationSender {

    private final KafkaTemplate<String, EmailNotificationEvent> kafkaTemplate;

    public void queueEmail(EmailNotificationEvent event) {
        // Отправили и забыли: поток метода не ждет отправки реального письма
        kafkaTemplate.send("email-notifications", event.userId(), event);
    }
}

🔴 Senior Level

1. Сглаживание пиковых нагрузок (Traffic Leveling / Backpressure)

Главное архитектурное преимущество асинхронных очередей — защита базы данных от разрушения:

Синхронный пик (Black Friday):
10 000 RPS ──► [Order Service] ──(10 000 одновременных транзакций)──► [PostgreSQL: СМЕРТЬ 💥]

Асинхронный пик с Kafka (Pull-модель):
10 000 RPS ──► [Kafka Topic: буферизация на диске]
                       │ (Backpressure: консьюмер берет строго по 100 сообщений)
                       ▼
               [Order Consumer] ──(100 TPS: стабильная нагрузка)──► [PostgreSQL: СТАБИЛЬНО ✅]

В синхронной архитектуре всплеск трафика пробивает все слои вплоть до диска СУБД. В асинхронной модели консьюмер работает по Pull-модели (max.poll.records = 100), физически не позволяя нагрузке превысить пропускную способность базы данных.

2. Обработка ошибок: HTTP Status Codes vs Dead Letter Queue (DLQ)

  • В синхронных вызовах: Ошибка обрабатывается на месте: возврат HTTP 4xx/5xx, срабатывание @CircuitBreaker, повтор @Retry или передача управления в Fallback.
  • В асинхронных системах: Если сообщение не может быть обработано (например, невалидные данные):
    1. Нельзя просто бесконечно повторять чтение, так как это заблокирует смещение (offset) всей партиции.
    2. Нельзя просто выбросить сообщение, потеряв деньги клиента.
    3. Используется паттерн Retry Topics + Dead Letter Topic (DLT / DLQ):
[Main Topic] ──(Ошибка)──► [Retry Topic (Backoff 5s)] ──(Ошибка)──► [Dead Letter Topic (DLT)]
                                                                               │
                                                                               ▼
                                                                     [Manual Audit / Alert]

3. Гибридная парадигма: Async бэкенд с Sync пользовательским опытом

Бизнес часто требует немедленной обратной связи в UI (например, «Заказ успешно оформлен»), в то время как весь бэкенд спроектирован асинхронно:

  1. HTTP 202 Accepted: Шлюз быстро проверяет синтаксис, генерирует orderId, кладет задачу в очередь и немедленно отдает клиенту ответ: HTTP 202 Accepted со ссылкой на статус /api/orders/{id}/status.
  2. Push-уведомление в UI: Фронтенд слушает результат через WebSocket или Server-Sent Events (SSE). Когда фоновая цепочка саги завершается, сервер пушит событие OrderConfirmed прямо в браузер.

4 Tricky Questions

1. Почему отправка сообщения через KafkaTemplate.send() может неожиданно заблокировать вызывающий поток?

Ответ: Метод kafkaTemplate.send() считается асинхронным, но он может заблокировать поток на время до max.block.ms (по умолчанию 60 секунд) в следующих случаях:

  1. Отсутствие метаданных кластера: При первом вызове продюсер должен получить метаданные о топике и лидерах партиций от брокера. Если сеть недоступна, поток блокируется в ожидании метаданных.
  2. Переполнение буфера памяти: Если сеть медленнее входящего потока данных, локальный буфер продюсера (buffer.memory, по умолчанию 32 МБ) переполняется, и метод send() блокирует вызывающий поток.
  3. Ошибочный вызов .get() на возвращенном объекте CompletableFuture.

2. Что такое Poison Pill («Отравленная пилюля») в очереди сообщений и как от неё защититься?

Ответ: Poison Pill — это поврежденное сообщение в топике, которое вызывает неустранимое исключение при попытке обработки (например, поврежденный JSON, который падает на этапе десериализации до попадания в бизнес-код). Консьюмер падает $\to$ брокер перезапускает консьюмер $\to$ консьюмер снова читает то же самое сообщение $\to$ падает опять (бесконечный цикл сбоя партиции). Защита: В Spring Kafka используется ErrorHandlingDeserializer. Он перехватывает ошибку десериализации, оборачивает её в специальный заголовок и передает управление в RecordListener, который сразу отправляет битое сообщение в Dead Letter Topic (DLT) без зависания консьюмера.

3. Как изменится latency пользовательского запроса при замене синхронного HTTP-вызова на асинхронную связку через брокер?

Ответ: Latency отдельного единичного запроса вырастет:

  • В синхронном REST: клиент $\to$ сеть $\to$ сервер $\to$ ответ (единицы миллисекунд).
  • В асинхронном флоу: клиент $\to$ запись в брокер $\to$ репликация на диск брокера $\to$ опрос консьюмером $\to$ обработка $\to$ нотификация клиента (десятки или сотни миллисекунд). Однако пропускная способность (Throughput) системы вырастет в разы, так как потоки не блокируются в ожидании, а нагрузка равномерно распределяется во времени.

4. Почему нельзя использовать асинхронную коммуникацию при прямых операциях списания средств в банковских POS-терминалах?

Ответ: Сценарий оплаты на кассе магазина требует строгой синхронности: кассовый терминал удерживает открытый сетевой сокет и требует бинарного ответа «Одобрено» или «Отклонено» в течение 2–3 секунд, пока покупатель держит карту у терминала. Асинхронная Eventual Consistency здесь неприменима: кассир не может отпустить покупателя с товаром, надеясь, что через 10 минут событие из Kafka обработается успешно и деньги реально спишутся со счета.


🎯 Шпаргалка для интервью

Главное:

  • Синхронная (REST, gRPC): Телефонный звонок. Быстро для чтения (Read), строгая консистентность, но Temporal Coupling и высокий риск каскадных сбоев.
  • Асинхронная (Kafka, RabbitMQ): Почтовое письмо. Высокая отказоустойчивость, развязка сервисов во времени, защита базы данных от пиков нагрузки (Traffic Smoothing / Backpressure), но требует Eventual Consistency.
  • Архитектурное правило:
    • Запросы на чтение (Queries) $\to$ чаще Синхронные.
    • Команды на изменение (Commands) и события (Events) $\to$ Асинхронные.
  • В асинхронных системах обязательны идемпотентность и обработка «отравленных сообщений» через Dead Letter Queue (DLQ).

Красные флаги (Чего говорить НЕЛЬЗЯ):

  • ❌ «Асинхронное взаимодействие всегда быстрее синхронного» (Latency одного запроса в асинхронной схеме выше из-за брокера, но выше общий throughput).
  • ❌ «В микросервисах синхронные вызовы запрещены» (Для простых запросов на чтение синхронный REST/gRPC — идеальный выбор).
  • ❌ «Если упал сервис-получатель, асинхронное сообщение теряется» (Брокеры вроде Kafka сохраняют сообщения на диск до возобновления работы сервиса).

Связанные темы: