Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Django

Использование Celery с Django для обработки фоновых задач

by
Difficulty:AdvancedLength:LongLanguages:

Russian (Pусский) translation by Marat Amerov (you can also view the original English article)

Веб-приложения обычно начинаются разрабатываться просто, но могут стать довольно сложными, и большинство из них быстро перерастает область ответственности только за HTTP-запросы.

Когда это происходит, нужно провести различие между тем, что должно выполняться мгновенно (обычно в жизненном цикле HTTP-запроса), и что может произойти в конечном итоге, после запроса. Почему это необходимо? Ну, потому что, когда ваше приложение становится перегруженным трафиком, решения, подобные этому, начинают иметь значение.

Операции в веб-приложении могут быть классифицированы как критические или request-time операции и фоновые задачи, которые происходят за пределами цикла запрос-ответ. Они сопоставляются с описанными выше:

  • Должно произойти мгновенно: request-time операция
  • Должно произойти после ответа: фоновые задачи

Request-time операции могут выполняться в течение одного цикла запрос/ответ, не беспокоясь о том, что операция может получить тайм-аут или что у пользователя может быть плохое соединение. Общие примеры включают CRUD (создание, чтение, обновление, удаление) операций с базой данных и управление пользователями (процедуры входа/выхода).

Фоновые задачи отличаются, поскольку они обычно довольно трудоемки и склонны к сбою, главным образом из-за внешних зависимостей. Некоторые распространенные сценарии среди сложных веб-приложений включают:

  • Отправка уведомлений о подтверждении или рассылка сообщений
  • Ежедневное сканирование и скрапинг некоторой информации из разных источников и сохранение их
  • Анализ данных
  • Удаление ненужных ресурсов
  • экспорт документов/фотографии в различных форматах

Фоновые задачи являются основным направлением данного руководства. Наиболее распространенным шаблоном программирования, используемым для этого сценария, является Producer-Consumer архитектура.

Проще говоря, эта архитектура может быть описана следующим образом:

  • Producers создают данные или задачи.
  • Задачи помещаются в очередь, которая называется очередью задач.
  • Consumers несут ответственность за потребление данных или выполнение задач.

Обычно сonsumers загружают задачи из очереди в режиме first-out (FIFO)  или в соответствии с их приоритетами. Потребителей также называют воркерами, и это термин, который мы будем использовать повсюду, поскольку он согласуется с терминологией, используемой обсуждаемыми технологиями.

Какие задачи можно обрабатывать в фоновом режиме? Задачи, которые:

  • Не являются существенными для основного функционала веб-приложения
  • Не могут выполняться в цикле запросо/ответ, поскольку они медленны (интенсивность ввода-вывода и.т.д.)
  • Зависят от внешних ресурсов, которые могут быть недоступны или не будут вести себя так, как ожидалось
  • Возможно, потребуют повторить попытку хотя бы один раз
  • Должны выполняться по графику

Celery является де-факто выбором для обработки фоновых задач в экосистеме Python/Django. Он имеет простой и понятный API, и он прекрасно сочетается с Django. Он поддерживает различные технологии для очереди задач и различные парадигмы для воркеров.

В этом уроке мы собираемся создать игровое веб-приложение Django (работающее с реальными сценариями), которое использует обработку фоновых задач.

Настройка

Предполагая, что вы уже знакомы с менеджером пакетов Python и виртуальными окружениями, давайте установим Django:

Я решил создать еще одно приложение для ведения блога. Приложение будет простым. Пользователь может просто создать учетную запись и без особых проблем может создать пост и опубликовать его на платформе.

Настройка Django проекта quick_publisher:

Давайте начнем разработку приложения:

При запуске нового Django проекта  мне нравится создавать main приложение, которое содержит, помимо прочего, кастомную модель пользователя. Чаще всего Я сталкиваюсь с ограничениями модели User по умолчанию. Наличие пользовательской модели User дает нам гибкость.

Обязательно ознакомьтесь с документацией Django, если вы не знакомы с тем, как работают пользовательские модели.

Теперь нам нужно указать Django, использовать эту модель пользователя вместо стандартной. Добавьте эту строку в файл quick_publisher/settings.py:

Нам также необходимо добавить main приложение в список INSTALLED_APPS в файле quick_publisher/settings.py. Теперь мы можем создавать миграции, применять их и создавать суперпользователя, чтобы иметь возможность входа в панель администратора Django:

Давайте теперь создадим отдельное Django приложение, которое отвечает за посты:

Давайте определим простую модель Post в publisher/models.py:

Привязка модели Post с  администратором Django выполняется в файле publisher/admin.py следующим образом:

Наконец, давайте подключим приложение publisher к нашему проекту, добавив его в список INSTALLED_APPS.

Теперь мы можем запустить сервер и перейти к http:// localhost:8000/admin/ и создать наши первые посты, чтобы у нас было то, с чем можно взаимодействовать:

Надеюсь, что Вы сделали задание и создали посты.

Давайте двигаться дальше. Следующий очевидный шаг - создать способ просмотра опубликованных постов.

Давайте свяжем наше новое представление с URL-адресом в: quick_publisher/urls.py

Наконец, давайте создадим шаблон, который отображает пост: publisher/templates/post.html

Теперь мы можем перейти к http://localhost:8000/the-slug-of-the-post-you-created/ в браузере. Это не совсем чудо веб-дизайна, но создание красивых страниц постов выходит за рамки этого урока.

Отправка писем с подтверждением

Вот классический сценарий:

  • Вы создаете учетную запись.
  • Вы указываете адрес электронной почты, который будет идентифицирован, как уникальный на платформе.
  • Платформа проверяет, что вы действительно являетесь владельцем адреса электронной почты, отправив электронное письмо с ссылкой на подтверждение.
  • Пока Вы не выполните проверку, вы не сможете (полностью) использовать платформу.

Давайте добавим флаг is_verified и verify_uid в модель User:

Давайте воспользуемся этим случаем, чтобы добавить возможность администрирования модели User:

Давайте сделаем изменения в базе данных:

Теперь нам нужно написать код, который отправляет электронное письмо при создании пользователя. В таких случаях используются сигналы Django, и это хороший момент, чтобы коснуться этой темы.

Сигналы срабатывают до/после определенных событий в приложении. Мы можем определить коллбеки, которые вызываются автоматически при срабатывании сигналов. Чтобы сделать триггер для коллбека, мы должны сначала подключить его к сигналу.

Мы собираемся создать коллбек, который будет вызван после создания пользователя. Мы добавим этот код после определения модели User в: main/models.py

Здесь мы определили функцию user_post_save и связали ее с сигналом post_save (который запускается после сохранения модели), отправленным моделью User.

Django не просто отправляет электронные письма самостоятельно; Его необходимо связать с почтовой службой. Для простоты вы можете добавить свои учетные данные Gmail в quick_publisher/settings.py или добавить своего любимого поставщика электронной почты.

Вот как выглядит конфигурация Gmail:

Чтобы проверить все, зайдите в панель администратора и создайте нового пользователя с действительным адресом электронной почты, который вы можете быстро проверить. Если все пойдет хорошо, вы получите электронное письмо со ссылкой для подтверждения. Процедура проверки еще не готова.

Вот как выполнить проверку учетной записи:

Привязка представления в: quick_publisher/urls.py

Кроме того, не забудьте создать файл home.html в каталоге main/templates/home.html. Это будет рендерится c представлением home.

Попробуйте запустить весь сценарий снова. Если все будет хорошо, вы получите электронное письмо с действительным URL-адресом подтверждения. Если пройдёте по URL-адресу, а затем проверите раздел администратора, Вы увидите, как была проверена учетная запись.

Асинхронная отправка писем

У нас есть проблема в текущей реализации. Возможно, вы заметили, что создание пользователя немного медленное. Это происходит потому, что Django отправляет письмо с проверкой во время запроса.

Вот как это работает: мы отправляем пользовательские данные в приложение Django. Приложение создает модель User, а затем создает соединение с Gmail (или другим выбранным вами сервисом). Django ждет ответа, и только после этого он возвращает ответ на наш браузер.

Вот где проявляется необходимость в Celery. Во первых, убедитесь, что он установлен:

Теперь нам нужно создать Celery приложение  в нашем Django приложении:

Celery - это очередь задач. Он получает задания из нашего приложения Django и запускает их в фоновом режиме. Celery должен быть сопряжен с другими сервисами, которые действуют в качестве брокеров.

Брокеры промежуточно отправляют сообщения между веб-приложением и Celery. В этом уроке мы будем использовать Redis. Redis прост в установке, и мы можем легко начать с него без особых проблем.

Вы можете установить Redis, следуя инструкциям на странице Redis Quick Start. Вам нужно будет установить библиотеку Redis Python, выполнив pip install redis и комплект, необходимый для использования Redis и Celery: pip install celery [redis].

Запустите Redis сервер в отдельной консоли следующим образом: $ redis-server

Давайте добавим связанные с Celery/Redis конфиги в quick_publisher/settings.py:

Прежде чем что-либо может быть запущено в Celery, оно должно быть декларировано как задача.

Вот как это сделать:

Мы здесь сделали следующее: мы переместили функцию отправки по почты в другой файл под названием tasks.py.

Несколько примечаний:

  • Имя файла имеет значение. Celery проходит через все приложения в INSTALLED_APPS и регистрирует задачи в файлах tasks.py.
  • Обратите внимание, как мы украсили декорировали send_verification_email с помощью @app.task. Это указывает Celery, что это задача, которая будет выполняться в очереди задач.
  • Обратите внимание, что мы ожидаем аргумент user_id, а не объект User. Это связано с тем, что при отправке задач на Celery может возникнуть проблема с сериализацией сложных объектов. Лучше использовать примитивные типы.

Возвращаясь к main/models.py, код сигнала преобразуется в:

Обратите внимание, как мы вызываем метод .delay объекта задачи. Это означает, что мы отправляем задание на Celery, и мы не ожидаем результата. Если бы мы использовали send_verification_email (instance.pk), мы все равно отправили бы его в Celery и ждали завершения задачи, чего мы не хотим.

Прежде чем Вы начнете создавать нового пользователя, есть есть загвоздка. Celery - это сервис, и нам нужно его запустить. Откройте новую консоль, убедитесь, что вы активируете соответствующий virtualenv, и перейдите в каталог проекта.

Эта команда запустит четыре процесса воркеров Celery. Да, теперь Вы можете, наконец, пойти и создать другого пользователя. Обратите внимание на то, что нет задержки, следите за логами в консоли Celery и посмотрите, правильно ли выполняются задачи. Это должно выглядеть примерно так:

Периодические задачи с Celery

Вот еще один общий сценарий. Большинство зрелых веб-приложений отправляют своим пользователям электронные письма c периодичностью. Некоторые распространенные примеры электронных писем с периодической отправкой:

  • Ежемесячные отчеты
  • Уведомления о деятельности (лайки, запросы в друзья и т. д.)
  • Напоминания для выполнения определенных действий («Не забудьте активировать свою учетную запись»)

Вот что мы будем делать в нашем приложении. Мы посчитаем, сколько раз просматривался каждый пост и будем отправлять ежедневный отчет автору. Каждый день мы собираемся пройтись через всех пользователей, загружать их посты и отправлять электронное письмо с таблицей, содержащей посты и количество просмотров.

Давайте изменим модель Post, чтобы мы могли выполнять подсчет просмотров.

Как всегда, когда мы меняем модель, нам нужно выполнить миграцию базы данных:

Давайте также изменим Django представление view_post  для подсчета просмотров:

Было бы полезно отобразить view_count в шаблоне. Добавьте <p>Viewed {{ post.view_count }} times</p> где-нибудь в файле publisher/templates/post.html. Сделайте несколько просмотров поста и посмотрите, как увеличивается счетчик.

Давайте создадим задачу Celery. Поскольку речь идет о постах, я собираюсь разместить её в publisher/​​tasks.py:

Каждый раз, когда Вы вносите изменения в задачи Celery, не забудьте перезапустить процесс Celery. Celery должен обнаружить и перезагрузить задачи. Перед созданием задачи выполняющейся периодический мы должны проверить это в Django shell, чтобы убедиться, что все работает как ожидается:

Надеюсь, что Вы получили правильный отчёт в своем письме.

Давайте теперь создадим периодическую задачу. Откройте quick_publisher/celery.py и зарегистрируйте задания:

До сих пор мы создавали расписание, которое запускало задачу publisher.tasks.send_view_count_report каждую минуту, как показано в нотации crontab(). Вы также можете указать различные расписания Celery Crontab.

Откройте другую консоль, активируйте соответствующее окружение и запустите службу Celery Beat.

Работа службы Beat заключается в том, чтобы задавать задачи в Celery в соответствии с расписанием. Учтите, что расписание выполняет задачу send_view_count_report каждую минуту в соответствии с настройкой. Это удобно для тестирования, но не рекомендуется для реального веб-приложения.

Сделать задачи более надежными

Задачи часто используются для выполнения ненадежных операций, операций, зависящих от внешних ресурсов, или задачи, которые могут легко падать по различным причинам. Вот инструкция, чтобы сделать их более надежными:

  • Сделать задачи идемпотентными. Идемпотентная задача - это задача, которая, не изменяет состояние системы, если она остановлена ​​на полпути. Задача либо полностью вносит изменения в систему, либо нет.
  • Повторение задачи. Если задача падает, рекомендуется попробовать выполнять ее снова и снова, пока она не будет выполнена успешно. Вы можете сделать это c помощью Celery Retry. Еще одна интересная вещь, на которую стоит обратить внимание, - это алгоритм экспоненциального отказа. Это может пригодиться, когда вы думаете об уменьшении ненужной нагрузки на сервер, возникающей из за повторных задач.

Заключение

Надеюсь, что это была интересная статья для вас и хорошее введение в использование Celery с Django.

Вот несколько выводов, которые мы можем сделать:

  • Хорошей практикой является сохранение ненадежных и трудоемких задач за пределами запроса.
  • Задачи, которые долго выполняются, должны выполняться в фоновом режиме воркерами (или другими способами).
  • Фоновые задачи могут использоваться для различных задач, которые не критичны для базового функционирования приложения.
  • Celery также может выполнять периодические задания с использованием службы celery beat.
  • Задачи могут быть более надежными, если они сделаны идемпотентными и повторены (возможно, с использованием алгоритма экспоненциального отказа).
Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.