🏗️ Розділ 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 зберігають повідомлення на диск до відновлення роботи сервісу).

Пов’язані теми: