執行與部署¶
由於 Tornado 提供自己的 HTTPServer,因此其執行與部署方式與其他 Python 網路框架略有不同。您不是設定 WSGI 容器來尋找您的應用程式,而是撰寫一個 main()
函式來啟動伺服器
import asyncio
async def main():
app = make_app()
app.listen(8888)
await asyncio.Event().wait()
if __name__ == '__main__':
asyncio.run(main())
設定您的作業系統或程序管理器來執行此程式以啟動伺服器。請注意,可能需要增加每個程序開啟的檔案數量(以避免「開啟的檔案過多」錯誤)。要提高此限制(例如設定為 50000),您可以使用 ulimit
命令、修改 /etc/security/limits.conf
或在您的 supervisord 設定中設定 minfds
。
程序與連接埠¶
由於 Python GIL(全域解釋器鎖),有必要執行多個 Python 程序才能充分利用多核心機器。通常,最好每個 CPU 執行一個程序。
最簡單的方法是在您的 listen()
呼叫中新增 reuse_port=True
,然後簡單地執行多個應用程式副本。
Tornado 也具有從單一父程序啟動多個程序的能力(請注意,這在 Windows 上無法運作)。這需要對應用程式啟動進行一些變更。
def main():
sockets = bind_sockets(8888)
tornado.process.fork_processes(0)
async def post_fork_main():
server = TCPServer()
server.add_sockets(sockets)
await asyncio.Event().wait()
asyncio.run(post_fork_main())
這是啟動多個程序並讓它們共享同一個連接埠的另一種方法,儘管它有一些限制。首先,每個子程序都會有自己的 IOLoop
,因此重要的是,在 fork 之前,沒有任何東西觸碰到全域 IOLoop
實例(即使是間接的)。其次,在此模型中很難進行零停機更新。最後,由於所有程序共享同一個連接埠,因此更難單獨監控它們。
對於更複雜的部署,建議獨立啟動程序,並讓每個程序監聽不同的連接埠。supervisord 的「程序群組」功能是安排此項目的好方法之一。當每個程序使用不同的連接埠時,通常需要像是 HAProxy 或 nginx 的外部負載平衡器,才能向外部訪客呈現單一地址。
在負載平衡器後方執行¶
當在像是 nginx 的負載平衡器後方執行時,建議將 xheaders=True
傳遞給 HTTPServer
建構函式。這會告訴 Tornado 使用像是 X-Real-IP
的標頭來取得使用者的 IP 位址,而不是將所有流量歸因於平衡器的 IP 位址。
這是一個簡陋的 nginx 設定檔,其結構與我們在 FriendFeed 使用的設定檔類似。它假設 nginx 和 Tornado 伺服器在同一部機器上執行,而四個 Tornado 伺服器在連接埠 8000 - 8003 上執行
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
# Enumerate all the Tornado servers here
upstream frontends {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
keepalive_timeout 65;
proxy_read_timeout 200;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
gzip on;
gzip_min_length 1000;
gzip_proxied any;
gzip_types text/plain text/html text/css text/xml
application/x-javascript application/xml
application/atom+xml text/javascript;
# Only retry if there was a communication error, not a timeout
# on the Tornado server (to avoid propagating "queries of death"
# to all frontends)
proxy_next_upstream error;
server {
listen 80;
# Allow file uploads
client_max_body_size 50M;
location ^~ /static/ {
root /var/www;
if ($query_string) {
expires max;
}
}
location = /favicon.ico {
rewrite (.*) /static/favicon.ico;
}
location = /robots.txt {
rewrite (.*) /static/robots.txt;
}
location / {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://frontends;
}
}
}
靜態檔案和積極的檔案快取¶
您可以透過在應用程式中指定 static_path
設定,從 Tornado 提供靜態檔案
settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
"login_url": "/login",
"xsrf_cookies": True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
(r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
dict(path=settings['static_path'])),
], **settings)
此設定會自動使所有以 /static/
開頭的請求從該靜態目錄提供,例如 https://127.0.0.1:8888/static/foo.png
將從指定的靜態目錄提供檔案 foo.png
。我們也會自動從靜態目錄提供 /robots.txt
和 /favicon.ico
(即使它們不是以 /static/
前置詞開頭)。
在上述設定中,我們已明確設定 Tornado 從根目錄提供 apple-touch-icon.png
,並使用 StaticFileHandler
,儘管它實際位於靜態檔案目錄中。(該正規表示式中的擷取群組對於告訴 StaticFileHandler
請求的檔案名稱是必要的;請回想一下,擷取群組會以方法引數的形式傳遞給處理程式。)您可以執行相同的操作來從網站根目錄提供,例如 sitemap.xml
。當然,您也可以透過在 HTML 中使用適當的 <link />
標籤來避免偽造根 apple-touch-icon.png
。
為了提高效能,通常最好讓瀏覽器積極快取靜態資源,以便瀏覽器不會傳送不必要的 If-Modified-Since
或 Etag
請求,這可能會阻礙頁面的呈現。Tornado 使用靜態內容版本控制開箱即用地支援此功能。
若要使用此功能,請在您的樣板中使用 static_url
方法,而不是直接在 HTML 中鍵入靜態檔案的 URL
<html>
<head>
<title>FriendFeed - {{ _("Home") }}</title>
</head>
<body>
<div><img src="{{ static_url("images/logo.png") }}"/></div>
</body>
</html>
static_url()
函式會將該相對路徑轉譯為看起來像 /static/images/logo.png?v=aae54
的 URI。v
引數是 logo.png
內容的雜湊,其存在會使 Tornado 伺服器將快取標頭傳送至使用者的瀏覽器,這會使瀏覽器無限期地快取內容。
由於 v
引數是以檔案內容為基礎,因此如果您更新檔案並重新啟動伺服器,它將開始傳送新的 v
值,因此使用者的瀏覽器會自動擷取新的檔案。如果檔案的內容沒有變更,瀏覽器將會繼續使用本機快取副本,而不會檢查伺服器上的更新,從而顯著提高呈現效能。
在生產環境中,您可能希望從更最佳化的靜態檔案伺服器(如 nginx)提供靜態檔案。您可以設定幾乎任何網路伺服器來識別 static_url()
使用的版本標籤,並據此設定快取標頭。以下是我們在 FriendFeed 使用的 nginx 設定的相關部分
location /static/ {
root /var/friendfeed/static;
if ($query_string) {
expires max;
}
}
偵錯模式和自動重新載入¶
如果您將 debug=True
傳遞給 Application
建構函式,則應用程式將在偵錯/開發模式下執行。在此模式下,將啟用一些旨在方便開發的功能(每個功能也可以作為個別旗標使用;如果同時指定了兩者,則以個別旗標為優先)
autoreload=True
:應用程式會監看其原始程式碼檔案的變更,並在任何變更時重新載入自身。這減少了在開發期間手動重新啟動伺服器的需要。然而,某些失敗(例如匯入時的語法錯誤)仍然可能以目前偵錯模式無法復原的方式關閉伺服器。compiled_template_cache=False
:樣板將不會被快取。static_hash_cache=False
:靜態檔案雜湊(由static_url
函式使用)將不會被快取。serve_traceback=True
:當RequestHandler
中的例外狀況未被捕獲時,將會產生包含堆疊追蹤的錯誤頁面。
自動重新載入模式與 HTTPServer
的多進程模式不相容。如果您使用自動重新載入模式,您必須避免給 HTTPServer.start
傳入 1 以外的參數(或呼叫 tornado.process.fork_processes
)。
除錯模式的自動重新載入功能在 tornado.autoreload
中以獨立模組的形式提供。這兩者可以結合使用,以提供額外的程式碼錯誤防護:在應用程式中設定 autoreload=True
以偵測執行中的變更,並使用 python -m tornado.autoreload myserver.py
啟動它,以便在啟動時捕獲任何語法錯誤或其他錯誤。
重新載入會遺失任何 Python 直譯器的命令列引數(例如 -u
),因為它會使用 sys.executable
和 sys.argv
重新執行 Python。此外,修改這些變數會導致重新載入行為不正確。
在某些平台(包括 Windows 和 10.6 之前的 Mac OSX)上,無法「就地」更新進程,因此當偵測到程式碼變更時,舊的伺服器會退出,並啟動新的伺服器。已知這會讓某些 IDE 感到困惑。