现象:
在本地利用Flask自带的WSGI服务进行调试没有问题后,经过Gunicorn进行部署。html
可是在一夜没有访问以后,次日再次访问会出现500(Internal error)。python
缘由:
经过追踪日志文件,发现是Sqlalchemy链接Mysql的断开问题mysql
2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))"sql
此问题来自于Mysql的链接(Session)在一段时间(默认为8小时,因此在我这里体现为次日)没有响应后会自动断开,而SQLAlchemy并不会监测到Session的断开因此在尝试使用旧Session的时候会出现此错误。查看mysql的日志,也能够看到它确实会主动放弃过时session:flask
Aborted connection 112225 to db。。。。。。。。。。。。。。。(Got an error reading communication packets)session
解决方案:
既然是由于Sqlalchemy的Session过时问题,因此固然要解决过时,Stack Overflow上找了不一样的解决方案,主要分为几种:app
1.增长mysql的wait_timeout让mysql不要太快放弃链接。
假如你的服务访问还算频繁,不太会出现长时间无链接。那么增长此变量从而避免短期无链接形成的abort是能够的,可是假如超出时间,迟早仍是会崩溃。具体到本身的Mysql的此设置为多少能够经过此命令查看(wait_timeout):wordpress
show variables like '%timeout%';
+-----------------------------+----------+
| Variable_name | Value |
+-----------------------------+----------+
| connect_timeout | 10 |
| delayed_insert_timeout | 300 |
| innodb_flush_log_at_timeout | 1 |
| innodb_lock_wait_timeout | 50 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| rpl_stop_slave_timeout | 31536000 |
| slave_net_timeout | 3600 |
| wait_timeout | 28800 |
+-----------------------------+----------+
2.在建立Engine的时候经过设定 pool_recycle让Sqlalchemy的session pool按期回收过时Session
可能被mysql抛弃了的session,因此应该要比你的mysql wait_timeout小)来保证Session的有效性。测试
e = create_engine("mysql://scott:tiger@localhost/test", pool_recycle=3600)
'''
pool_recycle=-1: this setting causes the pool to recycle
connections after the given number of seconds has passed. It
defaults to -1, or no timeout. For example, setting to 3600
means connections will be recycled after one hour. Note that
MySQL in particular will disconnect automatically if no
activity is detected on a connection for eight hours (although
this is configurable with the MySQLDB connection itself and the
server configuration as well).
'''
!注意,若是是云服务提供商,可能对这个断开的时间限制有不一样的设定,好比pythonanywhere的设置是5分钟:网站
SQLAlchemy needs to some extra arguments to work on PythonAnywhere:
engine = create_engine('mysql+mysqldb://...', pool_recycle=280)
The RDS service disconnects clients after 5 minutes (300s), so we need to set the pool_recycle
to something lower than that, or you'll occasionally see disconnection errors in your logs.
而新浪云的设置则更加短,只有30秒:
MySQL gone away问题
MySQL链接超时时间为30s,不是默认的8小时,因此你须要在代码中检查是否超时,是否须要重连。
对于使用sqlalchemy的用户,须要在请求处理结束时调用 db.session.close() ,关闭当前session,将mysql链接还给链接池,而且将链接池的链接recyle时间设的小一点(推荐为60s)。
3.重建Session??(来自StackOverflow)
在这个问题中的最高票答案说,在每个你须要使用SqlAlchemy的地方实例化一个Session。
可是在我在实际操做中,经过查看Session id发现,两次实例的ID一致,也就是说明Session()只是从session pool里面取回来旧的Session,那么这个Session迟早会过时.事实也证实,这个方法并无解决个人服务中mysql gone away的问题。因此这个答案我持保留态度。
4.SQlAlchemy官方:设置pool_pre_ping=True
官方文档其实对于myslq断链的问题,官方除了设置pool_recycle以外还建议在建立Engine的时候设置pool_pre_ping=True也就是在每一次使用Session以前都会进行简单的查询检查,判断是Session是否过时。
engine = create_engine("mysql+pymysql://user:pw@host/db", pool_pre_ping=True)
详细可见参考网站。
可是以上几个方法我都尝试事后,依然会重现该问题,最后看到这篇文档中说:
To use SQLAlchemy in a declarative way with your application, you just have to put the following code into your application module. Flask will automatically remove database sessions at the end of the request or when the application shuts down:
from yourapplication.database import db_session
@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()
终于,mysql has gone away不见啦! 也就是每一次请求结束,Flask都会关闭Session.(TODO: 测试此方法是否低效率。)
Reference:
Avoiding “MySQL server has gone away” on infrequently used Python / Flask server with SQLAlchemy
Connection Pooling¶
SQLAlchemy in Flask
http://docs.sqlalchemy.org/en/latest/core/pooling.html#dealing-with-disconnects
https://mofanim.wordpress.com/2013/01/02/sqlalchemy-mysql-has-gone-away/