現象:
在本地利用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/