Как запустить повторяющуюся задачу в фреймворке Python Flask?

#python #cron #flask #apscheduler

Вопрос:

Я создаю веб-сайт, который предоставляет некоторую информацию посетителям. Эта информация агрегируется в фоновом режиме путем опроса пары внешних API каждые 5 секунд. Сейчас у меня все работает так, что я использую задания APScheduler. Сначала я предпочел APScheduler, потому что он упрощает перенос всей системы (так как мне не нужно устанавливать задания cron на новой машине). Я запускаю функции опроса следующим образом:

 from apscheduler.scheduler import Scheduler

@app.before_first_request
def initialize():
    apsched = Scheduler()
    apsched.start()

    apsched.add_interval_job(checkFirstAPI, seconds=5)
    apsched.add_interval_job(checkSecondAPI, seconds=5)
    apsched.add_interval_job(checkThirdAPI, seconds=5)
 

Это вроде как работает, но с этим есть некоторые проблемы:


  1. Для начала это означает, что интервальные задания выполняются вне контекста колбы. До сих пор это не было большой проблемой, но при сбое вызова конечной точки я хочу, чтобы система отправила мне электронное письмо (со словами «привет, вызов API X не удался»). Однако, поскольку он не запускается в контексте Flask, он жалуется, что flask-mail не может быть выполнен ( RuntimeError('working outside of application context') ).
  2. Во-вторых, мне интересно, как это будет вести себя, когда я больше не буду использовать встроенный отладочный сервер Flask, а рабочий сервер, скажем, с 4 рабочими. Тогда он будет начинать каждую работу четыре раза?

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

[ПРАВИТЬ] Я только что читал о Сельдерее с его расписанием. Хотя я действительно не понимаю, чем Сельдерей отличается от APScheduler и может ли он таким образом решить мои два вопроса, мне интересно, думает ли кто-нибудь, читающий это, что я должен больше исследовать Сельдерей?

[ЗАКЛЮЧЕНИЕ] Примерно два года спустя я читаю это, и я подумал, что мог бы сообщить вам, ребята, что у меня получилось. Я решил, что @BluePeppers был прав, говоря, что я не должен быть так тесно связан с экосистемой Колб. Поэтому я выбрал регулярные задания cron, выполняемые каждую минуту, которые задаются с помощью Ansible. Хотя это немного усложняет задачу (мне нужно было изучить Ansible и преобразовать некоторый код, чтобы его было достаточно запускать каждую минуту). Я думаю, что это более надежно. В настоящее время я использую удивительный pythonr-rq для постановки в очередь заданий синхронизации (проверка API и отправка электронной почты). Я только что узнал о rq-планировщике. Я еще не тестировал его, но, похоже, он делает именно то, что мне было нужно в первую очередь. Так что, возможно, это совет для будущих читателей этого вопроса.

В остальном я просто желаю всем вам прекрасного дня!

Ответ №1:

(1)

Вы можете использовать app.app_context() контекстный менеджер для настройки контекста приложения. Я предполагаю, что использование будет выглядеть примерно так:

 from apscheduler.scheduler import Scheduler

def checkSecondApi():
    with app.app_context():
        # Do whatever you were doing to check the second API

@app.before_first_request
def initialize():
    apsched = Scheduler()
    apsched.start()

    apsched.add_interval_job(checkFirstAPI, seconds=5)
    apsched.add_interval_job(checkSecondAPI, seconds=5)
    apsched.add_interval_job(checkThirdAPI, seconds=5)
 

В качестве альтернативы вы можете использовать декоратора

 def with_application_context(app):
    def inner(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            with app.app_context():
                return func(*args, **kwargs)
        return wrapper
    return inner

@with_application_context(app)
def checkFirstAPI():
    # Check the first API as before
 

(2)

Да, это все равно будет работать. Единственное (существенное) различие заключается в том, что ваше приложение не будет напрямую взаимодействовать с миром; оно будет проходить через обратный прокси-сервер или что-то еще через fastcgi/uwsgi/что угодно. Единственная проблема заключается в том, что если у вас запущено несколько экземпляров приложения, то будет создано несколько планировщиков. Чтобы справиться с этим, я бы посоветовал вам перенести свои внутренние задачи из приложения Flask и использовать инструмент, предназначенный для регулярного выполнения задач (например, Сельдерей). Недостатком этого является то, что вы не сможете использовать такие вещи, как почта в колбе, но, имо, не слишком хорошо быть так тесно связанным с экосистемой колбы; что вы получаете, используя почту в колбе по сравнению со стандартной, не колбой, почтовой библиотекой?

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

Комментарии:

1. Спасибо вам за ваш подробный ответ. И последнее: поскольку APScheduler работает отлично, как вы думаете, в чем будет преимущество использования сельдерея перед APScheduler? Что бы вы выбрали и почему?

2. Ну, Сельдерей предлагает вам гораздо больше, чем простое планирование. С учетом сказанного, я мало что знаю об APScheduler, и, читая документы сейчас, все выглядит совершенно нормально.

3. Я нахожу этот пост очень полезным. Но я хотел бы спросить, я использую базу данных SQL для своего приложения, которое из APScheduler или Сельдерея было бы более подходящим для использования? Я вижу редис в Сельдерее. Могу ли я вместо этого пойти на APScheduler?

4. Можете ли вы сказать мне, можно ли запустить запланированное задание только для одного процесса с помощью uwsgi ? когда у меня есть процессы 4 в моем файле uwsgi.ini, задачи выполняются с каждым процессом.

5. Спасибо за этот ответ, это очень помогает мне решить проблему, она работает как заклинание.