由于 Tornado 提供了自己的 HTTPServer,因此運(yùn)行和部署它與其他 Python Web 框架略有不同。 您無(wú)需配置 WSGI 容器來(lái)查找您的應(yīng)用程序,而是編寫(xiě)一個(gè)啟動(dòng)服務(wù)器的 ?main()
?函數(shù):
def main():
app = make_app()
app.listen(8888)
IOLoop.current().start()
if __name__ == '__main__':
main()
配置您的操作系統(tǒng)或進(jìn)程管理器以運(yùn)行此程序以啟動(dòng)服務(wù)器。 請(qǐng)注意,可能需要增加每個(gè)進(jìn)程的打開(kāi)文件數(shù)(以避免“打開(kāi)文件過(guò)多”-錯(cuò)誤)。 要提高此限制(例如將其設(shè)置為 50000),您可以使用 ?ulimit
?命令,修改 ?/etc/security/limits.conf
? 或在您的 supervisord 配置中設(shè)置 ?minfds
?。
由于 Python GIL(全局解釋器鎖),需要運(yùn)行多個(gè) Python 進(jìn)程才能充分利用多 CPU 機(jī)器。 通常最好每個(gè) CPU 運(yùn)行一個(gè)進(jìn)程。
Tornado 包含一個(gè)內(nèi)置的多進(jìn)程模式,可以同時(shí)啟動(dòng)多個(gè)進(jìn)程(請(qǐng)注意,多進(jìn)程模式在 Windows 上不起作用)。 這需要對(duì)標(biāo)準(zhǔn)的 main 函數(shù)稍作改動(dòng):
def main():
app = make_app()
server = tornado.httpserver.HTTPServer(app)
server.bind(8888)
server.start(0) # forks one process per cpu
IOLoop.current().start()
這是啟動(dòng)多個(gè)進(jìn)程并讓它們都共享同一個(gè)端口的最簡(jiǎn)單方法,盡管它有一些限制。 首先,每個(gè)子進(jìn)程都有自己的 ?IOLoop
?,因此在分叉之前沒(méi)有任何東西(甚至間接地)觸及全局 ?IOLoop
?實(shí)例是很重要的。 其次,在這種模式下很難做到零停機(jī)更新。 最后,由于所有進(jìn)程共享同一個(gè)端口,因此單獨(dú)監(jiān)控它們更加困難。
對(duì)于更復(fù)雜的部署,建議獨(dú)立啟動(dòng)進(jìn)程,并讓每個(gè)進(jìn)程監(jiān)聽(tīng)不同的端口。 supervisord 的“進(jìn)程組”特性是一種很好的安排方式。 當(dāng)每個(gè)進(jìn)程使用不同的端口時(shí),通常需要外部負(fù)載均衡器(例如 HAProxy 或 nginx)來(lái)向外部訪問(wèn)者呈現(xiàn)單個(gè)地址。
在像 nginx 這樣的負(fù)載均衡器后面運(yùn)行時(shí),建議將 ?xheaders=True
? 傳遞給 HTTPServer 構(gòu)造函數(shù)。 這將告訴 Tornado 使用 ?X-Real-IP
? 之類(lèi)的標(biāo)頭來(lái)獲取用戶(hù)的 IP 地址,而不是將所有流量歸因于平衡器的 IP 地址。
這是一個(gè)準(zhǔn)系統(tǒng) nginx 配置文件,其結(jié)構(gòu)類(lèi)似于我們?cè)?nbsp;FriendFeed 使用的配置文件。 它假設(shè) nginx 和 Tornado 服務(wù)器運(yùn)行在同一臺(tái)機(jī)器上,并且四個(gè) Tornado 服務(wù)器運(yùn)行在端口 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;
}
}
}
您可以通過(guò)在應(yīng)用程序中指定 ?static_path
? 設(shè)置從 Tornado 提供靜態(tài)文件:
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)
此設(shè)置將自動(dòng)使所有以 ?/static/
?開(kāi)頭的請(qǐng)求都從該靜態(tài)目錄提供服務(wù),例如 ?http://localhost:8888/static/foo.png
? 將從指定的靜態(tài)目錄提供文件 ?foo.png
?。 我們還自動(dòng)從靜態(tài)目錄提供 ?/robots.txt
? 和 ?/favicon.ico
?(即使它們不以 ?/static/
? 前綴開(kāi)頭)。
在上述設(shè)置中,我們明確配置 Tornado 以使用 StaticFileHandler 從根目錄提供 ?apple-touch-icon.png
?,盡管它在物理上位于靜態(tài)文件目錄中。 (該正則表達(dá)式中的捕獲組對(duì)于告訴 StaticFileHandler 請(qǐng)求的文件名是必要的;回想一下,捕獲組作為方法參數(shù)傳遞給處理程序。)您可以做同樣的事情來(lái)服務(wù),例如 ?sitemap.xml
? 來(lái)自站點(diǎn)根目錄。 當(dāng)然,您也可以通過(guò)在 HTML 中使用適當(dāng)?shù)?nbsp;?<link />
? 標(biāo)簽來(lái)避免偽造根 ?apple-touch-icon.png
?。
為了提高性能,瀏覽器積極緩存靜態(tài)資源通常是一個(gè)好主意,這樣瀏覽器就不會(huì)發(fā)送可能會(huì)阻塞頁(yè)面呈現(xiàn)的不必要的 ?If-Modified-Since
? 或 ?Etag
? 請(qǐng)求。 Tornado 開(kāi)箱即用地支持靜態(tài)內(nèi)容版本控制
要使用此功能,請(qǐng)?jiān)谀0逯惺褂?nbsp;static_url 方法,而不是直接在 HTML 中鍵入靜態(tài)文件的 URL:
<html>
<head>
<title>FriendFeed - {{ _("Home") }}</title>
</head>
<body>
<div><img src="{{ static_url("images/logo.png") }}"/></div>
</body>
</html>
?static_url()
?函數(shù)將該相對(duì)路徑轉(zhuǎn)換為類(lèi)似于 ?/static/images/logo.png?v=aae54
? 的 URI。 ?v
?參數(shù)是 ?logo.png
? 中內(nèi)容的哈希值,它的存在使 Tornado 服務(wù)器向用戶(hù)的瀏覽器發(fā)送緩存表頭,這將使瀏覽器無(wú)限期地緩存內(nèi)容。
由于 ?v
? 參數(shù)基于文件的內(nèi)容,如果您更新文件并重新啟動(dòng)服務(wù)器,它將開(kāi)始發(fā)送新的 ?v
? 值,因此用戶(hù)的瀏覽器將自動(dòng)獲取新文件。 如果文件的內(nèi)容沒(méi)有改變,瀏覽器將繼續(xù)使用本地緩存的副本,而無(wú)需檢查服務(wù)器上的更新,從而顯著提高渲染性能。
在生產(chǎn)中,您可能希望從靜態(tài)文件服務(wù)器(如 nginx)提供靜態(tài)文件。 您可以配置任何 Web 服務(wù)器以識(shí)別 ?static_url()
? 使用的版本標(biāo)簽并相應(yīng)地設(shè)置緩存表頭。 這是我們?cè)?nbsp;FriendFeed 使用的 nginx 配置的相關(guān)部分:
location /static/ {
root /var/friendfeed/static;
if ($query_string) {
expires max;
}
}
如果將 ?debug=True
? 傳遞給 ?Application
?構(gòu)造函數(shù),則應(yīng)用程序?qū)⒁哉{(diào)試/開(kāi)發(fā)模式運(yùn)行。 在此模式下,將啟用幾個(gè)旨在方便開(kāi)發(fā)的功能(每個(gè)功能也可作為單獨(dú)的標(biāo)志使用;如果同時(shí)指定了單獨(dú)的標(biāo)志):
autoreload=True
?:應(yīng)用程序?qū)⒈O(jiān)視其源文件的更改,并在任何更改時(shí)重新加載。 這減少了在開(kāi)發(fā)過(guò)程中手動(dòng)重新啟動(dòng)服務(wù)器的需要。 但是,某些故障(例如導(dǎo)入時(shí)的語(yǔ)法錯(cuò)誤)仍然會(huì)以調(diào)試模式當(dāng)前無(wú)法恢復(fù)的方式使服務(wù)器停機(jī)。compiled_template_cache=False
?:模板不會(huì)被緩存。static_hash_cache=False
?:靜態(tài)文件哈希(由 ?static_url
? 函數(shù)使用)不會(huì)被緩存。serve_traceback=True
?:當(dāng) RequestHandler 中的異常未被捕獲時(shí),將生成包含堆棧跟蹤的錯(cuò)誤頁(yè)面。Autoreload 模式與 HTTPServer 的多進(jìn)程模式不兼容。 如果您使用自動(dòng)重載模式,則不能給 HTTPServer.start 提供 1 以外的參數(shù)(或調(diào)用 tornado.process.fork_processes)。
調(diào)試模式的自動(dòng)重新加載功能在 tornado.autoreload 中作為獨(dú)立模塊提供。 兩者可以結(jié)合使用以提供針對(duì)語(yǔ)法錯(cuò)誤的額外魯棒性:在應(yīng)用程序中設(shè)置 ?autoreload=True
? 以在其運(yùn)行時(shí)檢測(cè)更改,并使用 ?python -m tornado.autoreload myserver.py
? 啟動(dòng)它以捕獲任何語(yǔ)法錯(cuò)誤或其他 啟動(dòng)時(shí)出錯(cuò)。
重新加載會(huì)丟失任何 Python 解釋器命令行參數(shù)(例如 ?-u
?),因?yàn)樗褂?nbsp;sys.executable 和 sys.argv 重新執(zhí)行 Python。 此外,修改這些變量將導(dǎo)致重新加載行為不正確。
在某些平臺(tái)上(包括 10.6 之前的 Windows 和 Mac OSX),進(jìn)程無(wú)法“就地”更新,因此當(dāng)檢測(cè)到代碼更改時(shí),舊服務(wù)器退出并啟動(dòng)新服務(wù)器。 眾所周知,這會(huì)使一些 IDE 感到困惑。
更多建議: