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

Ответ

Помимо очередей сообщений (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), композиция операций (реактивные потоки) или простота (база данных).

Ответ 18+ 🔞

А, ну так это ж про асинхронность в распределённых системах! Ёпта, тут тебе не просто очереди, тут целый зоопарк подходов, чтобы твоя система не вставала в позу «ждуна», пока где-то там что-то долго ебётся.

Смотри, кроме этих твоих RabbitMQ и Kafka, есть ещё куча приёмов, которые можно впендюрить.

1. Event Sourcing и CQRS (Ёперный театр, звучит сложно, но идея — огонь)

  • Как работает: Вместо того чтобы хранить просто «текущее состояние» (типа «баланс = 100»), ты хранишь всю историю, как в ленте соцсети. Сделал перевод — событие MoneyTransferred. Начислили проценты — событие InterestAccrued. Состояние потом просто сумма этих событий. А CQRS — это когда ты для записи (команд) и чтения (запросов) используешь вообще разные модели, которые обновляются асинхронно.
  • Нахуя это надо: Во-первых, ты можешь воспроизвести состояние системы на любую дату — аудиторам просто праздник. Во-вторых, нагрузка на запись и чтение разделяется естественным путём. Читай из оптимизированной «читаемой» модели, а пиши в свой лог событий.
  • Пример (схематично):
    // Команда (выполняется синхронно, но быстро)
    OrderService.placeOrder(command) -> генерирует событие OrderPlacedEvent
    // А дальше события летят асинхронно кто куда хочет
    EmailService->onOrderPlaced(event) // Шлёт письмо
    AnalyticsService->onOrderPlaced(event) // Считает статистику

2. Асинхронные вызовы (gRPC, HTTP с колбэками)

  • Как работает: Ты не стоишь и не ждёшь, пока удалённый сервис всё сделает. Ты ему говоришь: «Слушай, чувак, сделай вот это» — и он тебе сразу: «Окей, принял в работу, иди пока погуляй». А когда он всё сделает, он тебе результат или просто пинок пришлёт на заранее указанный тобой webhook или в очередь. Доверия, ебать, ноль, поэтому нужны идемпотентность и таймауты.
  • Нахуя это надо: Чтобы твой API не тупил минуту, пока на другом конце света PDF-ку генерируют. Отправил задачу — и сразу освободился.

3. Шаблон «Отложенный результат» (Future/Promise) и реактивные потоки

  • Как работает: Ты вызываешь метод, а он тебе возвращает не результат, а бумажку (Future, Promise), типа «расписка, что результат будет». С этой бумажкой ты потом можешь делать что хочешь: ждать её, прицепить к ней колбэк («когда будет готово — сделай вот это»), или склеить несколько таких бумажек в одну операцию. Реактивные потоки (Reactor, RxJava) — это вообще мощь, они позволяют строить целые конвейеры из асинхронных операций без этого ада из вложенных колбэков.
  • Нахуя это надо: Удобство, чувак! Код выглядит почти как синхронный, но работает асинхронно. Никакого «callback hell», где в конце концов сам чёрт ногу сломит.
  • Пример (Java):

    // Запускаем два асинхронных запроса ПАРАЛЛЕЛЬНО
    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("Всё, готово: " + result)); // И потом напечатаем

4. База данных как очередь (SELECT ... FOR UPDATE SKIP LOCKED)

  • Как работает: Иногда ставить целый Kafka для простых фоновых задач — это как из пушки по воробьям. Берёшь обычную таблицу в PostgreSQL или другой адекватной БД. Пишешь туда задачи. Потом несколько воркеров одновременно делают запрос типа: «Дай мне одну незаблокированную задачу». БД сама пессимистично блокирует строку для одного воркера, а остальные её просто не видят (SKIP LOCKED). Обработал — поменял статус или удалил.
  • Нахуя это надо: Простота, ебать! Не надо поднимать и мониторить отдельную очередь. Всё держится на транзакционности БД. Правда, масштабируется хуже, чем специализированные очереди, но для многих случаев — то, что надо.

Итог, бля: Выбор зависит от того, что тебе важнее. Надёжная доставка и полный аудит — Event Sourcing. Нужно просто не блокировать клиента — асинхронный RPC. Хочешь удобно работать с кучей асинхронных операций в коде — бери Future и реактивные потоки. Если задача простая и хочется минимализма — БД в качестве очереди тебя спасёт. Главное — понимать, что у каждого подхода своя, блядь, цена и свои грабли.