1. 程式人生 > >Nginx的基本功能和工作原理

Nginx的基本功能和工作原理

Nginx的基本功能

反向代理
正向代理
負載均衡
HTTP伺服器(包含動靜分離)

反向代理和正向代理

正向代理:

簡單的說,我是一個使用者,我無法直接訪問一個網站,但是我能訪問一個代理伺服器,這個代理伺服器能訪問那個我不能訪問的網站,於
是我先連上代理伺服器,告訴它我需要那個無法訪問網站的內容,代理伺服器去取回來,然後返回給我。從網站的角度,只在代理伺服器來
取內容的時候有一次記錄。結論就是,正向代理,是一個位於客戶端和原始伺服器(origin server)之間的伺服器,為了從原始服務
器取得內容,客戶端向代理髮送一個請求並指定目標(原始伺服器),然後代理向原始伺服器轉交請求並將獲得的內容返回給客戶端。客
戶端必須要進行一些特別的設定才能使用正向代理。

反向代理:

例如我要訪問 localhost:8080/views/test1 這個頁面,但view對應的伺服器上並沒有test1這個資源,它是從另一臺服務
器上呼叫的資源。這樣view對應的那個伺服器就使用了反向代理。即使用者只需要把請求發給特定的反向代理伺服器,具體請求是誰處理
的使用者不需要知道(其實也不知道),由代理伺服器統一處理。結論就是 反向代理正好相反,對於客戶端而言它就像是原始伺服器,並
且客戶端不需要進行任何特別的設定。客戶端向反向代理 的名稱空間(name-space)中的內容傳送普通請求,接著反向代理將判斷
向何處(原始伺服器)轉交請求,並將獲得的內容返回給客戶端,就像這些內容 原本就是它自己的一樣。

正向代理的典型用途

是為在防火牆內的區域網客戶端提供訪問Internet的途徑。正向代理還可以使用緩衝特性減少網路使用率。

反向代理的典型用途

是將 防火牆後面的伺服器提供給Internet使用者訪問。反向代理還可以為後端的多臺伺服器提供負載平衡,或為後端較慢的伺服器提
供緩衝服務。

正向代理和反向代理總結

正向代理:針對客戶端而言, 代理伺服器代理客戶端,轉發請求,並將獲得的內容返回給客戶端。
反向代理:針對客戶端而言, 代理伺服器就像是原始伺服器,代理叢集的web節點伺服器返回結果。

負載均衡

負載均衡也是Nginx常用的一個功能,負載均衡其意思就是分攤到多個操作單元上進行執行,例如Web伺服器、FTP伺服器、企業關鍵應用伺服器和其它關鍵任務伺服器等,從而共同完成工作任務。簡單而言就是當有2臺或以上伺服器時,根據規則隨機的將請求分發到指定的伺服器上處理,負載均衡配置一般都需要同時配置反向代理,通過反向代理跳轉到負載均衡。而Nginx目前支援自帶3種負載均衡策略,還有2種常用的第三方策略。

1、RR

按照輪詢(預設)方式進行負載,每個請求按時間順序逐一分配到不同的後端伺服器,如果後端伺服器down掉,能自動剔除。雖然
這種方式簡便、成本低廉。但缺點是:可靠性低和負載分配不均衡。

2、權重

指定輪詢機率,weight和訪問比率成正比,用於後端伺服器效能不均的情況。
    
upstream test{
     server localhost:8080 weight=9;             ##8081佔90%
     server localhost:8081 weight=1;             ##8081佔10%
}

3、fair(第三方)
upstream-fair模組

按後端伺服器的響應時間來分配請求,響應時間短的優先分配。

upstream backend { 
    fair; 
    server localhost:8080;
    server localhost:8081;
}

4、url_hash
採用Haproxy的loadbalance或者Nginx的upstream_hash模組都能做到針對url的雜湊演算法式的負載均衡轉發

按訪問url的hash結果來分配請求,使每個url定向到同一個後端伺服器,後端伺服器為快取時比較有效。 在upstream中加入
hash語句,server語句中不能寫入weight等其他的引數,hash_method是使用的hash演算法。

upstream backend { 
    hash $request_uri; 
    hash_method crc32; 
    server localhost:8080;
    server localhost:8081;
} 

HTTP伺服器

Nginx本身也是一個靜態資源的伺服器,當只有靜態資源的時候,就可以使用Nginx來做伺服器,同時現在也很流行動靜分離,就可
以通過Nginx來實現,動靜分離是讓動態網站裡的動態網頁根據一定規則把不變的資源和經常變的資源區分開來,動靜資源做好了拆
分以後,我們就可以根據靜態資源的特點將其做快取操作,這就是網站靜態化處理的核心思路。

Nginx程序模型

`在工作方式上,Nginx分為單工作程序和多工作程序兩種模式。在單工作程序模式下,除主程序外,還有一個工作程序,工作進
程是單執行緒的;在多工作程序模式下,每個工作程序包含多個執行緒。Nginx預設為單工作程序模式`。

Nginx在啟動後,會有一個master程序和多個worker程序。

master程序

而基本的網路事件,則是放在worker程序中來處理了。多個worker程序之間是對等的,他們同等競爭來自客戶端的請求,各程序互相之間是獨立的。一個請求,只可能在一個worker程序中處理,一個worker程序,不可能處理其它程序的請求。worker程序的個數是可以設定的,一般我們會設定與機器cpu核數一致,這裡面的原因與nginx的程序模型以及事件處理模型是分不開的。

worker程序之間是平等的,每個程序,處理請求的機會也是一樣的。當我們提供80埠的http服務時,一個連線請求過來,每個程序都有可能處理這個連線,怎麼做到的呢?首先,每個worker程序都是從master程序fork過來,在master程序裡面,先建立好需要listen的socket(listenfd)之後,然後再fork出多個worker程序。

所有worker程序的listenfd會在新連線到來時變得可讀,為保證只有一個程序處理該連線,所有worker程序在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個程序註冊listenfd讀事件,在讀事件裡呼叫accept接受該連線。當一個worker程序在accept這個連線之後,就開始讀取請求,解析請求,處理請求,產生資料後,再返回給客戶端,最後才斷開連線,這樣一個完整的請求就是這樣的了。

我們可以看到,一個請求,完全由worker程序來處理,而且只在一個worker程序中處理。worker程序之間是平等的,每個程序,處理請求的機會也是一樣的。當我們提供80埠的http服務時,一個連線請求過來,每個程序都有可能處理這個連線,怎麼做到的呢?首先,每個worker程序都是從master程序fork過來,在master程序裡面,先建立好需要listen的socket(listenfd)之後,然後再fork出多個worker程序。

所有worker程序的listenfd會在新連線到來時變得可讀,為保證只有一個程序處理該連線,所有worker程序在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個程序註冊listenfd讀事件,在讀事件裡呼叫accept接受該連線。當一個worker程序在accept這個連線之後,就開始讀取請求,解析請求,處理請求,產生資料後,再返回給客戶端,最後才斷開連線,這樣一個完整的請求就是這樣的了。我們可以看到,一個請求,完全由worker程序來處理,而且只在一個worker程序中處理。

worker程序

而基本的網路事件,則是放在worker程序中來處理了。多個worker程序之間是對等的,他們同等競爭來自客戶端的請求,各程序互相之間是獨立的。一個請求,只可能在一個worker程序中處理,一個worker程序,不可能處理其它程序的請求。worker程序的個數是可以設定的,一般我們會設定與機器cpu核數一致,這裡面的原因與nginx的程序模型以及事件處理模型是分不開的。

worker程序之間是平等的,每個程序,處理請求的機會也是一樣的。當我們提供80埠的http服務時,一個連線請求過來,每個程序都有可能處理這個連線,怎麼做到的呢?首先,每個worker程序都是從master程序fork過來,在master程序裡面,先建立好需要listen的socket(listenfd)之後,然後再fork出多個worker程序。

所有worker程序的listenfd會在新連線到來時變得可讀,為保證只有一個程序處理該連線,所有worker程序在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個程序註冊listenfd讀事件,在讀事件裡呼叫accept接受該連線。當一個worker程序在accept這個連線之後,就開始讀取請求,解析請求,處理請求,產生資料後,再返回給客戶端,最後才斷開連線,這樣一個完整的請求就是這樣的了。

我們可以看到,一個請求,完全由worker程序來處理,而且只在一個worker程序中處理。worker程序之間是平等的,每個程序,處理請求的機會也是一樣的。當我們提供80埠的http服務時,一個連線請求過來,每個程序都有可能處理這個連線,怎麼做到的呢?首先,每個worker程序都是從master程序fork過來,在master程序裡面,先建立好需要listen的socket(listenfd)之後,然後再fork出多個worker程序。

所有worker程序的listenfd會在新連線到來時變得可讀,為保證只有一個程序處理該連線,所有worker程序在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個程序註冊listenfd讀事件,在讀事件裡呼叫accept接受該連線。當一個worker程序在accept這個連線之後,就開始讀取請求,解析請求,處理請求,產生資料後,再返回給客戶端,最後才斷開連線,這樣一個完整的請求就是這樣的了。我們可以看到,一個請求,完全由worker程序來處理,而且只在一個worker程序中處理。

多程序IO模型好處

首先,對於每個worker程序來說,獨立的程序,不需要加鎖,所以省掉了鎖帶來的開銷,同時在程式設計以及問題查詢時,也會方便很多。

其次,採用獨立的程序,可以讓互相之間不會影響,一個程序退出後,其它程序還在工作,服務不會中斷,master程序則很快啟動新的worker程序。當然,worker程序的異常退出,肯定是程式有bug了,異常退出,會導致當前worker上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。

Nginx多程序事件模型:非同步非阻塞

雖然nginx採用多worker的方式來處理請求,每個worker裡面只有一個主執行緒,那能夠處理的併發數很有限啊,多少個worker就能處理多少個併發,何來高併發呢?非也,這就是nginx的高明之處,nginx採用了非同步非阻塞的方式來處理請求,也就是說,nginx是可以同時處理成千上萬個請求的。

一個worker程序可以同時處理的請求數只受限於記憶體大小,而且在架構設計上,不同的worker程序之間處理併發請求時幾乎沒有同步鎖的限制,worker程序通常不會進入睡眠狀態,因此,當Nginx上的程序數與CPU核心數相等時(最好每一個worker程序都繫結特定的CPU核心),程序間切換的代價是最小的。

而apache的常用工作方式(apache也有非同步非阻塞版本,但因其與自帶某些模組衝突,所以不常用),每個程序在一個時刻只處理一個請求,因此,當併發數上到幾千時,就同時有幾千的程序在處理請求了。這對作業系統來說,是個不小的挑戰,程序帶來的記憶體佔用非常大,程序的上下文切換帶來的cpu開銷很大,自然效能就上不去了,而這些開銷完全是沒有意義的。

為什麼nginx可以採用非同步非阻塞的方式來處理呢,或者非同步非阻塞到底是怎麼回事呢?

我們先回到原點,看看一個請求的完整過程:首先,請求過來,要建立連線,然後再接收資料,接收資料後,再發送資料。

具體到系統底層,就是讀寫事件,而當讀寫事件沒有準備好時,必然不可操作,如果不用非阻塞的方式來呼叫,那就得阻塞呼叫了,事件沒有準備好,那就只能等了,等事件準備好了,你再繼續吧。阻塞呼叫會進入核心等待,cpu就會讓出去給別人用了,對單執行緒的worker來說,顯然不合適,當網路事件越多時,大家都在等待呢,cpu空閒下來沒人用,cpu利用率自然上不去了,更別談高併發了。

好吧,你說加程序數,這跟apache的執行緒模型有什麼區別,注意,別增加無謂的上下文切換。所以,在nginx裡面,最忌諱阻塞的系統呼叫了。不要阻塞,那就非阻塞嘍。非阻塞就是,事件沒有準備好,馬上返回EAGAIN,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。

好吧,你過一會,再來檢查一下事件,直到事件準備好了為止,在這期間,你就可以先去做其它事情,然後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你可以做更多的事情了,但帶來的開銷也是不小的。