Как выполнить планирование с помощью python?

Я пытаюсь запланировать несколько заданий внутри моего python. Предположительно, текст из журнала должен появляться каждые 1 минуту и ​​каждые 5 минут из файла jobs.py внутри моего контейнера докеров. Однако текст появляется каждые 2 минуты внутри док-контейнера. Есть ли конфликт между расписанием python и cronjobs?

Текущий вывод внутри док-контейнера

13:05:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:05:00] "GET /reminder/send_reminders HTTP/1.1" 200 -

13:06:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:06:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

13:07:00 [D] schedule Running job Job(interval=1, unit=minutes, do=job_feeds_update, args=(), kwargs = {})

13:07:00 [I] jobs job_feeds_update

13:07:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:07:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

13:08:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:08:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

13:09:00 [D] schedule Running job Job(interval=1, unit=minutes, do=job_feeds_update, args=(), kwargs = {})

13:09:00 [I] jobs job_feeds_update

13:09:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:09:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

13:10:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:10:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

13:10:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:10:00] "GET /reminder/send_reminders HTTP/1.1" 200 -

13:11:00 [D] schedule Running job Job(interval=1, unit=minutes, do=job_feeds_update, args=(), kwargs = {})

13:11:00 [I] jobs job_feeds_update

13:11:00 [D] schedule Running job Job(interval=5, unit=minutes, do=job_send_reminders, args=(), kwargs = {})

13:11:00 [I] jobs job_send_reminders

сервер.py

#Cron Job
@app.route('/feeds/update_feeds')
def update_feeds():
    schedule.run_pending()
    return 'OK UPDATED FEED!'
   
@app.route('/reminder/send_reminders')
def send_reminders():
    schedule.run_pending()
    return 'OK UPDATED STATUS!'
    

рабочие места.py

def job_feeds_update():
    update_feed()
    update_feed_eng()
    logger.info("job_feeds_update")
        
schedule.every(1).minutes.do(job_feeds_update)

# send email reminders
def job_send_reminders():
    send_reminders()
    logger.info("job_send_reminders")
schedule.every(5).minutes.do(job_send_reminders) 

Докер-файл

FROM alpine:latest

# Install curlt 
RUN apk add --no-cache curl

# Copy Scripts to Docker Image
COPY reminders.sh /usr/local/bin/reminders.sh
COPY feeds.sh /usr/local/bin/feeds.sh

RUN echo ' */5  *  *  *  * /usr/local/bin/reminders.sh' >> /etc/crontabs/root
RUN echo ' *  *  *  *  * /usr/local/bin/feeds.sh' >> /etc/crontabs/root

# Run crond  -f for Foreground 
CMD ["/usr/sbin/crond", "-f"]

если бы кто-нибудь мог мне помочь, я был бы признателен!

Gavin Juen 08.05.2022 08:10

Напоминания просто запускают «/reminder/send_reminders», а каналы запускают «/feeds/update_feeds»? Также когда выполняется jobs.py?

Azarro 08.05.2022 09:21

@Azaro да, это запускает маршруты соответственно

Gavin Juen 08.05.2022 09:23

Попался - когда job.py запускается/инициализируется, кстати?

Azarro 08.05.2022 09:23

Вы имеете в виду, когда jobs.py запускается внутри контейнера докеров?

Gavin Juen 08.05.2022 09:25

Да, для этого есть отдельный dockerfile? Просто любопытно - я не думаю, что с этим что-то не так. Кроме того, чтобы помочь нам получить больше информации, можете ли вы добавить logger.info("job_feeds_update started") в начало вашего метода job_feeds_update() (и сделать то же самое для журнала методов задания напоминаний), чтобы мы могли получить четкую временную метку того, когда задания действительно выполняются!

Azarro 08.05.2022 09:26

Итак, у меня было две папки в моих проектах. Я создал файл dockerfile для каталога приложений, job.py находится внутри каталога приложений и файл dockerfile для каталога cronjob. В настоящее время я использую docker compose для запуска двух сервисов для своих проектов. Хорошо, я добавлю информацию о регистраторе и опубликую последнюю отметку времени.

Gavin Juen 08.05.2022 09:31

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

Azarro 08.05.2022 09:43

я добавил logger.info("job_feeds_updated started ") в начало job_feeds_update(), но он не показал результат.

Gavin Juen 08.05.2022 09:45

вывод не проблема? кажется, что расписание не синхронизировано, поэтому мне интересно, правильно это или неправильно

Gavin Juen 08.05.2022 09:51

Мне странно, что «начатый» журнал не печатается, а другой журнал печатается в конце функции. Но в любом случае у меня есть некоторые мысли о потенциальной проблеме. Попытка проверить вещи локально, но дайте мне немного!

Azarro 08.05.2022 10:14

Хорошо !! с нетерпением ждем ответа от вас.

Gavin Juen 08.05.2022 10:32

Я опубликовал (длинное) объяснение. У меня есть несколько предложений о том, как вы можете решить свою проблему, но я еще не добавил их в пост. Я подумал, что вы можете прочитать объяснение, пока я продолжаю редактировать свой пост с решениями

Azarro 08.05.2022 10:36

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

Azarro 08.05.2022 10:43

Я буду читать его и занять некоторое время, чтобы обработать информацию. Я сообщу вам о пробной/ошибочной части, чтобы убедиться, что все работает.

Gavin Juen 08.05.2022 10:45

Отлично, надеюсь, что это в конечном итоге поможет!

Azarro 08.05.2022 10:46

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

Gavin Juen 08.05.2022 10:47

Без проблем! Обратите внимание, я только что добавил дополнительное возможное решение, о котором я только что узнал: модуль pypi.org/проект/ischeduleischedule, который работает как расписание, но на самом деле учитывает время выполнения задания. Это может быть самым простым решением для запуска, если очередей/потоков слишком много!

Azarro 08.05.2022 10:50

Просто хотел продолжить и посмотреть, помогло ли это в конечном итоге!

Azarro 08.05.2022 22:41

@Azarro, использующий ischedule, помогает поддерживать согласованное время выполнения задания и планирование. большое спасибо!!

Gavin Juen 09.05.2022 11:35
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
20
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, вы столкнулись с несколькими проблемами:

  1. Как вы подозревали, ваш schedule находится в другом расписании/интервале, чем ваша работа cron. Они не синхронизированы (и вы никогда не можете ожидать, что они будут синхронизированы по следующей причине). С момента выполнения вашего скрипта jobs.py это начальная точка, от которой расписание отсчитывает интервалы.

то есть, если вы запускаете что-то каждую минуту, но скрипт jobs.py запускается через 30 секунд текущей минуты (т.е. 01:00:30 - 1:00 через 30 секунд), то планировщик запустит задание в 1:01:30. , затем 1:02:30, затем 1:03:30 и так далее.

  1. Schedule Частотное исполнение не гарантирует вам точности. Когда планировщик запускает задание, время выполнения задания не учитывается. Поэтому, если вы запланируете что-то вроде заданий на рассылку/напоминания, это может занять некоторое время. После завершения выполнения планировщик решает, что следующее задание будет выполняться только 1 минуту после окончания предыдущей работы. Это означает, что ваше время выполнения может нарушить график.

Попробуйте запустить этот пример в скрипте Python, чтобы понять, о чем я говорю.

# Schedule Library imported
import schedule
import time
from datetime import datetime
     
def geeks():
    now = datetime.now() # current date and time
    date_time = now.strftime("%m/%d/%Y, %H:%M:%S")
    time.sleep(5)
    print(date_time + "- Look at the timestamp")
 
geeks();
# Task scheduling
# After every 10mins geeks() is called.
schedule.every(1).seconds.do(geeks)
 
# Loop so that the scheduling task
# keeps on running all time.
while True:
 
    # Checks whether a scheduled task
    # is pending to run or not
    schedule.run_pending()
    time.sleep(0.1)

Мы запланировали запуск функции geeks каждую секунду. Но если вы посмотрите на функцию гиков, я добавил time.sleep(5), чтобы представить, что здесь может быть какой-то блокирующий вызов API, который может занять 5 секунд. Затем посмотрите на записанные временные метки — вы заметите, что они не всегда соответствуют расписанию, которое мы изначально хотели!

Теперь о том, как ваша работа cron и планировщик не синхронизированы.

Посмотрите на следующие журналы:

13:07:00 [D] schedule Running job Job(interval=1, unit=minutes, do=job_feeds_update, args=(), kwargs = {})
13:07:00 [I] jobs job_feeds_update
13:07:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:07:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

# minute 8 doesn't trigger the schedule for feeds

13:09:00 [D] schedule Running job Job(interval=1, unit=minutes, do=job_feeds_update, args=(), kwargs = {})
13:09:00 [I] jobs job_feeds_update
13:09:00 [I] werkzeug 172.20.0.2 - - [08/May/2022 13:09:00] "GET /feeds/update_feeds HTTP/1.1" 200 -

Вероятно, здесь происходит следующее:

  • в 13:07:00 ваш cron отправляет запрос на подачу элементов

  • в 13:07:00 в расписании заданий есть ожидающее задание для элементов фида

  • в 13:07:00: задание завершается, и расписание определяет, что следующее задание может быть запущено только через 1 минуту, что составляет примерно ~13:08:01 (обратите внимание на 01, это для учета миллисекунд/времени задания). выполнений, что позволяет предположить, что для запуска обновления элементов фида потребовалась 1 секунда)

  • в 13:08:00 ваше задание cron запускает запрос, запрашивающий задания schedule run_pending.

  • в 13:08:00 Однако, есть нет ожидающих выполнения заданий, потому что следующий раз, когда элементы ленты могут запускаться, будет 13:08:01, а не прямо сейчас.

  • в 13:09:00 ваша вкладка cron снова запускает запрос

  • в 13:09:00 доступно ожидающее задание, которое должно было выполняться в 13:08:01, чтобы оно было выполнено сейчас.

Я надеюсь, что это иллюстрирует проблему, с которой вы столкнулись, из-за рассинхронизации между cron и расписанием. Эта проблема усугубится в производственной среде. Вы можете прочитать больше о Параллельное исполнение для schedule как о средстве отвлечься от основного потока, но это пока не так далеко. Давайте поговорим о...

Возможные решения

  1. Используйте run_all из расписания вместо run_pending, чтобы принудительно запускать задания, независимо от того, когда они фактически запланированы.

Но если подумать, это ничем не отличается от простого вызова job_feeds_update прямо из вашего маршрута API. Это неплохая идея сама по себе, но она все еще не очень чистая, поскольку она будет блокировать основной поток вашего API-сервера до тех пор, пока job_feeds_update не будет завершен, что может быть не идеальным, если у вас есть другие маршруты, которые нужны пользователям.

Вы можете объединить это со следующим предложением:

  1. Использование очереди заданий и потоков Ознакомьтесь со вторым примером в Страница параллельного выполнения документации по расписанию. В нем показано, как использовать очередь заданий и потоки для разгрузки заданий.

Поскольку вы запускаете schedule.run_pending(), ваш основной поток на вашем сервере заблокирован до тех пор, пока не будут запущены задания. Используя потоки (+ очередь заданий), вы можете планировать задания в очереди + избегать блокировки основного сервера своими заданиями. Это должно немного оптимизировать работу для вас, позволяя планировать задания.

  1. Вместо этого используйте ischedule, поскольку он учитывает время выполнения задания и предоставляет точные расписания: https://pypi.org/project/ischedule/. Это может быть простейшее решение для вас на случай, если 1+2 станет головной болью!

  2. Не использовать расписание, и ваши задания cron просто попадут на маршрут, который просто запускает реальную функцию (так что в основном это противоречит совету использовать 1 + 2 выше). Проблема заключается в том, что если вашим функциям требуется больше минуты для обновления веб-канала, у вас может быть несколько перекрывающихся заданий cron, выполняющихся одновременно для обновления веб-канала. Поэтому я бы рекомендовал не делать этого и полагаться на механизм для постановки в очередь/планирования ваших запросов с потоками и заданиями. Только упоминая об этом как о потенциальном сценарии того, что еще вы могли бы сделать.

Другие вопросы по теме