задний план
При разработке функций, связанных с фоновыми задачами, возникает требование: пользователи должны иметь возможность настраивать политики синхронизации для задач, чтобы задачи могли выполнять определенные операции на регулярной основе.
анализ спроса
В соответствии с потребностями, мы можем разобрать его на следующие шаги:
- Осуществление «операции»
- Настроить как запланированную задачу
- Настраиваемая временная стратегия
- хороший пользовательский опыт
Среди них шаг 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
В процессе я столкнулся с двумя проблемами, связанными с часовыми поясами:
- Созданная запланированная задача имеет 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, так что с этой конфигурацией могут быть проблемы.
- в базе данных,
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:
На рисунке «при изменении конфигурации» относится к новой информации о конфигурации в запросе на изменение, отправленном из внешнего интерфейса.
Конкретный код не будет вдаваться в подробности, просто упомянем способ приостановки задачи по времени:
Исправлять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 |