Про воркеры простыми словами
На работе мне понадобилось реализовать воркер. Описываю, как сам эту тему разобрал. Я не разработчик, пишу утилиты на Python для QA.
Воркер — это сервис, который ждёт событие-триггер и по нему выполняет некий коллбэк. В отличие от фоновых задач в вашем сервисе, воркер живёт в отдельном контейнере.
Пример
Сборщик мусора в БД: пройтись раз в час, например, и удалить старые записи. Или, как у меня задача на работе: обработать xlsx-файл, который передали в ручку.
Зачем
Чтобы сделать работу приложения асинхронной. Представим задачу, которую можно обработать дольше, чем за 10 секунд. Клиент на вашем сайте не будет сидеть и смотреть в экран эти 10 секунд. Он перезагрузит страницу, сессия прервётся, и задача не выполнится. Или веб-сервер вернёт клиенту таймаут. В описанном сценарии обработка запроса — синхронная процедура. Она плохо подходит для быстрых веб-сервисов. А вот асинхронная обработка: кинули запрос, получили ответ 200 OK и пошли чилить, пока задача исполняется — это то, что нужно. Воркер как раз для этого.
Коллбэк
Коллбэком воркера может быть любая нужная функция: отправить имейл, прочитать содержимое, залить файл во временное хранилище и т. д.
Триггеры
Триггерами для воркера могут быть:
крон
таймер
событие
Очереди
Воркеры по событию обычно подписаны на очередь. В моём случае это как раз Redis Queue (библиотека rq, например https://python-rq.org/ ). Запрос в ручку получает 200 OK. Мой сервис создаёт запись в БД типа «задача id такой-то, статус processing» и публикует событие в очереди. Воркер забирает событие, чтобы другие воркеры не могли задачу задублировать, и пробует выполнить свой коллбэк. Если всё ок, воркер пишет в БД данные по выполненной задаче и подтверждает в очереди прочтение события. Иначе воркер может ретраить, может завалить задачу и вернуть её в очередь, а может и сам упасть.
Воркер-пул
Воркеров может быть несколько. Они могут выполнять как несколько разных задач, так и одну вместе. Увеличение числа воркеров требует оркестрации и иногда для этого также выделяет контейнер с оркестратором. Воркеры могут передавать задачи друг другу. Могут конкурировать за задачи, если очередь организовать неправильно. А могут вообще читать разные потоки и быть никак не связаны друг с другом.
Накладные расходы
Чем сложнее слой с воркером, тем больше необходимость следить за их хелсчеками. В отличие от вашего сервиса, воркер может тихо упасть. Ваш сервис отдаёт 200, а по факту задачи не отрабатывают. Так что воркеры накладывают дополнительные накладные расходы: связанность, обработка ошибок, логирование, алерты, ретраи, рестарты подов и т. д.
Образ
Воркер собирается из того же образа, что и ваше приложение, но у него отдельный энтри-поинт. Вместо запуска через main.py у, например, worker.py, есть строчка вида:
def main():
... # Какая-то логика по инициализации воркера и очереди
if name == '__main__':
main() # Если запускают этот модуль напрямую, выполни команду main()Из-за этого кода модуль можно вызвать напрямую python -m app.worker. В main(), как правило, скрыта логика какого-то while-true цикла и шатдауна на случай завершения работы воркера.