运行 Flask 服务器(Apache)几天时出现 MySQL OperationalError
Posted
技术标签:
【中文标题】运行 Flask 服务器(Apache)几天时出现 MySQL OperationalError【英文标题】:MySQL OperationalError when running a Flask server (Apache) for some days 【发布时间】:2017-10-11 05:05:09 【问题描述】:我在 Apache 下有一个 Flask 服务器,我将它用作应用程序的 Rest 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()
起初我认为问题正在发生,因为我没有在调用fetchone
方法的游标中使用buffered=True
。但是我在阅读this后添加了该参数。
这是我的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.
其他信息:
我在 Banana Pi 中的 armbian (Debian) 下运行 Apache/2.4.10。
我真的不知道为什么服务器运行一段时间后停止,我想我几乎尝试了所有方法。
编辑:在login
方法中抛出403
错误之前,我还添加了cursor.close()
。但这无关紧要,因为我是唯一一个登录应用程序的人,而且我总是输入正确的凭据。
编辑 2: 正如@stamaimer 告诉我的那样,如果我在获得任何光标之前添加connection.ping()
,它效果很好,但这种方法对我来说似乎是一种 hacky 方式,我不这样做'不知道这是否是一个好的解决方案,甚至不知道 MySQL 服务器为什么会断开连接。
【问题讨论】:
你是如何运行 MySQL 的?是在 Banana Pi 还是其他服务器上? 它在 Banana Pi 上。 尝试在从连接中获取光标之前添加connection.ping()
。
@stamaimer 这对我来说似乎有点 hacky,你确定这是一个好的解决方案吗? (我没试过)。
试试看。我以前也遇到过类似的问题。
【参考方案1】:
在@9000s answer 上构建,当使用参数reconnect=True
、see the corresponding code 调用时,ping 应该可以正常工作。这将发出 ping,如果 ping 失败,请尝试重新连接到数据库。
虽然这应该可以解决错误,但您应该深入挖掘以找出根本问题,即与 MySQL 的非功能连接。
如前所述,这可能来自多个来源,因此也许您可以使用以下列表来指导您的搜索:
在您发布之前的日志中是否还有其他错误消息? 您是通过 TCP/IP 还是通过套接字连接到 MySQL?也许尝试另一种变体,如果一种有效而另一种无效,这可能有助于确定问题。 将max_timeout
增大到一个较大的值,只是为了确定它是否有效果。
您可能还想增加max_allowed_packet
、as this helped in other cases。
检查 MySQL 服务器日志以了解丢失连接的潜在问题。
还要查看操作系统的syslog,因为MySQL might get killed due to low memory,可以修复by setting better values in 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
装饰器,以避免出现 AssertionError
,如 here 所示。
【讨论】:
我正在使用mysql
。我有两个问题:connection.ping()
是强制重新连接的好方法吗?为什么我应该使用connection.begin_transaction()
而不仅仅是connection.commit
?
嗯,connection.begin_transaction()
是什么意思?没有这样命名的函数。
@Grender:我错了;事务开始在 DBAPI 中是隐含的。更新我的答案。
您能否告诉我connection.ping()
是否是强制重新连接的好方法?会投票给你,因为你帮助了我,但我认为这并不能回答主要问题/赏金。
坦率地说,我不知道ping
。上次遇到上述断开连接问题时,我使用了重试逻辑,主要是因为断开连接可能发生在长时间运行的事务的中间,而不仅仅是在开始时。以上是关于运行 Flask 服务器(Apache)几天时出现 MySQL OperationalError的主要内容,如果未能解决你的问题,请参考以下文章
如何在apache服务器上托管python Flask应用程序?
必须重新启动 apache 服务器才能在 Flask 中显示来自 SQL Server 的数据
apache flask sqlite 无法在 ubuntu 上打开数据库文件