經過了各種測試,代碼的正確性和質量都有了很大保證,但是,部署前的準備工作尚未結束。雖然代碼能實現預期的效果,但在性能上未必是合格的。頁面加載時間太長會讓用戶失去興趣,所以儘早發現並修正性能問題是一件很重要的工作。我們可以從請求響應的流程中的下面幾個環節來進行優化:
- 函數執行
- 數據庫查詢
- 模板渲染
- 頁面資源加載
我們可以Flask-DebugToolBar的性能分析功能對代碼進行分析,當然也可以使用Werkzeug的源碼分析器(稍後介紹),找出運行最慢的函數。那麼如何對這些函數進行優化呢?除了在代碼層面進行優化外,我們通常會使用異步任務隊列把它們放到後臺處理,這樣可以避免阻塞請求響應的處理。常用的Python任務隊列有Celery和更輕量的Redis-Queue等,其中Celery還支持週期任務和定時任務。
一. 在日誌中記錄影響性能的緩慢數據庫查詢
如果應用的性能隨着時間的推移不斷降低,很可能是因爲數據庫查詢變慢了,隨着數據庫規模的增長,這一情況會變得更糟。優化數據庫有時很簡單,只需添加更多的索引即可;有時卻很複雜,需要在應用和數據庫之間加入緩存。多數數據庫查詢語言都提供了explain語句,用於顯示數據庫執行查詢時採取的步驟,從這些步驟中我們經常能發現數據庫索引設計的不足之處。
一次請求往往可能要執行多條數據庫查詢,所以經常很難分辨那一條查詢較慢。Flask-SQLAlchemy提供可一個選項,可以記錄一次請求中和數據庫查詢有關的統計數據。
app/main/views.py:報告緩慢的數據庫查詢
from flask_sqlalchemy import get_debug_queries
@main.after_app_request
def after_request(response):
for query in get_debug_queries():
if query.duration >= current_app.config['FLASKY_SLOW_DB_QUERY_TIME']:
current_app.logger.warning('Slow query:{}\nParameters:{}\nDuration:{}\nContext:{}\n'.
format(query.statement, query.parameters, query.duration, query.context))
return response
這個功能使用after_app_request處理程序實現,它和before_app_request處理程序的工作方式類似,只不過在視圖函數處理完請求之後執行。Flask把響應對象傳給after_app_request處理程序,以防需要修改響應。本例中,我們不需要修改response對象,只是獲取Flask-SQLAlchemy記錄的查詢時間,把緩慢的查詢寫入日誌(應用的日誌需要通過app.logger設置),然後再返回響應,發送給客戶端。
get_debug_queries()函數返回一個列表,其元素是請求中執行的查詢。Flask-SQLAlchemy記錄的查詢信息如下:
名稱 | 說明 |
statement | SQL語句 |
parameters | SQL語句使用的參數 |
start_time | 執行查詢時的實踐 |
end_time | 返回查詢結果時的時間 |
duration | 查詢持續的時間,單位爲秒 |
context | 表示查詢在源碼中所處的位置 |
這裏設置的日誌等級是告警,不過有時候更適合把緩慢數據庫查詢視作錯誤。默認情況下,get_debug_queries()函數只在調試模式中可用。但是性能問題很少發生在開發階段,因爲開發時使用的數據量較小。因此,在生產環境中使用該選項才能發揮它的作用。若想在生產環境中監控數據庫性能,我們必須修改如下配置:
class Config:
...
SQLALCHEMY_RECORD_QUERIES = True
FLASK_SLOW_DB_QUERY_TIME = 0.5
...
當發現緩慢查詢時,Flask應用的日誌記錄器會寫入一條記錄。若想保存這些日誌記錄,必須配置日誌記錄器。 關於日誌記錄器的配置我們將在部署章節展開介紹。這裏小編使用Flask-DebugToolBar查看記錄的緩慢查詢日誌(爲了看到效果,把FLASKY_SLOW_DB_QUERY_TIME 設置爲了 0.0005,即查詢時間大於0.5ms時將記錄一條日誌):
二. 分析源碼
性能的另一個可能誘因是高CPU消耗,由執行大量運算的函數導致。源碼分析器能找出應用中最慢的部分。分析器監視運行中的應用,記錄調用的函數以及運行各函數所消耗的時間,然後生成一份詳細的報告,指出運行最慢的函數。一般只在開發環境中分析源碼,因爲源碼分析器會導致應用運行速度比常規情況下慢得多。
Flask使用的Web開發服務器由Werkzeug提供,可根據需要爲每條請求啓用python分析器。
fasky.py:在請求分析器的監視下運行應用
import sys
from werkzeug.middleware.profiler import ProfilerMiddleware
from app import create_app
if __name__ == "__main__":
app = create_app('development')
try:
restrictions = int(sys.argv[1])
except(IndexError, TypeError):
restrictions = 25
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[restrictions])
app.run(debug=False)
(注:新版本的Flask似乎不建議自定義命令中調用app.run(),所以小編略微調整了一點點架構,可在github源碼倉庫中查看。)
通過運行"python flask.py"在分析器模式下運行應用。(不使用分析器,直接flask run運行即可。) 使用app的wsgi_app屬性,把Werkzeug的ProfilerMiddleware中間件依附到應用上。這裏通過中間件捕獲分析數據,注意這裏以app.run()方法,以編程的方式啓動應用。
終端輸出如下:
但了使用ProfilerMiddleware中間件外,目前更流行的一種做法是使用Flask-DebugToolbar,可以直接在WEB界面查看結果,通過如下方式安裝:
pipenv install flask-debugtoolbar
然後進行擴展的初始化:
extensions.py:創建擴展
from flask_debugtoolbar import DebugToolbarExtension
debug_toolbar = DebugToolbarExtension()
app/__init__.py:完成擴展初始化
from app.extensions import bootstrap, mail, moment, db, login_manager, page_down, debug_toolbar, migrate
def create_app(config_name=None):
if not config_name:
config_name = os.environ.get('FLASK_CONFIG') or 'default'
app = Flask('app')
app.config.from_object(config[config_name]) # 使用對象始化app.config
config[config_name].init_app(app=app)
register_extensions(app)
register_blueprints(app)
register_command(app)
register_shell_context(app)
return app
...
def register_extensions(app):
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
login_manager.init_app(app)
page_down.init_app(app)
debug_toolbar.init_app(app)
migrate.init_app(app, db)
...
config.py:添加和Debugtoolbar相關的配置:
class Config:
...
# debug toolbar默認會攔截重定向
DEBUG_TB_INTERCEPT_REDIRECTS = False
# 控制debug toolbar的開關
# DEBUG_TB_ENABLED = False
啓動程序後,打開右側懸浮的sidebar,Profile源碼分析器默認關閉,點擊Profile後重新刷新頁面即可打開Profile: