Асинхронные задачи в Django без Celery
В этом посте я расскажу вам, как реализовать асинхронные задачи Django без Celery. Прежде всего я определю, что я имею в виду под термином «асинхронная задача».
Что такое асинхронные задачи Django?
Предположим, что вы хотите выполнить долгосрочную задачу в своем веб-приложении Django, но хотите дать немедленный ответ пользователю, не дожидаясь его завершения. Задачей может быть: отправка электронного письма, создание отчета, отправка запроса во внешнюю веб-службу и т.д. Ответ, предоставляемый пользователю, обычно является подтверждением того, что задача запущена.
Каждая задача, выполнение которой может занять некоторое время, не должна блокировать цикл запрос-ответ между пользователем и вашим приложением.
Если вы искали в Google по щапросу «асинхронных задач Django», вероятно, вам будут попадаться преложения по работе с Celery как способом реализации асинхронных задач с помощью Django. Пожалуйста, не поймите меня неправильно: в Celery нет ничего плохого. Многие успешные проекты с используют Celery. Я также использовал Celery в нескольких проектах, и это хорошо работает. Что меня раздражает, так это дополнительные усилия.
Celery - еще один сервис для настройки, запуска и обслуживания.
Введение в uWSGI
Как я писал в своем другом посте, Django - NGINX: разверните свой проект Django на производственном сервере, мне нравится использовать сервер приложений uWSGI. Что ж, получается, что uWSGI предоставляет полнофункциональную, готовую к работе систему для реализации асинхронных задач. Они называют это Spooler uWSGI. Ссылаясь на документацию uWSGI:
Spooler - это менеджер очередей, встроенный в uWSGI, который работает как система печати / почты.
Вы можете поставить в очередь массовую отправку электронных писем, обработку изображений, кодирование видео и т.д. И позволить Spooler выполнять тяжелую работу в фоновом режиме, пока ваши пользователи получают свои запросы от обычных работников.
Spooler работает, определяя каталог, в который будут записываться «файлы спула», каждый раз, когда спулер находит файл в своем каталоге, он анализирует его и запускает определенную функцию.
Spooler uWSGI очень прост в настройке!
Установка пакета uwsgidecorators
Для взаимодействия с uWSGI Spooler из кода Python действительно удобно установить пакет uwsgidecorators. К сожалению, в PyPI нет последней версии uwsgidecorators, но вы можете установить ее с помощью apt:
sudo apt install python3-uwsgidecorators
Если вы используете virtualenv для вашего проекта Django (как вы должны), вы можете связать модуль uwsgidecorator с вашим virtualenv:
ln -s /usr/lib/python3/dist-packages/uwsgidecorators.py /path/to/your/virtualenv/lib/python3.6/site-packages
Создаем каталог для «файлов спула»
Прежде всего, вы должны создать каталог, в котором uWSGI будет сохранять «файлы спула», используемые Spooler:
mkdir -p /home/ubuntu/tasks sudo chown www-data.www-data /home/ubuntu/tasks
Пожалуйста, убедитесь, что каталог доступен для записи пользователю uWSGI, то есть www-data в примере.
Создайте модуль задач в вашем приложении Django
Предполагая, что у вас есть приложение Django с именем app1, вы должны создать модуль с именем tasks.py в каталоге приложения. Содержимое модуля должно быть примерно таким:
import logging try: from uwsgidecorators import spool except: def spool(func): def func_wrapper(**arguments): return func(arguments) return func_wrapper import django django.setup() from .models import Model1 logger = logging.getLogger(__name__) @spool def long_running_task(arguments): id = arguments['id'] obj1 = Model1.objects.get(pk=id) obj1.long_running_model_method()
Конструкция try / catch позволит вам протестировать код, даже если он не запущен на сервере приложений uWSGI, например, при локальном запуске с использованием ./manage.py runserver. В этом случае задача будет выполняться синхронно, так как это был обычный вызов функции python.
Функция long_running_task - это задача, которую вы будете вызывать с помощью спулера uWSGI. Как вы видите, вы должны украсить его с помощью декоратора @spool, а параметры для задачи передаются в словаре с именем arguments.
В этом примере я использую Django ORM, чтобы получить экземпляр модели и вызвать метод модели, выполнение которого займет много времени.
Вызовите асинхронную задачу из вашего представления Django
Здесь вы можете найти, как вызвать асинхронную задачу из примера:
from django.shortcuts import redirect from django.contrib import messages from .tasks import long_running_task def async_view(request, id): long_running_task(id=id) messages.add_message(request, messages.SUCCESS, 'Task started correctly') return redirect('some_other_view')
Здесь я вызываю задачу, затем использую структуру сообщений Django, чтобы показать пользователю сообщение с подтверждением на следующем просмотре страницы, и, наконец, перенаправляю пользователя в другое представление.
Настройте uWSGI для использования спулера
Чтобы настроить спулер uWSGI, вы должны отредактировать файл конфигурации uWSGI, который я представил в посте Django - NGINX: разверните ваш проект Django на рабочем сервере. Файл находится здесь: /etc/uwsgi/apps-enabled/django.ini. Содержимое файла должно быть примерно таким:
[uwsgi] chdir = /home/ubuntu/django_project # customize with your django installation directory env = DJANGO_SETTINGS_MODULE=project.settings.production # customize with your settings module wsgi-file = project/wsgi.py # customize with the relative path to your wsgi.py file workers = 1 spooler-chdir = /home/ubuntu/django_project # customize with your django installation directory spooler = /home/ubuntu/tasks/ import = app1.tasks
Перезапустите uWSGI с помощью:
service uwsgi restart
Вы найдете журналы uWSGI в /var/log/uwsgi/apps/django.log . Поэтому вы можете проверить их, чтобы увидеть, правильно ли запущен процесс Python или есть проблемы.
В файле журнала вы также найдете сообщения о Spooler, что-то вроде этого:
Mon Nov 18 14:50:45 2019 - [spooler /home/ubuntu/tasks pid: 8646] managing request uwsgi_spoolfile_on_www.example.com_8647_3_1383635828_1574067630_419736 ...
Вывод
В этом посте я показал, как настроить асинхронные задачи Django с помощью спулера uWSGI. Это простой и удобный способ выполнения длительных задач вне цикла запрос-ответ. Особенно, если вы уже используете сервер приложений uWSGI.
Перевод статьи: Django asynchronous tasks without Celery