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

上一篇《WEB請求處理一:瀏覽器請求發起處理》,咱們講述了瀏覽器端請求發起過程,經過DNS域名解析服務器IP,並創建TCP鏈接,發送HTTP請求。本文將講述請求到達反向代理服務器的一個處理過程,好比:在Nginx中請求的反向代理處理流程,請求都是通過了哪些模塊,作了哪些處理,又是如何找到應用服務器呢?javascript

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

本章所述模塊css

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

1 反向代理#

1.1 概念##

反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個服務器。前端

舉個例子,好比我想訪問 http://www.test.com/readme ,但www.test.com上並不存在readme頁面,因而他是偷偷從另一臺服務器上取回來,而後做爲本身的內容返回用戶,但用戶並不知情。這裏所提到的 www.test.com 這個域名對應的服務器就設置了反向代理功能。java

結論就是,反向代理服務器對於客戶端而言它就像是原始服務器,而且客戶端不須要進行任何特別的設置。客戶端向反向代理的命名空間(name-space)中的內容發送普通請求,接着反向代理服務器將判斷向何處(原始服務器)轉交請求,並將得到的內容返回給客戶端,就像這些內容本來就是它本身的同樣。node

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

正向代理(Forward Proxy)一般都被簡稱爲代理,就是在用戶沒法正常訪問外部資源,比方說受到GFW的影響沒法訪問twitter的時候,咱們能夠經過代理的方式,讓用戶繞過防火牆,從而鏈接到目標網絡或者服務。nginx

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

結論就是,正向代理是一個位於客戶端和原始服務器(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 請求完整處理過程##

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

相關文章
相關標籤/搜索