1. 程式人生 > >WEB 請求處理二:Nginx 請求 反向代理

WEB 請求處理二:Nginx 請求 反向代理

上一篇《WEB請求處理一:瀏覽器請求發起處理》,我們講述了瀏覽器端請求發起過程,通過DNS域名解析伺服器IP,並建立TCP連線,傳送HTTP請求。本文將講述請求到達反向代理伺服器的一個處理過程,比如:在Nginx中請求的反向代理處理流程,請求都是經過了哪些模組,做了哪些處理,又是如何找到應用伺服器呢?

為直觀明瞭,先上一張圖,紅色部分為本章所述模組:

本章所述模組

正如標題所述,Nginx功能是進行請求的反向代理,在講解Nginx請求處理之前,首先要給大家清楚地說明下反向代理是什麼?它的功能是什麼?它在Nginx中又是怎麼配置實現的?

1 反向代理#

1.1 概念##

反向代理(Reverse Proxy)方式是指以代理伺服器來接受internet上的連線請求,然後將請求轉發給內部網路上的伺服器,並將從伺服器上得到的結果返回給internet上請求連線的客戶端,此時代理伺服器對外就表現為一個伺服器。

舉個例子,比如我想訪問 http://www.test.com/readme ,但www.test.com上並不存在readme頁面,於是他是偷偷從另外一臺伺服器上取回來,然後作為自己的內容返回使用者,但使用者並不知情。這裡所提到的 www.test.com 這個域名對應的伺服器就設定了反向代理功能。

結論就是,反向代理伺服器對於客戶端而言它就像是原始伺服器,並且客戶端不需要進行任何特別的設定

。客戶端向反向代理的名稱空間(name-space)中的內容傳送普通請求,接著反向代理伺服器將判斷向何處(原始伺服器)轉交請求,並將獲得的內容返回給客戶端,就像這些內容原本就是它自己的一樣。

正向代理,既然有反向代理,就肯定有正向代理。什麼叫正向代理呢?

正向代理(Forward Proxy)通常都被簡稱為代理,就是在使用者無法正常訪問外部資源,比方說受到GFW的影響無法訪問twitter的時候,我們可以通過代理的方式,讓使用者繞過防火牆,從而連線到目標網路或者服務。

正向代理的工作原理就像一個跳板,比如:我訪問不了google.com,但是我能訪問一個代理伺服器A,A能訪問google.com,於是我先連上代理伺服器A,告訴他我需要google.com的內容,A就去取回來,然後返回給我。從網站的角度,只在代理伺服器來取內容的時候有一次記錄,有時候並不知道是使用者的請求,也隱藏了使用者的資料,這取決於代理告不告訴網站。

結論就是,正向代理是一個位於客戶端和原始伺服器(origin server)之間的伺服器。為了從原始伺服器取得內容,客戶端向代理髮送一個請求並指定目標(原始伺服器),然後代理向原始伺服器轉交請求並將獲得的內容返回給客戶端。

反向代理VS正向代理:

反向代理VS正向代理

1.2 工作流程##

  1. 使用者通過域名發出訪問Web伺服器的請求,該域名被DNS伺服器解析為反向代理伺服器的IP地址;

  2. 反向代理伺服器接受使用者的請求;

  3. 反向代理伺服器在本地快取中查詢請求的內容,找到後直接把內容傳送給使用者;

  4. 如果本地快取裡沒有使用者所請求的資訊內容,反向代理伺服器會代替使用者向源伺服器請求同樣的資訊內容,並把資訊內容發給使用者,如果資訊內容是快取的還會把它儲存到快取中。

1.3 優點##

  1. 保護了真實的web伺服器,web伺服器對外不可見,外網只能看到反向代理伺服器,而反向代理伺服器上並沒有真實資料,因此,保證了web伺服器的資源安全

通常的代理伺服器,只用於代理內部網路對Internet外部網路的連線請求,客戶機必須指定代理伺服器,並將本來要直接傳送到Web伺服器上的http請求傳送到代理伺服器中。不支援外部網路對內部網路的連線請求,因為內部網路對外部網路是不可見的。當一個代理伺服器能夠代理外部網路上的主機,訪問內部網路時,這種代理服務的方式稱為反向代理服務。此時代理伺服器對外就表現為一個Web伺服器,外部網路就可以簡單把它當作一個標準的Web伺服器而不需要特定的配置。不同之處在於,這個伺服器沒有儲存任何網頁的真實資料,所有的靜態網頁或者CGI程式,都儲存在內部的Web伺服器上。因此對反向代理伺服器的攻擊並不會使得網頁資訊遭到破壞,這樣就增強了Web伺服器的安全性。

代理伺服器充當內容伺服器的替身,如果您的內容伺服器具有必須保持安全的敏感資訊,如信用卡號資料庫,可在防火牆外部設定一個代理伺服器作為內容伺服器的替身。當外部客戶機嘗試訪問內容伺服器時,會將其送到代理伺服器。實際內容位於內容伺服器上,在防火牆內部受到安全保護。代理伺服器位於防火牆外部,在客戶機看來就像是內容伺服器

當客戶機向站點提出請求時,請求將轉到代理伺服器。然後,代理伺服器通過防火牆中的特定通路,將客戶機的請求傳送到內容伺服器。內容伺服器再通過該通道將結果回傳給代理伺服器。代理伺服器將檢索到的資訊傳送給客戶機,好像代理伺服器就是實際的內容伺服器。如果內容伺服器返回錯誤訊息,代理伺服器會先行擷取該訊息並更改標頭中列出的任何URL,然後再將訊息傳送給客戶機。如此可防止外部客戶機獲取內部內容伺服器的重定向URL。

這樣,代理伺服器就在安全資料庫和可能的惡意攻擊之間提供了又一道屏障。與有權訪問整個資料庫的情況相對比,就算是僥倖攻擊成功,作惡者充其量也僅限於訪問單個事務中所涉及的資訊。未經授權的使用者無法訪問到真正的內容伺服器,因為防火牆通路只允許代理伺服器有權進行訪問

可以配置防火牆路由器,使其只允許特定埠上的特定伺服器有權通過防火牆進行訪問,而不允許其他任何機器進出。安全反向代理,指當代理伺服器與其他機器之間有一個或多個連線使用安全套接字層 (SSL) 協議加密資料時,即會進行安全反向代理

  1. 節約了有限的IP地址資源

企業內所有的網站共享一個在internet中註冊的IP地址,這些伺服器分配私有地址,採用虛擬主機的方式對外提供服務。

  1. 減少WEB伺服器壓力,提高響應速度

反向代理就是通常所說的web伺服器加速,它是一種通過在繁忙的web伺服器和外部網路之間增加一個高速的web緩衝伺服器來降低實際的web伺服器的負載的一種技術。反向代理是針對web伺服器提高加速功能,作為代理快取,它並不是針對瀏覽器使用者,而針對一臺或多臺特定的web伺服器,它可以代理外部網路對內部網路的訪問請求

反向代理伺服器會強制將外部網路對要代理的伺服器的訪問經過它,這樣反向代理伺服器負責接收客戶端的請求,然後到源伺服器上獲取內容,把內容返回給使用者,並把內容儲存到本地,以便日後再收到同樣的資訊請求時,它會把本地快取裡的內容直接發給使用者,以減少後端web伺服器的壓力,提高響應速度。因此Nginx還具有快取功能。

  1. 其他優點

(1)請求的統一控制,包括設定許可權、過濾規則等;

(2)區分動態和靜態可快取內容;

(3)實現負載均衡,內部可以採用多臺伺服器來組成伺服器叢集,外部還是可以採用一個地址訪問;

(4)解決Ajax跨域問題;

(5)作為真實伺服器的緩衝,解決瞬間負載量大的問題;

2 Nginx常用配置#

寫到這時,一直在由於要不要去開這一節Nginx配置的講解,如果講的話,感覺與本文的主題有所偏離,但又考慮到,如果對Nginx配置檔案都不熟悉的話,下面的內容再去講解Nginx反向代理處理流程就有點紙上談兵了,擔心大家有些雲裡霧裡,毫無收穫。

終究旨在為了要讓大家有所收穫的初衷,決定還是要著重講解Nginx的幾種常見配置,其中包括:動靜分離、快取設定、負載均衡、反向代理、還有虛擬主機功能

2.1 Nginx啟動和關閉##

Mac平臺,我用brew安裝的:

/usr/local/bin/nginx # 啟動
/usr/local/bin/nginx -s reload #平滑重啟
/usr/local/etc/nginx/nginx.cnf #配置檔案。

2.2 配置檔案詳解##

其實,對比,apache的配置檔案,它的相對比較清晰和簡單,之前覺得很難,現在沉下心來想想,其實很簡單。大致的分塊下,基本就分為以下幾塊:

main # 全域性設定
events { # Nginx工作模式
    ....
}
http { # http設定
    ....
    upstream myproject { # 負載均衡伺服器設定
        .....
    }
    server  { # 主機設定
        ....
        location { # URL匹配
            ....
        }
    }
    server  {
        ....
        location {
            ....
        }
    }
    ....
}

2.2.1 main模組###

下面是一個main區域,它是一個全域性的設定:

user nobody nobody;
worker_processes 2;
error_log /usr/local/var/log/nginx/error.log notice;
pid /usr/local/var/run/nginx/nginx.pid;
worker_rlimit_nofile 1024;

user 來指定Nginx Worker程序執行使用者以及使用者組,預設由nobody賬號執行。

worker_processes 來指定了Nginx要開啟的子程序數。每個Nginx程序平均耗費10M~12M記憶體。根據經驗,一般指定1個程序就足夠了,如果是多核CPU,建議指定和CPU的數量一樣的程序數即可。我這裡寫2,那麼就會開啟2個子程序,總共3個程序。

error_log 來定義全域性錯誤日誌檔案。日誌輸出級別有debug、info、notice、warn、error、crit可供選擇,其中,debug輸出日誌最為最詳細,而crit輸出日誌最少。

pid 來指定程序id的儲存檔案位置

worker_rlimit_nofile 來指定一個nginx程序可以開啟的最多檔案描述符數目,這裡是65535,需要使用命令“ulimit -n 65535”來設定。

2.2.2 events模組###

events模組來用指定nginx的工作模式和工作模式及連線數上限,一般是這樣:

events {
    use kqueue; #mac平臺
    worker_connections  1024;
}

use 用來指定Nginx的工作模式。Nginx支援的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。其中select和poll都是標準的工作模式,kqueue和epoll是高效的工作模式,不同的是epoll用在Linux平臺上,而kqueue用在BSD系統中,因為Mac基於BSD,所以Mac也得用這個模式,對於Linux系統,epoll工作模式是首選。

worker_connections 用於定義Nginx每個程序的最大連線數,即接收前端的最大請求數,預設是1024。最大客戶端連線數由worker_processes和worker_connections決定,即Max_clients = worker_processes * worker_connections,在作為反向代理時,Max_clients變為:Max_clients = worker_processes * worker_connections / 4。

程序的最大連線數受Linux系統程序的最大開啟檔案數限制,在執行作業系統命令“ulimit -n 65536”後worker_connections的設定才能生效。

2.2.3 http模組###

http模組可以說是最核心的模組了,它負責HTTP伺服器相關屬性的配置,它裡面的server和upstream子模組,至關重要,等到反向代理和負載均衡以及虛擬目錄等會仔細說。

http {
    include mime.types;
    default_type application/octet-stream;
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    access_log /usr/local/var/log/nginx/access.log  main;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 10;
    #gzip on;
    upstream myproject {
        .....
    }
    server {
        ....
    }
}
  1. include

用來設定檔案的mime型別,型別在配置檔案目錄下的mime.type檔案定義,來告訴nginx來識別檔案型別。

  1. default_type

設定了預設的型別為二進位制流,也就是當檔案型別未定義時使用這種方式,例如在沒有配置asp的locate 環境時,Nginx是不予解析的,此時,用瀏覽器訪問asp檔案就會出現下載視窗了。

  1. log_format

用於設定日誌的格式,和記錄哪些引數,這裡設定為main,剛好用於access_log來紀錄這種型別

main的型別日誌如下:也可以增刪部分引數。

127.0.0.1 - - [21/Apr/2015:18:09:54 +0800] "GET /index.php HTTP/1.1" 200 87151 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36"

  1. access_log

用來紀錄每次的訪問日誌的檔案地址,後面的main是日誌的格式樣式,對應於log_format的main。

  1. sendfile

用於開啟高效檔案傳輸模式。將tcp_nopush和tcp_nodelay兩個指令設定為on用於防止網路阻塞。

  1. keepalive_timeout

設定客戶端連線保持活動的超時時間。在超過這個時間之後,伺服器會關閉該連線。

2.2.4 server模組###

server模組是http的子模組,它用來定一個虛擬主機,我們先講最基本的配置,這些在後面再講。我們看一下一個簡單的server是如何做的?

server {
    listen 8080;
    server_name localhost 192.168.12.10 www.yangyi.com;
    # 全域性定義,如果都是這一個目錄,這樣定義最簡單。
    root   /Users/yangyi/www;
    index  index.php index.html index.htm; 
    charset utf-8;
    access_log  usr/local/var/log/host.access.log  main;
    error_log  usr/local/var/log/host.error.log  error;
    ....
}

server 標誌定義虛擬主機開始。

listen 用於指定虛擬主機的服務埠。

server_name 用來指定IP地址或者域名,多個域名之間用空格分開。

root 表示在這整個server虛擬主機內,全部的root web根目錄。注意要和locate {}下面定義的區分開來。

index 全域性定義訪問的預設首頁地址。注意要和locate {}下面定義的區分開來。

charset 用於設定網頁的預設編碼格式。

access_log 用來指定此虛擬主機的訪問日誌存放路徑,最後的main用於指定訪問日誌的輸出格式。

2.2.5 location模組###

location模組是nginx中用的最多的,也是最重要的模組了,什麼負載均衡啊、反向代理啊、虛擬域名啊都與它相關

location根據它字面意思就知道是來定位的,定位URL,解析URL,所以,它也提供了強大的正則匹配功能,也支援條件判斷匹配,使用者可以通過location指令實現Nginx對動、靜態網頁進行過濾處理。像我們的php環境搭建就是用到了它。

  1. 我們先來看這個,設定預設首頁和虛擬機器目錄
location / {
    root   /Users/yangyi/www;
    index  index.php index.html index.htm;
}

location / 表示匹配訪問根目錄。

root 指令用於指定訪問根目錄時,虛擬主機的web目錄,這個目錄可以是相對路徑(相對路徑是相對於nginx的安裝目錄)。也可以是絕對路徑

index 用於設定我們只輸入域名後訪問的預設首頁地址,有個先後順序:index.php index.html index.htm,如果沒有開啟目錄瀏覽許可權,又找不到這些預設首頁,就會報403錯誤。

  1. location 還有一種方式就是正則匹配,開啟正則匹配這樣:location 。後面加個

下面這個例子是運用正則匹配來連結php。我們之前搭建環境也是這樣做:

location ~ \.php$ {
    root           /Users/yangyi/www;
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    include        fastcgi.conf;
}

.php$ 熟悉正則的我們直到,這是匹配.php結尾的URL,用來解析php檔案。裡面的root也是一樣,用來表示虛擬主機的根目錄

fastcgi_pass 連結的是php-fpm的地址。其他幾個引數我們以後再說。

location 還有其他用法,等講到例項的時候,再看吧。

2.2.6 upstream模組###

upstream 模組負責負載均衡模組,通過一個簡單的排程演算法來實現客戶端IP到後端伺服器的負載均衡。先學習怎麼用,具體的使用例項以後再說。

upstream iyangyi.com{
    ip_hash;
    server 192.168.12.1:80;
    server 192.168.12.2:80 down;
    server 192.168.12.3:8080  max_fails=3  fail_timeout=20s;
    server 192.168.12.4:8080;
}

在上面的例子中,通過upstream指令指定了一個負載均衡器的名稱iyangyi.com。這個名稱可以任意指定,在後面需要的地方直接呼叫即可。裡面是ip_hash這是其中的一種負載均衡排程演算法,下面會著重介紹。緊接著就是各種伺服器了。用server關鍵字表識,後面接ip

Nginx的負載均衡模組目前支援4種排程演算法:

  1. weight 輪詢(預設)。每個請求按時間順序逐一分配到不同的後端伺服器,如果後端某臺伺服器宕機,故障系統被自動剔除,使使用者訪問不受影響。weight。指定輪詢權值,weight值越大,分配到的訪問機率越高,主要用於後端每個伺服器效能不均的情況下。

  2. ip_hash。每個請求按訪問IP的hash結果分配,這樣來自同一個IP的訪客固定訪問一個後端伺服器,有效解決了動態網頁存在的session共享問題。

  3. fair(第三方)。比上面兩個更加智慧的負載均衡演算法。此種演算法可以依據頁面大小和載入時間長短智慧地進行負載均衡,也就是根據後端伺服器的響應時間來分配請求,響應時間短的優先分配。Nginx本身是不支援fair的,如果需要使用這種排程演算法,必須下載Nginx的upstream_fair模組。

  4. url_hash(第三方)。按訪問url的hash結果來分配請求,使每個url定向到同一個後端伺服器,可以進一步提高後端快取伺服器的效率。Nginx本身是不支援url_hash的,如果需要使用這種排程演算法,必須安裝Nginx的hash軟體包。

在HTTP Upstream模組中,可以通過server指令指定後端伺服器的IP地址和埠,同時還可以設定每個後端伺服器在負載均衡排程中的狀態。常用的狀態有:

  1. down,表示當前的server暫時不參與負載均衡。

  2. backup,預留的備份機器。當其他所有的非backup機器出現故障或者忙的時候,才會請求backup機器,因此這臺機器的壓力最輕。

  3. max_fails,允許請求失敗的次數,預設為1。當超過最大次數時,返回proxy_next_upstream 模組定義的錯誤。

  4. fail_timeout,在經歷了max_fails次失敗後,暫停服務的時間。max_fails可以和fail_timeout一起使用。

注意:當負載排程演算法為ip_hash時,後端伺服器在負載均衡排程中的狀態不能是weight和backup。

2.3 基於域名的虛擬主機##

假設我們在本地開發有3個專案,分別在hosts裡對映到本地的127.0.0.1上:

127.0.0.1 www.iyangyi.com iyangyi.com
127.0.0.1 api.iyangyi.com
127.0.0.1 admin.iyangyi.com

有這樣3個專案,分別對應於web根目錄下的3個資料夾,我們用域名對應資料夾名字,這樣子好記:

/Users/yangyi/www/www.iyangyi.com/
/Users/yangyi/www/api.iyangyi.com/
/Users/yangyi/www/admin.iyangyi.com/

每個目錄下都有一個index.php檔案,都是簡單的輸入自己的域名。

下面我們就來搭建這3個域名的虛擬主機,很顯然,我們要新建3個server來完成。建議將對虛擬主機進行配置的內容寫進另外一個檔案,然後通過include指令包含進來,這樣更便於維護和管理。不會使得這個nginx.conf內容太多:

main
events {
    ....
}
http {
    ....
    include vhost/www.iyangyi.conf;
    include vhost/api.iyangyi.conf;
    include vhost/admin.iyangyi.conf;
    # 或者用 *.conf  包含
    # include vhost/*.conf
}

include:主模組指令,實現對配置檔案所包含的檔案的設定,可以減少主配置檔案的複雜度。

既然每一個conf都是一個server,前面已經學習了一個完整的server寫的了。下面就開始:

# www.iyangyi.conf
server {
    listen 80;
    server_name www.iyangyi.com iyangyi.com;

    root /Users/yangyi/www/www.iyangyi.com/;
    index index.php index.html index.htm;

    access_log /usr/local/var/log/nginx/www.iyangyi.access.log main;
    error_log /usr/local/var/log/nginx/www.iyangyi.error.log error;
    
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000; 
        fastcgi_index  index.php;
        include        fastcgi.conf;
    }
}
# api.iyangyi.conf
server {
    listen 80;
    server_name api.iyangyi.com;

    root /Users/yangyi/www/api.iyangyi.com/;
    index index.php index.html index.htm;

    access_log /usr/local/var/log/nginx/api.iyangyi.access.log main;
    error_log /usr/local/var/log/nginx/api.iyangyi.error.log error;
    
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000; 
        fastcgi_index  index.php;
        include        fastcgi.conf;
    }
}
# admin.iyangyi.conf
server {
    listen 80;
    server_name admin.iyangyi.com;

    root /Users/yangyi/www/admin.iyangyi.com/;
    index index.php index.html index.htm;

    access_log /usr/local/var/log/nginx/admin.iyangyi.access.log main;
    error_log /usr/local/var/log/nginx/admin.iyangyi.error.log error;

    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000; 
        fastcgi_index  index.php;
        include        fastcgi.conf;
    }
}

這樣3個很精簡的虛擬域名就搭建好了。重啟下nginx,然後開啟瀏覽器訪問一下這3個域名,就能看到對應的域名內容了。

2.4 反向代理##

Nginx 使用反向代理,主要是使用location模組下的proxy_pass選項。

來個最簡單的。當我訪問 mac 上的nginx 的 centos.iyangyi.com 的內容時候, 就反向代理到虛擬機器centos上的 apache 192.168.33.10 的index.html頁面。

192.168.33.10 中的html 是很簡單的一句輸出:

centos apache2 index.html

在hosts裡新加上這個域名:

#vi /etc/hosts 
127.0.0.1 centos.iyangyi.com

在vhost目錄中新建一個conf server:

#centos.iyangyi.conf
server {
    listen 80;
    server_name centos.iyangyi.com;

    access_log /usr/local/var/log/nginx/centos.iyangyi.access.log main;
    error_log /usr/local/var/log/nginx/centos.iyangyi.error.log error;

    location / {
        proxy_pass http://192.168.33.10;
    }
}

重啟下nginx:

sudo nginx -s reload

當然。proxy 還有其他的引數,比如:proxy_set_header 用來設定header頭部資訊引數轉發等,等用了可以仔細看看。

2.5 負載均衡##

別被這個名字給嚇住了,以為是什麼很牛逼的東西的。其實不然。也很簡單。

先簡單說下負載均衡是幹嘛的?舉個例子:我們的小網站,剛開始就一臺nginx伺服器,後來,隨著業務量增大,使用者增多,一臺伺服器已經不夠用了,我們就又多加了幾臺伺服器。那麼這幾臺伺服器如何排程?如何均勻的提供訪問?這就是負載均衡。

負載均衡的好處是可以叢集多臺機器一起工作,並且對外的IP和域名是一樣的,外界看起來就好像一臺機器一樣。

  1. 基於 weight 權重的負載

先來一個最簡單的,weight權重的:

upstream webservers{
    server 192.168.33.11 weight=10;
    server 192.168.33.12 weight=10;
    server 192.168.33.13 weight=10;
}

server {
    listen 80;
    server_name upstream.iyangyi.com;

    access_log /usr/local/var/log/nginx/upstream.iyangyi.access.log main;
    error_log /usr/local/var/log/nginx/upstream.iyangyi.error.log error;
    
    location / {
        proxy_pass http://webservers;
        proxy_set_header  X-Real-IP  $remote_addr;
    }
}

我們再來繼續看幾個引數 : max_fails和fail_timeout

**max_fails : **允許請求失敗的次數,預設為1。當超過最大次數時,返回proxy_next_upstream 模組定義的錯誤。

**fail_timeout : **在經歷了max_fails次失敗後,暫停服務的時間。max_fails可以和fail_timeout一起使用,進行健康狀態檢查。

upstream webservers{
    server 192.168.33.11 weight=10 max_fails=2 fail_timeout=30s;
    server 192.168.33.12 weight=10 max_fails=2 fail_timeout=30s;
    server 192.168.33.13 weight=10 max_fails=2 fail_timeout=30s;
}

down: 表示這臺機器暫時不參與負載均衡。相當於註釋掉了。

backup: 表示這臺機器是備用機器,是其他的機器不能用的時候,這臺機器才會被使用,俗稱備胎

upstream webservers{
    server 192.168.33.11 down;
    server 192.168.33.12 weight=10 max_fails=2 fail_timeout=30s;
    server 192.168.33.13 backup;
}
  1. 基於 ip_hash 的負載

這種分配方式,每個請求按訪問IP的hash結果分配,這樣來自同一個IP的訪客固定訪問一個後端伺服器,有效解決了動態網頁存在的session共享問題。

upstream webservers{
    ip_hash;
    server 192.168.33.11 weight=1 max_fails=2 fail_timeout=30s;
    server 192.168.33.12 weight=1 max_fails=2 fail_timeout=30s;
    server 192.168.33.13 down;
}

ip_hash 模式下,最好不要設定weight引數,因為你設定了,就相當於手動設定了,將會導致很多的流量分配不均勻。

ip_hash 模式下,backup引數不可用,加了會報錯,為啥呢?因為,本身我們的訪問就是固定的了,其實,備用已經不管什麼作用了。

2.6 頁面快取##

頁面快取也是日常web 開發中很重要的一個環節,對於一些頁面,我們可以將其靜態化,儲存起來,下次請求時候,直接走快取,而不用去請求反相代理伺服器甚至資料庫服務了。從而減輕伺服器壓力。

nginx 也提供了簡單而強大的下重定向,反向代理的快取功能,只需要簡單配置下,就能將指定的一個頁面快取起來。它的原理也很簡單,就是匹配當前訪問的url, hash加密後,去指定的快取目錄找,看有沒有,有的話就說明匹配到快取了。

我們先來看一下一個簡單的頁面快取的配置:

http {
    proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=cache_zone:10m inactive=1d max_size=100m;
    upstream myproject {
        .....
    }
    server  {
        ....
        location ~ *\.php$ {
            proxy_cache cache_zone; #keys_zone的名字
            proxy_cache_key $host$uri$is_args$args; #快取規則
            proxy_cache_valid any 1d;
            proxy_pass http://127.0.0.1:8080;
        }
    }
    ....
}

下面我們來一步一步說。用到的配置引數,主要是proxy_*字首的很多配置。

首先需要在http中加入proxy_cache_path 它用來制定快取的目錄以及快取目錄深度制定等。它的格式如下:

proxy_cache_path path [levels=number] keys_zone=zone_name:zone_size [inactive=time] [max_size=size]; 

path是用來指定 快取在磁碟的路徑地址。比如:/data/nginx/cache。那以後生存的快取檔案就會存在這個目錄下。

levels用來指定快取資料夾的級數,可以是:levels=1, levels=1:1, levels=1:2, levels=1:2:3 可以使用任意的1位或2位數字作為目錄結構分割符,如 X, X:X,或 X:X:X 例如: 2, 2:2, 1:1:2,但是最多隻能是三級目錄。

那這個裡面的數字是什麼意思呢。表示取hash值的個數。比如:

現在根據請求地址localhost/index.php?a=4 用md5進行雜湊,得到e0bd86606797639426a92306b1b98ad9

levels=1:2 表示建立2級目錄,把hash最後1位(9)拿出建一個目錄,然後再把9前面的2位(ad)拿來建一個目錄, 那麼快取檔案的路徑就是/data/nginx/cache/9/ad/e0bd86606797639426a92306b1b98ad9

以此類推:levels=1:1:2表示建立3級目錄,把hash最後1位(9)拿出建一個目錄,然後再把9前面的1位(d)建一個目錄, 最後把d前面的2位(8a)拿出來建一個目錄 那麼快取檔案的路徑就是/data/nginx/cache/9/d/8a/e0bd86606797639426a92306b1b98ad9

keys_zone 所有活動的key和元資料儲存在共享的記憶體池中,這個區域用keys_zone引數指定。zone_name指的是共享池的名稱,zone_size指的是共享池的大小。注意每一個定義的記憶體池必須是不重複的路徑,例如:

proxy_cache_path  /data/nginx/cache/one  levels=1      keys_zone=one:10m;
proxy_cache_path  /data/nginx/cache/two  levels=2:2    keys_zone=two:100m;
proxy_cache_path  /data/nginx/cache/three  levels=1:1:2  keys_zone=three:1000m;

inactive 表示指定的時間內快取的資料沒有被請求則被刪除,預設inactive為10分鐘。inactive=1d 1天。inactive=30m 30分鐘。

max_size 表示單個檔案最大不超過的大小。它被用來刪除不活動的快取和控制快取大小,當目前快取的值超出max_size指定的值之後,超過其大小後最少使用資料(LRU替換演算法)將被刪除。max_size=10g表示當快取池超過10g就會清除不常用的快取檔案。

clean_time 表示每間隔自動清除的時間。clean_time=1m 1分鐘清除一次快取。

好。說完了這個很重要的引數。我們再來說在server模組裡的幾個配置引數:

proxy_cache 用來指定用哪個keys_zone的名字,也就是用哪個目錄下的快取。上面我們指定了三個one, two,three 。比如,我現在想用one 這個快取目錄 : proxy_cache one

proxy_cache_key 這個其實蠻重要的,它用來指定生成hash的url地址的格式。根據這個key對映成一個hash值,然後存入到本地檔案。proxy_cache_key $host$uri表示無論後面跟的什麼引數,都會訪問一個檔案,不會再生成新的檔案。 而如果proxy_cache_key $is_args$args,那麼傳入的引數 localhost/index.php?a=4 與localhost/index.php?a=44 將對映成兩個不同hash值的檔案。

proxy_cache_key 預設是 "$scheme$host$request_uri"。但是一般我們會把它設定成:$host$uri$is_args$args 一個完整的url路徑。

proxy_cache_valid 它是用來為不同的http響應狀態碼設定不同的快取時間。

proxy_cache_valid  200 302  10m;
proxy_cache_valid  404      1m;

表示為http status code 為200和302的設定快取時間為10分鐘,404程式碼快取1分鐘。 如果只定義時間:

proxy_cache_valid 5m;

那麼只對程式碼為200, 301和302的code進行快取。 同樣可以使用any引數任何相響應:

proxy_cache_valid  200 302 10m;
proxy_cache_valid  301 1h;
proxy_cache_valid  any 1m; #所有的狀態都快取1小時

好。快取的基本一些配置講完了。也大致知道了怎麼使用這些引數。現在開始實戰!我們啟動一臺vagrant linux 機器 web1 (192.168.33.11) 用作遠端代理機器,就不搞複雜的負載均衡了。

先在Mac本地加一個域名cache.iyangyi.com, 然後按照上面的配置在vhost 下新建一個proxy_cache.iyangyi.conf 檔案:

proxy_cache_path /usr/local/var/cache levels=1:2 keys_zone=cache_zone:10m inactive=1d max_size=100m;
server  {
    listen 80;
    server_name cache.iyangyi.com;
 
    access_log /usr/local/var/log/nginx/cache.iyangyi.access.log main;
    error_log /usr/local/var/log/nginx/cache.iyangyi.error.log error;
 
    add_header X-Via $server_addr;
    add_header X-Cache $upstream_cache_status;
   
    location / {
        proxy_set_header  X-Real-IP  $remote_addr;
        proxy_cache cache_zone;
        proxy_cache_key $host$uri$is_args$args;
        proxy_cache_valid 200 304 1m;
        proxy_pass http://192.168.33.11;
    }
}

開啟稽核元素或者firebug。看network網路請求選項,我們可以看到,Response Headers,在這裡我們可以看到:

X-Cache:MISS
X-Via:127.0.0.1

X-cache 為 MISS 表示未命中,請求被傳送到後端。因為是第一次訪問,沒有快取,所以肯定是未命中。我們再重新整理下,就發現其變成了HIT, 表示命中。它還有其他幾種狀態:

MISS 未命中,請求被傳送到後端

HIT 快取命中

EXPIRED 快取已經過期請求被傳送到後端

UPDATING 正在更新快取,將使用舊的應答

STALE 後端將得到過期的應答

BYPASS 快取被繞過了

我們再去看看快取資料夾 /usr/local/var/cache裡面是否有了檔案:

cache git:(master) cd a/13
➜  13 git:(master) ls
5bd1af99bcb0db45c8bd601d9ee9e13a
➜  13 git:(master) pwd
/usr/local/var/cache/a/13

已經生成了快取檔案。

我們在url 後面隨便加一個什麼引數,看會不會新生成一個快取資料夾及檔案:http://cache.iyangyi.com/?w=ww55 。因為我們使用的生成規則是全部url轉換(proxy_cache_key $host$uri$is_args$args;)

檢視 X-cache 為 MISS,再重新整理 ,變成HIT。再去看一下快取資料夾 /usr/local/var/cache。

~cache git:(master) ls
 4 a

果然又生成了一個4資料夾。

2.7 location 正則模組##

這一小節,主要來學習nginx中的URL重寫怎麼做。url重寫模組,主要是在location模組面來實現,我們一點一點的看。

首先看下location 正則匹配的使用。還記得之前是如何用location來定位.php檔案的嗎?

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000; 
    fastcgi_index  index.php;
    include        fastcgi.conf;
}

我們用~來表示location開啟正則匹配, 這樣:location ~。還可以用這個來匹配靜態資源,快取它們,設定過期時間:

location ~ .*\.(gif|jpg|jpeg|bmp|png|ico|txt|mp3|mp4|swf){
    expires 15d;
}
location ~ .*\.(css|js){
    expires 12h;
}

expires 用來設定HTTP應答中的Expires和Cache-Control的頭標時間,來告訴瀏覽器訪問這個靜態檔案時,不用再去請求伺服器,直接從本地快取讀取就可以了

語法: expires [time|epoch|max|off]
預設值: expires off
作用域: http, server, location

可以在time值中使用正數或負數。“Expires”頭標的值將通過當前系統時間加上您設定的 time 值來獲得。可以設定的引數如下:

epoch 指定“Expires”的值為 1 January, 1970, 00:00:01 GMT。

max 指定“Expires”的值為 31 December 2037 23:59:59 GMT,“Cache-Control”的值為10年。

-1 指定“Expires”的值為 伺服器當前時間 -1s,即永遠過期。

負數:Cache-Control: no-cache。

正數或零:Cache-Control: max-age = #, # 會轉換為指定時間的秒數。比如:1d、2h、3m。

off 表示不修改“Expires”和“Cache-Control”的值。

比如再看個例子: 控制圖片等過期時間為30天

location ~ \.(gif|jpg|jpeg|png|bmp|ico)$ {
    expires 30d;
}

我們還可以控制哪一個檔案目錄的時間,比如控制匹配/resource/或者/mediatorModule/裡所有的檔案快取設定到最長時間。

location ~ /(resource|mediatorModule)/ {
    root    /opt/demo;
    expires max;
}

2.8 URL重寫模組##

重寫模組與很多模組一起使用。先看一下是怎麼用的,看2個例子,然後我們再一點一點講每個的使用方法:

location /download/ {
    if ($forbidden) {
        return   403;
    }
    if ($slow) {
        limit_rate  10k;
    }
    rewrite ^/(download/.*)/media/(.*)\..*$  /$1/mp3/$2.mp3 break;
    ......
}
location / {
    root   html;
    index  index.html index.htm;
    rewrite ^/bbs/(.*)$ http://192.168.18.201/forum/$1;
}

上面2個例子就是利用rewrite來完成URL重寫的。我們慢慢來看它的用法。

  1. break

break和程式語言中的用法一樣,就是跳出某個邏輯。

語法:break

預設值:none

使用欄位:server, location, if

if (!-f $request_filename) {
    break;
}

上面這個例子就是在if裡面使用break,意思是如果訪問的檔名不存在,就跳出。後續會有更多的例子。

  1. if

if 判斷一個條件,如果條件成立,則後面的大括號內的語句將執行,相關配置從上級繼承。

語法:if (condition) { … }

預設值:none

使用欄位:server, location

可以在判斷語句中指定下列值:

一個變數的名稱;不成立的值為:空字元傳”“或者一些用“0”開始的字串。

一個使用=或者!=運算子的比較語句。

使用符號*和模式匹配的正則表示式:

~為區分大小寫的匹配。

~*不區分大小寫的匹配(firefox匹配FireFox)。

!和!*意為“不匹配的”。

使用-f和!-f檢查一個檔案是否存在。

使用-d和!-d檢查一個目錄是否存在。

使用-e和!-e檢查一個檔案,目錄或者軟連結是否存在。

使用-x和!-x檢查一個檔案是否為可執行檔案。

$http_user_agent變數獲取瀏覽器的agent,使用~ 來匹配大小寫。使用者如果使用的IE 瀏覽器,就執行if裡面的操作。

if ($http_user_agent ~ MSIE) {
    rewrite  ^(.*)$  /msie/$1  break;
}

$request_method變數獲取請求的方法,使用=來判斷是否等於POST 。如果複合,就執行if 裡面的操作。

if ($request_method = POST ) {
    return 405;
}

$request_filename變數獲取請求的檔名,使用!-f來匹配檔案,如果不是一個檔名,就執行if 裡面的邏輯。

if (!-f $request_filename) {
    break;
    proxy_pass  http://127.0.0.1;
}
  1. return

這個指令結束執行配置語句併為客戶端返回狀態程式碼,可以使用下列的值:204,400,402-406,408,410, 411, 413, 416與500-504。此外,非標準程式碼444將關閉連線並且不傳送任何的頭部。

語法:return code

預設值:none

使用欄位:server, location, if

  1. rewrite

語法:rewrite regex replacement flag

預設值:none

使用欄位:server, location, if

rewrite用來重寫url,有3個位置:

regex 表示用來匹配的正則

replacement 表示用來替換的

flag 是尾部的標記

flag可以是以下的值:

last - url重寫後,馬上發起一個新的請求,再次進入server塊,重試location匹配,超過10次匹配不到報500錯誤,位址列url不變

break - url重寫後,直接使用當前資源,不再執行location裡餘下的語句,完成本次請求,位址列url不變

redirect - 返回302臨時重定向,url會跳轉,爬蟲不會更新url。

permanent - 返回301永久重定向。url會跳轉。爬蟲會更新url。

為空 - URL 不會變,但是內容已經變化,也是永久性的重定向。

上面的正則表示式的一部分可以用圓括號,方便之後按照順序用$1-$9來引用。

我們來看幾個例子:

需要將/photos/123456重寫成/path/to/photos/12/1234/123456.png

可以這樣:

rewrite  "/photos/([0-9] {2})([0-9] {2})([0-9] {2})" /path/to/photos/$1/$1$2/$1$2$3.png;

下面是一些簡單的常見的重寫:

rewrite ^/js/base.core.v3.js /js/base.core.v3.dev.js redirect;
rewrite ^/js/comment.frame.js /js/comment.frame.dev.js redirect;
rewrite ^/live-static/(.*)$ http://live.bilibili.com/public/$1 last;

2.9 配置整理##

在此記錄下Nginx伺服器nginx.conf的配置檔案說明, 部分註釋收集與網路:

# 執行使用者
user www-data;    
# 啟動程序,通常設定成和cpu的數量相等
worker_processes  1;

# 全域性錯誤日誌及PID檔案
error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

# 工作模式及連線數上限
events {
    use epoll; #epoll是多路複用IO(I/O Multiplexing)中的一種方式,但是僅用於linux2.6以上核心,可以大大提高nginx的效能
    worker_connections 1024; #單個後臺worker process程序的最大併發連結數
    # multi_accept on; 
}

#設定http伺服器,利用它的反向代理功能提供負載均衡支援
http {
    #設定mime型別,型別由mime.type檔案定義
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    #設定日誌格式
    access_log    /var/log/nginx/access.log;

    #sendfile 指令指定 nginx 是否呼叫 sendfile 函式(zero copy 方式)來輸出檔案,對於普通應用,
    #必須設為 on,如果用來進行下載等應用磁碟IO重負載應用,可設定為 off,以平衡磁碟與網路I/O處理速度,降低系統的uptime.
    sendfile        on;
    #將tcp_nopush和tcp_nodelay兩個指令設定為on用於防止網路阻塞
    tcp_nopush      on;
    tcp_nodelay     on;
    #連線超時時間
    keepalive_timeout  65;
    
    #開啟gzip壓縮
    gzip  on;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";

    #設定請求緩衝
    client_header_buffer_size    1k;
    large_client_header_buffers  4 4k;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

    #設定負載均衡的伺服器列表
    upstream mysvr {
        #weigth引數表示權值,權值越高被分配到的機率越大
        #本機上的Squid開啟3128埠
        server 192.168.8.1:3128 weight=5;
        server 192.168.8.2:80  weight=1;
        server 192.168.8.3:80  weight=6;
    }


    server {
        #偵聽80埠
        listen       80;
        #定義使用www.xx.com訪問
        server_name  www.xx.com;

        #設定本虛擬主機的訪問日誌
        access_log  logs/www.xx.com.access.log  main;

        #預設請求
        location / {
            root   /root;      #定義伺服器的預設網站根目錄位置
            index index.php index.html index.htm;   #定義首頁索引檔案的名稱

            fastcgi_pass  www.xx.com;
            fastcgi_param  SCRIPT_FILENAME  $document_root/$fastcgi_script_name; 
            include /etc/nginx/fastcgi_params;
        }

        # 定義錯誤提示頁面
        error_page   500 502 503 504 /50x.html;  
            location = /50x.html {
            root   /root;
        }

        #靜態檔案,nginx自己處理
        location ~ ^/(images|javascript|js|css|flash|media|static)/ {
            root /var/www/virtual/htdocs;
            #過期30天,靜態檔案不怎麼更新,過期可以設大一點,如果頻繁更新,則可以設定得小一點。
            expires 30d;
        }
        #PHP 指令碼請求全部轉發到 FastCGI處理. 使用FastCGI預設配置.
        location ~ \.php$ {
            root /root;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME /home/www/www$fastcgi_script_name;
            include fastcgi_params;
        }
        #設定檢視Nginx狀態的地址
        location /NginxStatus {
            stub_status            on;
            access_log              on;
            auth_basic              "NginxStatus";
            auth_basic_user_file  conf/htpasswd;
        }
        #禁止訪問 .htxxx 檔案
        location ~ /\.ht {
            deny all;
        }
     
    }

    #第一個虛擬伺服器
    server {
        #偵聽192.168.8.x的80埠
        listen       80;
        server_name  192.168.8.x;

        #對aspx字尾的進行負載均衡請求
        location ~ .*\.aspx$ {
            root   /root;#定義伺服器的預設網站根目錄位置
            index index.php index.html index.htm;#定義首頁索引檔案的名稱

            proxy_pass  http://mysvr;#請求轉向mysvr 定義的伺服器列表

            #以下是一些反向代理的配置可刪除.
            proxy_redirect off;

            #後端的Web伺服器可以通過X-Forwarded-For獲取使用者真實IP
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            client_max_body_size 10m;    #允許客戶端請求的最大單檔案位元組數
            client_body_buffer_size 128k;  #緩衝區代理緩衝使用者端請求的最大位元組數,
            proxy_connect_timeout 90;  #nginx跟後端伺服器連線超時時間(代理連線超時)
            proxy_send_timeout 90;        #後端伺服器資料回傳時間(代理髮送超時)
            proxy_read_timeout 90;         #連線成功後,後端伺服器響應時間(代理接收超時)
            proxy_buffer_size 4k;             #設定代理伺服器(nginx)儲存使用者頭資訊的緩衝區大小
            proxy_buffers 4 32k;               #proxy_buffers緩衝區,網頁平均在32k以下的話,這樣設定
            proxy_busy_buffers_size 64k;    #高負荷下緩衝大小(proxy_buffers*2)
            proxy_temp_file_write_size 64k;  #設定快取資料夾大小,大於這個值,將從upstream伺服器傳
        }
    }
}

3 Nginx模組#

上面我們已經詳細講解了Nginx常用配置,從中我們已經體會到了,Nginx模組化配置的優點。其中,模組化設計類似於面向物件中的介面類,它增強了nginx原始碼的可讀性、可擴充性和可維護性。

所以,Nginx有五大優點:模組化、事件驅動、非同步、非阻塞、多程序單執行緒。由核心和模組組成的,其中核心完成的工作比較簡單,僅僅通過查詢配置檔案將客戶端請求對映到一個location block,然後又將這個location block中所配置的每個指令將會啟動不同的模組去完成相應的工作。

3.1 模組劃分##

Nginx的模組從結構上分為核心模組、基礎模組和第三方模組:

核心模組:HTTP模組、EVENT模組和MAIL模組

基礎模組:HTTP Access模組、HTTP FastCGI模組、HTTP Proxy模組和HTTP Rewrite模組,

第三方模組:HTTP Upstream Request Hash模組、Notice模組和HTTP Access Key模組。

Nginx的模組從功能上分為如下三類:

Core(核心模組):構建nginx基礎服務、管理其他模組。

Handlers(處理器模組):此類模組直接處理請求,並進行輸出內容和修改headers資訊等操作。Handlers處理器模組一般只能有一個。

Filters (過濾器模組):此類模組主要對其他處理器模組輸出的內容進行修改操作,最後由Nginx輸出。

Proxies (代理類模組):此類模組是Nginx的HTTP Upstream之類的模組,這些模組主要與後端一些服務比如FastCGI等進行互動,實現服務代理和負載均衡等功能。

Nginx的核心模組主要負責建立nginx服務模型、管理網路層和應用層協議、以及啟動針對特定應用的一系列候選模組。其他模組負責分配給web伺服器的實際工作:

(1) 當Nginx傳送檔案或者轉發請求到其他伺服器,由Handlers(處理模組)或Proxies(代理類模組)提供服務;

(2) 當需要Nginx把輸出壓縮或者在服務端加一些東西,由Filters(過濾模組)提供服務。

3.2 模組處理##

  1. 當伺服器啟動,每個handlers(處理模組)都有機會對映到配置檔案中定義的特定位置(location);如果有多個handlers(處理模組)對映到特定位置時,只有一個會“贏”(說明配置檔案有衝突項,應該避免發生)。

處理模組以三種形式返回:

OK

ERROR

或者放棄處理這個請求而讓預設處理模組來處理(主要是用來處理一些靜態檔案,事實上如果是位置正確而真實的靜態檔案,預設的處理模組會搶先處理)。

  1. 如果handlers(處理模組)把請求反向代理到後端的伺服器,就變成另外一類的模組:load-balancers(負載均衡模組)。負載均衡模組的配置中有一組後端伺服器,當一個HTTP請求過來時,它決定哪臺伺服器應當獲得這個請求。

Nginx的負載均衡模組採用兩種方法:

輪轉法,它處理請求就像紙牌遊戲一樣從頭到尾分發;

IP雜湊法,在眾多請求的情況下,它確保來自同一個IP的請求會分發到相同的後端伺服器。

  1. 如果handlers(處理模組)沒有產生錯誤,filters(過濾模組)將被呼叫。多個filters(過濾模組)能對映到每個位置,所以(比如)每個請求都可以被壓縮成塊。它們的執行順序在編譯時決定。

filters(過濾模組)是經典的“接力連結串列(CHAIN OF RESPONSIBILITY)”模型:一個filters(過濾模組)被呼叫,完成其工作,然後呼叫下一個filters(過濾模組),直到最後一個filters(過濾模組)。

過濾模組鏈的特別之處在於:

每個filters(過濾模組)不會等上一個filters(過濾模組)全部完成;

它能把前一個過濾模組的輸出作為其處理內容;有點像Unix中的流水線;

過濾模組能以buffer(緩衝區)為單位進行操作,這些buffer一般都是一頁(4K)大小,當然你也可以在nginx.conf檔案中進行配置。這意味著,比如,模組可以壓縮來自後端伺服器的響應,然後像流一樣的到達客戶端,直到整個響應傳送完成。

總之,過濾模組鏈以流水線的方式高效率地向客戶端傳送響應資訊。

  1. 所以總結下上面的內容,一個典型的HTTP處理週期是這樣的:

客戶端傳送HTTP請求 –>

Nginx基於配置檔案中的位置選擇一個合適的處理模組 ->

(如果有)負載均衡模組選擇一臺後端伺服器 –>

處理模組進行處理並把輸出緩衝放到第一個過濾模組上 –>

第一個過濾模組處理後輸出給第二個過濾模組 –>

然後第二個過濾模組又到第三個 –>

依此類推 –> 最後把響應發給客戶端。

下圖展示了Nginx模組處理流程:

Nginx模組處理流程

Nginx本身做的工作實際很少,當它接到一個HTTP請求時,它僅僅是通過查詢配置檔案將此次請求對映到一個location block,而此location中所配置的各個指令則會啟動不同的模組去完成工作,因此模組可以看做Nginx真正的勞動工作者。通常一個location中的指令會涉及一個handler模組和多個filter模組(當然,多個location可以複用同一個模組)。handler模組負責處理請求,完成響應內容的生成,而filter模組對響應內容進行處理

4 Nginx請求處理#

Nginx在啟動時會以daemon形式在後臺執行,採用多程序+非同步非阻塞IO事件模型來處理各種連線請求。多程序模型包括一個master程序,多個worker程序,一般worker程序個數是根據伺服器CPU核數來決定的master程序負責管理Nginx本身和其他worker程序。如下圖:

Master程序負責管理Nginx本身和其他worker程序

從上圖中可以很明顯地看到,4個worker程序的父程序都是master程序,表明worker程序都是從父程序fork出來的,並且父程序的ppid為1,表示其為daemon程序。

需要說明的是,在nginx多程序中,每個worker都是平等的,因此每個程序處理外部請求的機會權重都是一致的。

Nginx架構及工作流程圖:

Nginx架構及工作流程圖

Nginx的每一個Worker程序都管理著大量的執行緒,真正處理請求業務的是Worker之下的執行緒。worker程序中有一個ngx_worker_process_cycle()函式,執行無限迴圈,不斷處理收到的來自客戶端的請求,並進行處理,直到整個Nginx服務被停止。

worker 程序中,ngx_worker_process_cycle()函式就是這個無限迴圈的處理函式。在這個函式中,一個請求的簡單處理流程如下:

  1. 作業系統提供的機制(例如 epoll, kqueue 等)產生相關的事件。

  2. 接收和處理這些事件,如是接收到資料,則產生更高層的 request 物件。

  3. 處理 request 的 header 和 body。

  4. 產生響應,併發送回客戶端。

  5. 完成 request 的處理。

  6. 重新初始化定時器及其他事件。

4.1 多程序處理模型##

下面來介紹一個請求進來,多程序模型的處理方式:

首先,master程序一開始就會根據我們的配置,來建立需要listen的網路socket fd,然後fork出多個worker程序。

其次,根據程序的特性,新建立的worker程序,也會和master程序一樣,具有相同的設定。因此,其也會去監聽相同ip埠的套接字socket fd

然後,這個時候有多個worker程序都在監聽同樣設定的socket fd,意味著當有一個請求進來的時候,所有的worker都會感知到。這樣就會產生所謂的“驚群現象”。為了保證只會有一個程序成功註冊到listenfd的讀事件,nginx中實現了一個“accept_mutex”類似互斥鎖,只有獲取到這個鎖的程序,才可以去註冊讀事件。其他程序全部accept 失敗。

最後,監聽成功的worker程序,讀取請求,解析處理,響應資料返回給客戶端,斷開連線,結束。因此,一個request請求,只需要worker程序就可以完成。

程序模型的處理方式帶來的一些好處就是:程序之間是獨立的,也就是一個worker程序出現異常退出,其他worker程序是不會受到影響的;此外,獨立程序也會避免一些不需要的鎖操作,這樣子會提高處理效率,並且開發除錯也更容易。

如前文所述,多程序模型+非同步非阻塞模型才是勝出的方案。單純的多程序模型會導致連線併發數量的降低,而採用非同步非阻塞IO模型很好的解決了這個問題;並且還因此避免的多執行緒的上下文切換導致的效能損失。

worker程序會競爭監聽客戶端的連線請求:這種方式可能會帶來一個問題,就是可能所有的請求都被一個worker程序給競爭獲取了,導致其他程序都比較空閒,而某一個程序會處於忙碌的狀態,這種狀態可能還會導致無法及時響應連線