Если для вас эти слова ничего не говорят, то возможно после этого поста вы сможете нехило улучшить и упростить ваше приложение. Зайдем через проблематику.
Буквально в каждом не тривиальном приложении, уже на старте, появляется задача выполнять какие-то задачи асинхронно. Самое простое - отправка писем после регистрации. Почему этого нельзя делать там же где делается регистрация? Отправка письма это почти наверняка взаимодействие с внешним сервисом по api или smtp. А это сеть со всеми вытекающими от задержек до ошибок. Ваши пользователи будут регулярно получать тайматы и ошибку 500. Сразу встает вопрос транзакционности и гарантий. Что в таких ситуациях делать?
Первое, что приходит на ум, это выполнять отправку в памяти, но отдельно. Такие механизмы иногда предоставляются самим фреймворком (async в spring boot), либо это можно организовать самому (nodejs, go). Для не критичных вещей, когда нет нагрузок и особых рисков, можно и так, но этот подход быстро упирается в потолок. Что не так?
• Нет гарантий выполнения. Процесс упал - задача исчезла. Получаем потерянные письма, вебхуки, уведомления. Такое будет происходит и при деплоях и автоматических рестартах и ошибках.
• Нет контроля нагрузки. Если растет потребление ресурсов, то такой подход не маштабируется и начинает мешать основному приложению.
• Нет прозрачности. Вы не видите: что сейчас выполняется, что упало, что зависло, что выполняется слишком долго.
Обычно после этого варианта, сразу переходят к очередям и стримам. А давайте воткнем redis/kafka/rabbitmq и на каком-нибудь задорном языке понапишем отдельных приложений, которые будут хватать эти сообщения и делать всякие задачи от отправки емейлов, до каких-то тяжелых вычислений. Сразу можем добавлять в резюме строчку про построение микросервисной архитектуры.
Да такой подход и разделение в какой-то момент может происходить. В первую очередь при росте числа разработчиков и необходимости разделять их в разные команды со своими приложениями и релизными циклами. Но это происходит далеко не всегда (на хекслете как было несколько разработчиков 12 лет назад, так и сейчас несколько разработчиков) либо происходит тогда, когда у компании уже много ресурсов и со старта проекта прошел не один год.
Короче, по середине между двумя этими подходами и затесались background jobs. Это механизм, позволяющий выполнять любые задачи асинхронно в отдельном процессе с персистентностью (а значит гарантиями) и возможностью масштабировать. То есть их можно запускать хоть на другом сервере и в любом количестве экземпляров. Чем это отличается от работы через очереди спросите вы? Отличается и вот в чем. Для начала пример в python (celery). Определение джобы:
# tasks.py
from celery import shared_task
@shared_task
def send_welcome_email(user_id, email):
send_email(email)
Использование:
# registration.py
from tasks import send_welcome_email
def register_user(email):
user = create_user(email)
send_welcome_email.delay(user.id, user.email)
return user
Джобы это код нашего приложения, а не отдельное приложение. На практике мы просто стартуем наше приложение в режиме джоб, когда оно не обрабатывает http запросы, а просто крутится в фоне. Планирование джобы, это отправка сообщений в космос для каких-то консьюмеров, о которых мы ничего не знаем. Джоба это запуск конкретного кода под конкретную задачу, что очень сильно отличает их от очередей. Джобу можно воспринимать просто как функцию, которая, кстати, в тестах часто вызывается вообще синхронно, значительно упрощая инфраструктуру и работу монолита. Данные при этом часто хранятся в redis, а сейчас вообще идет тенденция, чтобы по дефолту хранить джобы в той же базе что и само приложение (что значительно упрощает старт и есть адаптеры). Ну и все это добро поддерживает ретраи, приоритеты и другие механизмы типичные для очередей.
Реализации джоб есть в каждом языке. Какой либой пользуетесь вы?
Ссылки: Телеграм | Youtube | VK