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進程給競爭獲取了,導致其他進程都比較空閑,而某一個進程會處于忙碌的狀態,這種狀態可能還會導致無法及時響應連接而丟棄discard掉本有能力處理的請求。這種不公平的現象,是需要避免的,尤其是在高可靠web服務器環境下。

針對這種現象,Nginx采用了一個是否打開accept_mutex選項的值,ngx_accept_disabled標識控制一個worker進程是否需要去競爭獲取accept_mutex選項,進而獲取accept事件

ngx_accept_disabled值,nginx單進程的所有連接總數的八分之一,減去剩下的空閑連接數量,得到的這個ngx_accept_disabled。

當ngx_accept_disabled大于0時,不會去嘗試獲取accept_mutex鎖,并且將ngx_accept_disabled減1,于是,每次執行到此處時,都會去減1,直到小于0。不去獲取accept_mutex鎖,就是等于讓出獲取連接的機會,很顯然可以看出,當空閑連接越少時,ngx_accept_disable越大,于是讓出的機會就越多,這樣其它進程獲取鎖的機會也就越大。不去accept,自己的連接就控制下來了,其它進程的連接池就會得到利用,這樣,nginx就控制了多進程間連接的平衡了。

4.2 一個簡單的HTTP請求##

從 Nginx 的內部來看,一個 HTTP Request 的處理過程涉及到以下幾個階段:

初始化 HTTP Request(讀取來自客戶端的數據,生成 HTTP Request 對象,該對象含有該請求所有的信息)。

處理請求頭。

處理請求體。

如果有的話,調用與此請求(URL 或者 Location)關聯的 handler。

依次調用各 phase handler 進行處理。

在建立連接過程中,對于nginx監聽到的每個客戶端連接,都會將它的讀事件的handler設置為ngx_http_init_request函數,這個函數就是請求處理的入口。在處理請求時,主要就是要解析http請求,比如:uri,請求行等,然后再根據請求生成響應。下面看一下nginx處理的具體過程:

Nginx處理的具體過程

在這里,我們需要了解一下 phase handler 這個概念。phase 字面的意思,就是階段。所以 phase handlers 也就好理解了,就是包含若干個處理階段的一些 handler

在每一個階段,包含有若干個 handler,再處理到某個階段的時候,依次調用該階段的 handler 對 HTTP Request 進行處理。

通常情況下,一個 phase handler 對這個 request 進行處理,并產生一些輸出。通常 phase handler 是與定義在配置文件中的某個 location 相關聯的

一個 phase handler 通常執行以下幾項任務:

獲取 location 配置。

產生適當的響應。

發送 response header。

發送 response body。

當 Nginx 讀取到一個 HTTP Request 的 header 的時候,Nginx 首先查找與這個請求關聯的虛擬主機的配置。如果找到了這個虛擬主機的配置,那么通常情況下,這個 HTTP Request 將會經過以下幾個階段的處理(phase handlers):

NGX_HTTP_POST_READ_PHASE: 讀取請求內容階段

NGX_HTTP_SERVER_REWRITE_PHASE: Server 請求地址重寫階段

NGX_HTTP_FIND_CONFIG_PHASE: 配置查找階段

NGX_HTTP_REWRITE_PHASE: Location請求地址重寫階段

NGX_HTTP_POST_REWRITE_PHASE: 請求地址重寫提交階段

NGX_HTTP_PREACCESS_PHASE: 訪問權限檢查準備階段

NGX_HTTP_ACCESS_PHASE: 訪問權限檢查階段

NGX_HTTP_POST_ACCESS_PHASE: 訪問權限檢查提交階段

NGX_HTTP_TRY_FILES_PHASE: 配置項 try_files 處理階段

NGX_HTTP_CONTENT_PHASE: 內容產生階段

NGX_HTTP_LOG_PHASE: 日志模塊處理階段

在內容產生階段,為了給一個 request 產生正確的響應,Nginx 必須把這個 request 交給一個合適的 content handler 去處理。如果這個 request 對應的 location 在配置文件中被明確指定了一個 content handler,那么Nginx 就可以通過對 location 的匹配,直接找到這個對應的 handler,并把這個 request 交給這個 content handler 去處理。這樣的配置指令包括像,perl,flv,proxy_pass,mp4等。

如果一個 request 對應的 location 并沒有直接有配置的 content handler,那么 Nginx 依次嘗試:

如果一個 location 里面有配置 random_index on,那么隨機選擇一個文件,發送給客戶端。

如果一個 location 里面有配置 index 指令,那么發送 index 指令指明的文件,給客戶端。

如果一個 location 里面有配置 autoindex on,那么就發送請求地址對應的服務端路徑下的文件列表給客戶端。

如果這個 request 對應的 location 上有設置 gzip_static on,那么就查找是否有對應的.gz文件存在,有的話,就發送這個給客戶端(客戶端支持 gzip 的情況下)。

請求的 URI 如果對應一個靜態文件,static module 就發送靜態文件的內容到客戶端。

內容產生階段完成以后,生成的輸出會被傳遞到 filter 模塊去進行處理。filter 模塊也是與 location 相關的。所有的 fiter 模塊都被組織成一條鏈。輸出會依次穿越所有的 filter,直到有一個 filter 模塊的返回值表明已經處理完成。

這里列舉幾個常見的 filter 模塊,例如:

server-side includes。

XSLT filtering。

圖像縮放之類的。

gzip 壓縮。

在所有的 filter 中,有幾個 filter 模塊需要關注一下。按照調用的順序依次說明如下:

copy: 將一些需要復制的 buf(文件或者內存)重新復制一份然后交給剩余的 body filter 處理。

postpone: 這個 filter 是負責 subrequest 的,也就是子請求的。

write: 寫輸出到客戶端,實際上是寫到連接對應的 socket 上。

4.3 請求完整處理過程##

根據以上請求步驟所述,請求完整的處理過程如下圖所示:

請求完整的處理過程
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 大多數 Nginx 新手都會頻繁遇到這樣一個困惑,那就是當同一個location配置塊使用了多個 Nginx 模塊...
    SkTj閱讀 7,807評論 0 12
  • Nginx簡介 解決基于進程模型產生的C10K問題,請求時即使無狀態連接如web服務都無法達到并發響應量級一萬的現...
    魏鎮坪閱讀 2,050評論 0 9
  • I/O模型: 阻塞型、非阻塞型、復用型、信號驅動型、異步 同步/異步:關注消息通知機制 消息通知:同步:等待對方返...
    Net夜風閱讀 2,027評論 0 1
  • 1.簡介: ? Nginx:engine X ,2002年,開源,商業版? http協議:web服務器(類似于ht...
    尛尛大尹閱讀 1,895評論 0 3
  • I/O模型Nginx介紹Nginx的安裝和目錄結構Nginx的配置Nginx的編譯安裝 一、I/O模型 (一)I/...
    哈嘍別樣閱讀 910評論 0 4