到目前為止,為了簡單起見,在我們的例子中都是使用單一的Tornado進程運行的。這使得測試應用和快速變更非常簡單,但是這不是一個合適的部署策略。部署一個應用到生產環境面臨著新的挑戰,既包括最優化性能,也包括管理獨立進程。本章將介紹強化你的Tornado應用、增加請求吞吐量的策略,以及使得部署Tornado服務器更容易的工具。
[TOC=2,3]
## 8.1 運行多個Tornado實例的原因
在大多數情況下,組合一個網頁不是一個特別的計算密集型處理。服務器需要解析請求,取得適當的數據,以及將多個組件組裝起來進行響應。如果你的應用使用阻塞的調用查詢數據庫或訪問文件系統,那么服務器將不會在等待調用完成時響應傳入的請求。在這些情況下,服務器硬件有剩余的CPU時間來等待I/O操作完成。
鑒于響應一個HTTP請求的時間大部分都花費在CPU空閑狀態下,我們希望利用這個停工時間,最大化給定時間內我們可以處理的請求數量。也就是說,我們希望服務器能夠在處理已打開的請求等待數據的過程中接收盡可能多的新請求。
正如我們在第五章討論的異步HTTP請求中所看到的,Tornado的非阻塞架構在解決這類問題上大有幫助。回想一下,異步請求允許Tornado進程在等待出站請求返回時執行傳入的請求。然而,我們碰到的問題是當同步函數調用塊時。設想在一個Tornado執行的數據庫查詢或磁盤訪問塊中,進程不允許回應新的請求。這個問題最簡單的解決方法是運行多個解釋器的實例。通常情況下,你會使用一個反向代理,比如Nginx,來非配多個Tornado實例的加載。
## 8.2 使用Nginx作為反向代理
一個代理服務器是一臺中轉客戶端資源請求到適當的服務器的機器。一些網絡安裝使用代理服務器過濾或緩存本地網絡機器到Internet的HTTP請求。因為我們將運行一些在不同TCP端口上的Tornado實例,因此我們將使用反向代理服務器:客戶端通過Internet連接一個反向代理服務器,然后反向代理服務器發送請求到代理后端的Tornado服務器池中的任何一個主機。代理服務器被設置為對客戶端透明的,但它會向上游的Tornado節點傳遞一些有用信息,比如原始客戶端IP地址和TCP格式。
我們的服務器配置如圖8-1所示。反向代理接收所有傳入的HTTP請求,然后把它們分配給獨立的Tornado實例。

圖8-1 反向代理服務器后端的Tornado實例
### 8.2.1 Nginx基本配置
代碼清單8-1中的列表是一個Nginx配置的示例。Nginx啟動后監聽來自80端口的連接,然后分配這些請求到配置文件中列出的上游主機。在這種情況下,我們假定上游主機監聽來自他們自己的環回接口上的端口的連接。
代碼清單8-1 一個簡單的Nginx代理配置
~~~
user nginx;
worker_processes 5;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
proxy_next_upstream error;
upstream tornadoes {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
server {
listen 80;
server_name www.example.org *.example.org;
location /static/ {
root /var/www/static;
if ($query_string) {
expires max;
}
}
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://tornadoes;
}
}
~~~
這個配置示例假定你的系統使用了epoll。在不同的UNIX發行版本中經常會有輕微的不同。一些系統可能使用了poll、/dev/poll或kqueue代替。
按順序來看匹配location /static/或location /的請求可能會很有幫助。Nginx把位于location指令中的字符串看作是一個以行起始錨點開始、任何字母重復結束的正則表達式。所以/被看作是表達式^/.*。當Nginx匹配這些字符串時,像/static這樣更加特殊的字符串在像/這樣的更加的通用的字符串之前被檢查。Nginx文檔中詳細解釋了匹配的順序。
除了一些標準樣板外,這個配置文件最重要的部分是upstream指令和服務器配置中的proxy指令。Nginx服務器在80端口監聽連接,然后分配這種請求給upstream服務器組中列出的Tornado實例。proxy_pass指令指定接收轉發請求的服務器URI。你可以在proxy_pass?URI中的主機部分引用upstream服務器組的名字。
Nginx默認以循環的方式分配請求。此外,你也可以選擇基于客戶端的IP地址分配請求,這種情況下(除非連接中斷)可以確保來自同一IP地址的請求總是被分配到同一個上游節點。你可以在[HTTPUpstreamModule文檔](http://wiki.nginx.org/HttpUpstreamModule)中了解更多關于這個選項的知識。
還需要注意的是location /static/指令,它告訴Nginx直接提供靜態目錄的文件,而不再代理請求到Tornado。Nginx可以比Tornado更高效地提供靜態文件,所以減少Tornado進程中不必要的加載是非常有意義的。
### 8.2.2 Nginx的SSL解密
應用的開發者在瀏覽器和客戶端之間傳輸個人信息時需要特別注意保護信息不要落入壞人之手。在不安全的WiFi接入中,用戶很容易受到cookie劫持攻擊,從而威脅他們在流行的社交網站上的賬戶。對此,大部分主要的社交網絡應用都默認或作為用戶可配置選項使用安全協議。同時,我們使用Nginx解密傳入的SSL加密請求,然后把解碼后的HTTP請求分配給上游服務器。
代碼清單8-2展示了一個用于解密傳入的HTTPS請求的server塊,并使用代碼清單8-1中我們使用過的代理指令轉發解密后的通信。
代碼清單8-2 使用SSL的server塊
~~~
server {
listen 443;
ssl on;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/cert.key;
default_type application/octet-stream;
location /static/ {
root /var/www/static;
if ($query_string) {
expires max;
}
}
location = /favicon.ico {
rewrite (.*) /static/favicon.ico;
}
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://tornadoes;
}
}
~~~
這段代碼和上面的配置非常相似,除了Nginx將在標準HTTPS的443端口監聽安全Web請求外。如果你想強制使用SSL連接,你可以在server塊中包含一個rewrite指令來監聽80端口的HTTP連接。代碼清單8-3是這種重定向的一個例子。
代碼清單8-3 用于重定向HTTP請求到安全渠道的server塊
~~~
server {
listen 80;
server_name example.com;
rewrite /(.*) https://$http_host/$1 redirect;
}
~~~
Nginx是一個非常魯棒的工具,我們在這里僅僅接觸到幫助Tornado部署的一些簡單的配置選項。Nginx的[wiki文檔](http://wiki.nginx.org/)是獲得安裝和配置這個強有力的工具額外信息的一個非常好的資源。
## 8.3 使用Supervisor監控Tornado進程
正如8.2節中埋下的伏筆,我們將在我們的Tornado應用中運行多個實例以充分利用現代的多處理器和多核服務器架構。開發團隊大多傳聞每個核運行一個Tornado進程。但是,正如我們所知道的,大量的傳聞并不代表事實,所以你的結果可能不同。在本節中,我們將討論在UNIX系統中管理多個Tornado實例的策略。
到目前為止,我們都是在命令行中運行Tornado服務器的,就像`$ python main.py --port=8000`。但是,再擦河南刮起的生產部署中,這是不可管理的。因為我們為每個CPU核心運行一個獨立的Tornado進程,因此有很多進程需要監控和控制。supervisor守護進程可以幫助我們完成這個任務。
Supervisor的設計是每次開機時啟動其配置文件中列出的進程。這里,我們將看到管理我們在Nginx配置文件中作為上游主機提到的四個Tornado實例的Supervisor配置。典型的supervisord.conf文件中包含了全局的配置指令,并加載conf.d目錄下的其他配置文件。代碼清單8-4展示了我們想啟動的Tornado進程的配置文件。
代碼清單8-4 tornado.conf
~~~
[group:tornadoes]
programs=tornado-8000,tornado-8001,tornado-8002,tornado-8003
[program:tornado-8000]
command=python /var/www/main.py --port=8000
directory=/var/www
user=www-data
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info
[program:tornado-8001]
command=python /var/www/main.py --port=8001
directory=/var/www
user=www-data
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info
[program:tornado-8002]
command=python /var/www/main.py --port=8002
directory=/var/www
user=www-data
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info
[program:tornado-8003]
command=python /var/www/main.py --port=8003
directory=/var/www
user=www-data
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/tornado.log
loglevel=info
~~~
為了Supervisor有意義,你需要至少包含一個program部分。在代碼清單8-4中,我們定義了四個程序,分別命名為tornado-8000到tornado-8003。program部分定義了Supervisor將要運行的每個命令的參數。command的值是必須的,通常是帶有我們希望監聽的port參數的Tornado應用。我們還為每個程序的工作目錄、有效用戶和日志文件定義了額外的設置;而把autorestart和redirect_stderr設置為true是非常有用的。
為了一起管理所有的Tornado進程,創建一個組是很有必要的。在這個例子的頂部,我們聲明了一個叫作tornadoes的組,并在其中列出了每個程序。現在,當我們想要管理我們的Tornado應用時,我們可以通過帶有通配符的組名引用所有的組成程序。比如,要重啟應用時,我們只需要在supervisorctl工具中使用命令`restart tornadoes:*`。
一旦你安裝和配置好Supervisor,你就可以使用supervisorctl來管理supervisord進程。為了啟動你的Web應用,你可以讓Supervisor重新讀取配置,然后任何配置改變的程序或程序組將被重啟。你同樣可以手動啟動、停止和重啟被管理的程序或檢查整個系統的狀態。
~~~
supervisor> update
tornadoes: stopped
tornadoes: updated process group
supervisor> status
tornadoes:tornado-8000 RUNNING pid 32091, uptime 00:00:02
tornadoes:tornado-8001 RUNNING pid 32092, uptime 00:00:02
tornadoes:tornado-8002 RUNNING pid 32093, uptime 00:00:02
tornadoes:tornado-8003 RUNNING pid 32094, uptime 00:00:02
~~~
Supervisor和你系統的初始化進程一起工作,并且它應該在系統啟動時自動注冊守護進程。當supervisor啟動后,程序組會自動在線。默認情況下,Supervisor會監控子進程,并在任何程序意外終止時重生。如果你想不管錯誤碼,重啟被管理的進程,你可以設置autorestart為true。
Supervisor不只可以使管理多個Tornado實例更容易,還能讓你在Tornado服務器遇到意外的服務中斷后重新上線時泰然處之。