Как обойти тайм-аут сеанса базы данных Flask -Alchemy (избегая "MySQL сервер ушел") - PullRequest
0 голосов
/ 13 января 2020

Я видел этот вопрос несколько раз здесь с ответами, которые мне не помогли.

Насколько я понимаю, Flask -SQLAlchemy подключается к базе данных в начале запроса Flask и разрывает вниз этой сессии, когда закончите. Но что, если этот запрос занимает много времени?

Например, мое приложение принимает запрос POST, который запускает долго выполняющуюся задачу Celery, которой необходимо выполнить запись в базу данных. Сервер MySQL через некоторое время «уходит», поэтому запись в базу данных по окончании задачи не работает.

Я читаю в режиме онлайн, чтобы установить options["pool_pre_ping"] = True для устранения такой проблемы (используя Обходное решение SQLAlchemy для подклассов нашло здесь ), но это не имеет значения. Происходит та же ошибка, и кажется, что никакие настройки на самом деле не меняются.

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

Ответы [ 2 ]

0 голосов
/ 28 февраля 2020

Закройте сеанс, но держите объекты рядом. Редактировать их. Затем используйте merge. Убедитесь, что настройка expire_on_commit равна False.

0 голосов
/ 13 января 2020

Я думаю, у вас есть правильное представление о переосмыслении того, как задача записывает в базу данных.

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

После этого вы можете запустить еще одну python (не flask) программу в фон, который непрерывно читает из этой очереди и извлекает из нее самые новые задачи, а затем выполняет задачи и сохраняет их в базе данных.

Это можно сделать с помощью Возможно, вы уже делаете это с помощью Celery для вызова асинхронного вызова. c функция? Вот запись в блоге о том, как создать такую ​​задачу Celery и использовать ее с Flask route https://blog.miguelgrinberg.com/post/using-celery-with-flask

Вы правы, что Flask -SQLAlchemy разрушает сеанс базы данных когда контекст веб-запроса закрывается, что происходит, когда возвращается маршрут flask.

Чтобы предотвратить закрытие сеанса Flask -SQLAlchemy, вы не должны передавать свой session от маршрута Flask к asyn c функция сельдерея. Вместо этого вы должны «создать новый сеанс» (сделать вызов в реестре сеансов) внутри функции ceyn asyn c.

Фактически это общий принцип при использовании сеансов из SQLAlchemy в веб-приложении. Вы хотите избежать передачи объекта сеанса. SQLAlchemy продвигает этот принцип, используя шаблон реестра для создания реестра сеансов, который возвращает объект сеанса «локальный поток». Эта идея «локального потока» означает, что при получении сеанса из реестра сеансов вы гарантируете, что сеанс используется только в текущем потоке. Поскольку вы в конечном итоге создаете новый поток для выполнения длительной задачи, вы можете положиться на Реестр сеансов, который даст вам поток, на который не повлияет закрытие веб-запроса, так как веб-запрос обрабатывается в другом потоке. .

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

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from celery import Celery

app = Flask(__name__)
db = SQLAlchemy(app)

app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'

celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)


@celery.task
def do_stuff_that_takes_10_minutes(payload):
    session = db.session  # Gets a thread local session from the Session Registry that is different from the session obj you used in the route because this function will be executed in its own thread.
    # some long running task here
    return result

@app.route('/long_running_task', methods=['GET'])
def long_running_task():
  session = db.session  # Get a thread local session from the Session Registry that will close once this web request is complete

  # use this session here and do stuff

  # Call this function, which Celery will spawn a new thread to do
  # Don't pass the above session object into this function, we want this
  # session obj to stay only within this thread.
  do_stuff_that_takes_10_minutes.delay()

  return "Executing task, could take 10 minutes or more."
...