Tornado 運(yùn)行和部署

2022-03-08 10:40 更新

由于 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?。

進(jìn)程和端口

由于 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è)地址。

在負(fù)載均衡器后面運(yùn)行

在像 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;
        }
    }
}

靜態(tài)文件和文件緩存

您可以通過(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。 ??參數(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;
    }
 }

調(diào)試模式和自動(dòng)重新加載

如果將 ?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 感到困惑。


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)