MySQL OperationalError при запуске сервера Flask (Apache) в течение нескольких дней
У меня есть сервер Flask под Apache, который я использую в качестве API-интерфейса для приложения, и когда сервер работает в течение 2 - 3 дней, он внезапно перестает работать и поднимает OperationalError: MySQL Connection not available.
Ошибка всегда выполняется с помощью метода login
, потому что она открывается первым при открытии приложения (но все методы следуют одному и тому же шаблону).
Это метод login
:
@app.route(LOGIN_API_URL, methods=['POST'])
def login():
if (request.method == 'POST'):
cursor = connection.cursor(buffered=True, dictionary=True)
cursor.execute('select * from users where username = %s', (request.form['username'],))
user = cursor.fetchone()
if user is None or user['password'] != str(request.form['password']):
abort(403)
else:
cursor.execute('update users set last_login = (%s) where user_id = %s', str(int(round(time.time() * 1000))), user['user_id'],)
utils.safe_commit(connection, cursor)
return utils.sanitize_response({'status':200, 'message':'Logged in'})
Оба safe_commit
и sanitize_response
следует:
def sanitize_response(response, is_array=False):
if response is None:
return '[]' if is_array else '{}'
else:
return jsonify(response)
def safe_commit(connection, cursor):
try:
connection.commit()
except:
connection.rollback()
finally:
cursor.close()
Сначала я думал, что проблема происходит, потому что я не использовал buffered=True
в курсоре, который вызывает метод fetchone
. Но я добавил этот параметр после прочтения этого.
Это мой файл wsgi
:
#!/usr/bin/python
import sys
sys.path.append("/var/www/protestr/")
from protestr import app as application
Это мой файл sites-available
conf
(я хотел сказать, что я пробовал множество комбинаций параметров threads
и processes
, и эта комбинация является той, которая поддерживает работу сервера в наибольшей степени время, обычно 2 - 3 дня):
<VirtualHost *:80>
ServerName protestr.tk
DocumentRoot /var/www/protestr/
WSGIDaemonProcess protestr user=www-data group=www-data processes=2 threads=25
WSGIScriptAlias / /var/www/protestr/protestr.wsgi
<Directory /var/www/protestr>
WSGIProcessGroup protestr
WSGIApplicationGroup %{GLOBAL}
Require all granted
</Directory>
</VirtualHost>
Это содержимое файла error.log
:
[Fri May 12 03:34:14.967624 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] [2017-05-12 03:34:14,963] ERROR in app: Exception on /api/v1/users/login [POST]
[Fri May 12 03:34:14.967812 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] Traceback (most recent call last):
[Fri May 12 03:34:14.967861 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1982, in wsgi_app
[Fri May 12 03:34:14.967900 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] response = self.full_dispatch_request()
[Fri May 12 03:34:14.967937 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1614, in full_dispatch_request
[Fri May 12 03:34:14.967973 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] rv = self.handle_user_exception(e)
[Fri May 12 03:34:14.968007 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1517, in handle_user_exception
[Fri May 12 03:34:14.968043 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] reraise(exc_type, exc_value, tb)
[Fri May 12 03:34:14.968076 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1612, in full_dispatch_request
[Fri May 12 03:34:14.968111 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] rv = self.dispatch_request()
[Fri May 12 03:34:14.968144 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1598, in dispatch_request
[Fri May 12 03:34:14.968179 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] return self.view_functions[rule.endpoint](**req.view_args)
[Fri May 12 03:34:14.968251 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/var/www/protestr/protestr.py", line 89, in login
[Fri May 12 03:34:14.968290 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] cursor = connection.cursor(buffered=True, dictionary=True)
[Fri May 12 03:34:14.968326 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] File "/usr/local/lib/python2.7/dist-packages/mysql/connector/connection.py", line 809, in cursor
[Fri May 12 03:34:14.968363 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] raise errors.OperationalError("MySQL Connection not available.")
[Fri May 12 03:34:14.968399 2017] [wsgi:error] [pid 18673:tid 2849002544] [remote 192.168.1.139:25727] OperationalError: MySQL Connection not available.
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:
Я запускаю Apache/2.4.10 под armbian (Debian) в банановой Pi.
Я действительно не знаю, почему сервер перестает работать некоторое время, я думаю, что пробовал почти все.
EDIT: Я добавил также cursor.close()
, прежде чем выбросить ошибку 403
в метод login
. Но это не актуально, поскольку я единственный, кто регистрируется в приложении, и я всегда ввожу правильные учетные данные.
РЕДАКТИРОВАТЬ 2: Как сказал мне @stamaimer, если я добавлю connection.ping()
, прежде чем получить какой-либо курсор, он будет работать хорошо, но этот подход кажется мне взломанным способом и не знаю, является ли это хорошим решением или даже почему сервер MySQL отключает соединение.
Ответы
Ответ 1
Основываясь на @9000s answer, ping должен работать нормально при вызове с параметром reconnect=True
, см. соответствующий код. Это вызовет ping, и в случае неудачного ping попытайтесь подключиться к базе данных.
Хотя это должно исправить ошибку, вы должны немного разобраться, чтобы найти основную проблему, которая является нефункциональным подключением к MySQL.
Как уже упоминалось, это может быть результатом нескольких источников, поэтому, возможно, вы можете использовать следующий список, чтобы вести поиск:
- Существуют ли другие сообщения об ошибках в журнале, прежде чем вы отправили сообщение?
- Вы подключаетесь к MySQL через TCP/IP или через сокет? Возможно, попробуйте другой вариант, если он работает, а другой - нет, это может помочь выявить проблему.
- Увеличьте
max_timeout
до большого значения, просто чтобы убедиться, что он имеет эффект.
- Вы также можете увеличить
max_allowed_packet
, поскольку это помогло в других случаях.
- Проверьте журналы сервера MySQL на наличие возможных проблем с потерянными соединениями.
- Также проверьте syslog операционной системы, потому что MySQL может быть убит из-за низкой памяти, который может быть исправлен установив лучшие значения в config.
Надеемся, что это поможет вам найти основную причину.
Ответ 2
Этот пример как-то груб, но, надеюсь, показывает логику вокруг обработки отключений. Специфика зависит от способа получения соединений в конкретной структуре.
В приведенном ниже коде предполагается использование retry
; при необходимости вы можете использовать другую логику повтора.
Он также предполагает, что вы используете пул соединений с вашим приложением, предоставляемым инфраструктурой. Обычно вы можете попросить его повторно подключиться или, по крайней мере, закрыть незанятые соединения, чтобы другой запрос на соединение создавал новый.
def reconnect_on_failure(func):
@retry(OperationalError, delay=0.25, backoff=1.5, max_delay=5)
@wraps(func)
def reconnecting_func(*args, **kwargs):
try:
return func(*args, **kwargs)
except OperationalError as e:
if 'connect' in e.msg.lower():
force_reconnection_somehow() # Look at your connection pool API.
raise # We want to retry on it
raise Exception('Unhandled MySQL error', e) # Will not retry.
return reconnecting_func
@reconnect_on_failure
def something(...):
connection = get_connecton_somehow() # Look at the framework API.
# A transaction implicitly begins with the first statement executed.
cursor = connection.cursor()
result = cursor.execute(...) # do stuff
connection.commit()
Вместо чрезмерно широкого Exception
вы можете использовать более узкий класс, например. для вашего приложения; идея заключается в том, что повышение чего-либо, кроме OperationalError
, не приведет к запуску повторных попыток, и оно немедленно поднимет Exception
, чтобы сообщить о проблеме.
ИЗМЕНИТЬ из Grender: Я добавил @wraps
decorator, чтобы избежать AssertionError
как показано здесь.