Какие существуют механизмы асинхронных запросов, кроме очередей?

«Какие существуют механизмы асинхронных запросов, кроме очередей?» — вопрос из категории Архитектура, который задают на 28% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Помимо очередей сообщений (RabbitMQ, Kafka), для асинхронной обработки в распределённых системах применяются следующие механизмы:

  1. Event Sourcing и CQRS

    • Как: Состояние приложения определяется последовательностью событий. Команды генерируют события, которые сохраняются в логе. Запросы обрабатываются отдельными моделями чтения, обновляемыми асинхронно.
    • Почему: Позволяет воспроизвести состояние системы в любой момент, обеспечивает надёжное аудит-логирование и естественным образом разделяет нагрузку на запись и чтение.
    • Пример (концептуальный):
      // Команда (синхронно)
      OrderService->placeOrder(command) -> emits OrderPlacedEvent
      // Событие (асинхронно)
      EmailProjector->onOrderPlaced(event) -> updates read model
      ReportingService->onOrderPlaced(event) -> updates dashboard
  2. Асинхронные RPC/Вызовы (gRPC, HTTP с колбэками)

    • Как: Сервис A отправляет запрос к сервису B и немедленно получает подтверждение (ACK), не дожидаясь полной обработки. Результат или уведомление о завершении доставляются позже через отдельный канал (webhook, очередь событий).
    • Почему: Освобождает вызывающую сторону от длительного блокирования, повышая отзывчивость.
  3. Шаблон «Отложенный результат» (Future/Promise) и реактивные потоки

    • Как: Вызов возвращает объект-обещание (Future, CompletableFuture в Java, Promise в JS), представляющий результат, который будет вычислен позже. Реактивные библиотеки (Project Reactor, RxJava) позволяют строить цепочки асинхронных операций.
    • Почему: Удобная абстракция для работы с асинхронностью в коде, комбинирование операций без "callback hell".
    • Пример (Java с CompletableFuture):

      CompletableFuture<User> userFuture = userService.getUserAsync(userId);
      CompletableFuture<Order> orderFuture = orderService.getOrderAsync(orderId);
      
      userFuture.thenCombine(orderFuture, (user, order) -> {
          // Асинхронная обработка, когда оба результата готовы
          return processOrder(user, order);
      }).thenAccept(result -> System.out.println("Done: " + result));
  4. База данных как очередь (SELECT ... FOR UPDATE SKIP LOCKED)

    • Как: Задачи хранятся в таблице БД со статусом. Несколько воркеров конкурентно выбирают задачи с помощью запросов, использующих пессимистичные блокировки с пропуском заблокированных строк.
    • Почему: Использует транзакционность БД для гарантированной доставки, не требует дополнительных инфраструктурных компонентов. Подходит для невысоких нагрузок или как fallback.

Ключевой выбор зависит от требований: гарантии доставки (очереди, Event Sourcing), низкие задержки (асинхронные RPC), композиция операций (реактивные потоки) или простота (база данных).