Django — проектирование и практика модуля задач на время

Django

задний план

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

анализ спроса

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

  1. Осуществление «операции»
  2. Настроить как запланированную задачу
  3. Настраиваемая временная стратегия
  4. хороший пользовательский опыт

Среди них шаг 1 не имеет отношения к этой статье, для реализации задач на время, в предыдущем разделеОчередь асинхронных задач CeleryСуществует краткое упоминание о том, что сельдерей также поддерживает запланированные задачи.

Стратегия задач Celery по времени настраивается в коде и записывается локально при запуске celery.shelveфайлы, не способствующие управлению.

Поэтому модуль расширения также упоминается в документации по celery.django-celery-beat, этот модуль записывает конфигурацию временной задачи в базу данных, настроенную Django.При запуске программы вы можете передатьadminОн управляется в фоновом режиме, и конфигурацию запланированного задания можно изменить непосредственно через ORM, без изменения кода и последующего перезапуска сельдерея, что соответствует нашим ожиданиям.

Конечно, есть много других библиотек, которые также можно реализовать, потому что мы использовали celery для выполнения асинхронных задач, поэтому в этой статье по-прежнему используетсяdjango-celery-beatРешать проблему.

Запланированные задачи Celery используют что-то вродеcrontabСледовательно, с точки зрения пользовательского опыта следует учитывать стоимость обучения обычных пользователей, и могут быть предоставлены некоторые общие конфигурации, такие как выполнение задач в 1 час каждый день в будние дни; также следует учитывать более позднюю расширяемость и ввод коробки могут быть предоставлены для облегчения настройки.

Дизайн и реализация

Основное использование

Стратегия выбора времени (CrontabSchedule)

CrontabScheduleПоддерживает crontab-подобный синтаксис, а также имеет 5 полей конфигурации, а именно:

  • Минута
  • Время
  • день недели
  • день месяца
  • месяц года

Разделяйте каждое поле конфигурации пробелом.

Общий синтаксис для каждого поля конфигурации:

  • *: все значения в диапазоне
  • M-N: значение между M и N
  • M-N/Xили*/X: каждые X минут, каждые X дней и т. д.
  • A,B,...,Z: значение перечисления

Например: Выполняется в 1:00 каждый рабочий день:0 1 1-5 * *

Код для создания временной стратегии выглядит следующим образом:

from django_celery_beat.models import CrontabSchedule, PeriodicTask
>>> schedule, _ = CrontabSchedule.objects.get_or_create(
...     minute='30',
...     hour='*',
...     day_of_week='*',
...     day_of_month='*',
...     month_of_year='*',
... )

задача на время

Задачи синхронизации могут зависеть от различных стратегий синхронизации, таких как crontab, interval и т. д., указанных при создании.scheduleВот и все. В качестве примера возьмем запланированную задачу crontab:

>>> import json
>>> from datetime import datetime, timedelta

>>> PeriodicTask.objects.create(
...     crontab=schedule,                  # we created this above.
...     name='Importing contacts',          # simply describes this periodic task.
...     task='proj.tasks.import_contacts',  # name of task.
...     args=json.dumps(['arg1', 'arg2']),
...     kwargs=json.dumps({
...        'be_careful': True,
...     }),
...     expires=datetime.utcnow() + timedelta(seconds=30)
... )

вnameЭто имя задачи на время, и каждое имя задачи должно быть уникальным;taskДля задач сельдерея, которые необходимо выполнить. Вместе с планировщиком политики синхронизации эти три атрибута являются необходимыми атрибутами для задачи синхронизации.

Существуют и другие конфигурации для запланированных задач, напримерargs/kwargsВходной параметр, соответствующий задаче celery;expiresУстановите время истечения запланированного задания.

Конфигурация Джанго

Самая базовая конфигурация должна быть только вINSTALLED_APPSДобавьте ссылку на него и установите планировщик задач по времени:

settings.py

INSTALLED_APPS = [
    ...
    'django_celery_beat'
]

# 配置 celery 定时任务使用的调度器
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'

проблема с часовым поясом

в настоящее время используетdjango-celery-beatВ процессе я столкнулся с двумя проблемами, связанными с часовыми поясами:

  1. Созданная запланированная задача имеет 8-часовую разницу во времени между фактическим временем запуска и настроенным временем.

решение:

8 часов, очевидно, из-за разных часовых поясов, иdjango-celery-beatКажется, всегда есть проблема с обработкой часовых поясов (пожалуйста, сообщите, если нет).

Исправлятьsettings.pyКонфигурация часового пояса в:

settings.py

# 设置 Django 大部分应用通用的时区
TIME_ZONE = 'Asia/Shanghai'
# 关闭 UTC
USE_TZ = False
CELERY_ENABLE_UTC = False
# 设置 django-celery-beat 真正使用的时区
CELERY_TIMEZONE = TIME_ZONE
# 使用 timezone naive 模式
DJANGO_CELERY_BEAT_TZ_AWARE = False

Чтобы узнать о разнице между режимами без учета часового пояса и с учетом часового пояса, обратитесь к статье:Подробная информация о часовом поясе Джанго

Проще говоря, наивный режим не хранит информацию о часовом поясе, а только время после преобразования часового пояса; напротив, осведомленный режим сохраняет информацию о времени UTC и часовом поясе UTC.

согласно сДокументация, после изменения часового пояса необходимоlast_run_atсбросить наNone:

python manage.py shell
>>> from django_celery_beat.models import PeriodicTask
>>> PeriodicTask.objects.all().update(last_run_at=None)

После модификации перезапуститьcelery beat.

PS: Даже после этой настройки я все равно сталкивался с проблемой непрерывного выполнения задач, и она не повторялась после многократного перезапуска celery, так что с этой конфигурацией могут быть проблемы.

  1. в базе данных,CrontabScheduleизtimezoneконфигурация всегдаUTC

решение:

ПроверятьCrontabScheduleИсходный код модели находится в базе данныхtimezoneСвойства поля:

class CrontabSchedule(models.Model):
    ...
    timezone = timezone_field.TimeZoneField(
        default='UTC',
        verbose_name=_('Cron Timezone'),
        help_text=_(
            'Timezone to Run the Cron Schedule on.  Default is UTC.'),
    )

Поскольку мы создаемCrontabScheduleэкземпляр не указанtimezone, поэтому при создании задачи добавьте конфигурацию этого поля:

from django_celery_beat.models import CrontabSchedule
>>> schedule, _ = CrontabSchedule.objects.get_or_create(
...     minute='30',
...     hour='*',
...     day_of_week='*',
...     day_of_month='*',
...     month_of_year='*',
...     timezone='Asia/Shanghai'
... )

*Дизайн бизнес-интерфейса и бэкенда

Содержимое этого раздела предназначено только для справки и может быть неприменимо к другим сценариям.

внешний интерфейс

Разработайте интерфейсный элемент конфигурации временной задачи, включая переключатель, компонент «три варианта — один — один» и поле ввода:

Чтобы облегчить нетехническому персоналу установку задач на время и оптимизировать взаимодействие с пользователем, в дополнение к «настраиваемому» режиму ввода также существуют «ежедневные» и «еженедельные» варианты для задач на время:

  • ежедневно:0 1 1-5 * *
  • еженедельно:0 1 1 * *

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

задняя часть

Предположим, для моего бизнеса поля данных задачи, необходимые для внешнего интерфейса, следующие:

{
    "task_id": 1,
    "is_periodic_task": true,
    "periodic_task_id": 1,
    "crontab": "* * * * *"
}

Модель ER представлена ​​на рисунке:

В данных, возвращаемых на внешний интерфейс, еслиperiodic_taskне пусто, тоis_periodic_taskзаTrue, и пройтиperiodic_task.crontab_idполучитьCrontabScheduleЭкземпляр, преобразованный в строку и возвращенный.

обращать внимание,CrontabScheduleиз__str__метод в дополнение к возвратуcrontabконфигурации, а также возвращает такую ​​информацию, как часовой пояс, которая не требуется для внешнего интерфейса.

Итак, вы можете создать новый метод:

def get_crontab_str(contab) -> str:
    """
    获取前端配置需要的 5 项值
    :param contab: CrontabSchedule对象
    :return:
    """
    return '{0} {1} {2} {3} {4}'.format(
        cronexp(contab.minute), cronexp(contab.hour),
        cronexp(contab.day_of_week), cronexp(contab.day_of_month),
        cronexp(contab.month_of_year)
    )

Вызовите этот метод во время сериализации и верните его во внешний интерфейс.

Изменить задачи

Задача модификации включает в себя следующие три случая

  1. От задач на время к задачам без времени
  2. Переход от задачи без учета времени к задаче с ограничением по времени
  3. Измените стратегию синхронизации на основе задачи синхронизации

Соответствующая блок-схема выглядит следующим образом:

1:

2, 3:

На рисунке «при изменении конфигурации» относится к новой информации о конфигурации в запросе на изменение, отправленном из внешнего интерфейса.

Конкретный код не будет вдаваться в подробности, просто упомянем способ приостановки задачи по времени:

ИсправлятьPeriodicTask.objects.enabledзаFalse/0Только что

>>> periodic_task.enabled = False
>>> periodic_task.save()

Суммировать

Django реализует функцию настройки временных задач на веб-странице.К сожалению, настройка часовых поясов Django и его плагинами относительно сложна.Потребовалось много времени, чтобы наступить на множество ям, но все же есть некоторые проблемы, которые нельзя разгадать. Продолжайте исследовать!

Выходные данные

Фреймворк/Сервис/Компонент Версия иллюстрировать
Python 3.6.7
Django 2.2
RabbitMQ 3
Celery 4.3
django-celery-beat 1.5.0

Ссылаться на