Ответ
Помимо очередей сообщений (RabbitMQ, Kafka), для асинхронной обработки в распределённых системах применяются следующие механизмы:
-
Event Sourcing и CQRS
- Как: Состояние приложения определяется последовательностью событий. Команды генерируют события, которые сохраняются в логе. Запросы обрабатываются отдельными моделями чтения, обновляемыми асинхронно.
- Почему: Позволяет воспроизвести состояние системы в любой момент, обеспечивает надёжное аудит-логирование и естественным образом разделяет нагрузку на запись и чтение.
- Пример (концептуальный):
// Команда (синхронно) OrderService->placeOrder(command) -> emits OrderPlacedEvent // Событие (асинхронно) EmailProjector->onOrderPlaced(event) -> updates read model ReportingService->onOrderPlaced(event) -> updates dashboard
-
Асинхронные RPC/Вызовы (gRPC, HTTP с колбэками)
- Как: Сервис A отправляет запрос к сервису B и немедленно получает подтверждение (ACK), не дожидаясь полной обработки. Результат или уведомление о завершении доставляются позже через отдельный канал (webhook, очередь событий).
- Почему: Освобождает вызывающую сторону от длительного блокирования, повышая отзывчивость.
-
Шаблон «Отложенный результат» (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));
-
База данных как очередь (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 и реактивные потоки. Если задача простая и хочется минимализма — БД в качестве очереди тебя спасёт. Главное — понимать, что у каждого подхода своя, блядь, цена и свои грабли.