nginx架構與基礎概念

趁着端午假期,瞭解了一些關於nginx web服務器的知識,順便摘抄整理一些要點。

資料來源:http://tengine.taobao.org/book/chapter_02.html

1       Nginx架構

Nginx 高性能,與其架構有關。

Nginx架構: nginx運行時,在unix系統中以daemon形式在後臺運行,後臺進程包含一個master進程和多個worker進程。Nginx以多進程形式工作,也支持多線程方式,丹nginx默認採用多進程方式,也是主流方式。

1.1      Nginx多進程模式

多進程模式,會有一個master進程和多個worker進程。

Master進程管理worker進程,包括:

接收來自外界的信號;

向各worker進程發送信號;

監控work進程狀態;

當worker退出後(異常情況下),自動重新啓動新worker進程。

 

多個worker進程之間對等,競爭來自客戶端的請求,一個請求,只會在一個worker中處理,一個worker進程不會處理其他進程的請求。

Worker進程個數的設置,一般設置與機器cpu核數一致。

 

進程模式的好處:

每個worker進程相互獨立,無需加鎖,節省鎖開銷;

採用獨立的進程,不會相互影響,一個進程退出,其他進程服務不會中斷;

Worker異常退出,會導致當前worker上的所有請求失敗,不過不會影響所有請求,降低了風險。

 

多進程模式對併發的支持

每個worker只有一個主線程,採用異步非阻塞方式來處理請求,使得nginx可以同時處理成千上萬個請求。相比Apache,每個請求會獨佔一個工作線程,併發上千時,就同時有幾千的線程在處理請求,線程帶來的內存佔用很大,線程的上下午切換帶來的cpu開銷也大,性能就上不去了。

異步非阻塞是什麼呢?

一個請求的完整過程:請求過來,建立連接,然後接收數據,接收數據後,再發送數據。

具體到系統底層,就是讀寫事件,當讀寫時間沒有準備好時,如果不用非阻塞的方式來調用,就得阻塞調用了,事件沒準備好,就只能等,等事件準備好再繼續。阻塞調用會進入內核等待,讓出cpu,對單線程的worker來說,顯然不合適,當網絡事件越多時,等待很多,cpu利用率上不去。非阻塞就是,事件沒有準備好,馬上返回eagain,表示事件還沒準備好,過會兒再來,過一會,再來檢查一下事件,直到事件準備好爲止,在這期間,你可以先去做其他事情,然後再來看看事件好了沒。這時,雖不阻塞了,但是還得不時來檢查事件的狀態,帶來的開銷也不小。所以有了異步非阻塞的事件處理機制,具體到系統調用就是像 select/poll/epoll/kquene這樣的系統調用。提供一種機制,讓你可以同時監控多個事件,調用他們是阻塞的,但是可以設置超時時間,在超時時間之內,如果有事件準備好了就返回。這種機制解決了上面的兩個問題,以epoll爲例,當事假沒準備好時,放到epoll裏,事件準備好了,就去讀寫,當讀寫返回eagain時,將它再次加入epoll,這樣,只要有事件準備好了,就去處理它,只有當所有事件都沒有準備好時,纔在epoll裏等着。這樣,就可以支持大量的併發,這裏的併發請求,是指未處理完的請求,線程只有一個,同時處理的請求只有一個,只是在請求間不斷切換,切換是因爲異步事件未準備好,主動讓出的。這裏的切換沒有什麼代價,可以理解爲在循環處理多個準備好的事件,事實上也是。與多線程相比,這種事件處理方式有很大優勢,不需創建線程,每個請求佔用的內存也很少,沒有上下文切換,事件處理非常輕量級,沒有上下文切換的開銷,更多併發,只會佔更多的內存而已。現在的網絡服務器基本都採用這種方式,也是nginx性能高效的主要原因

 

         推薦設置worker數與cpu的核數一致,因爲更多的worker,會導致進程競爭cpu資源,從而帶來不必要的上下文切換。

1.2      操作nginx

怎樣操作運行的nignx呢?master進程會接收來自外界發來的信號,因此要控制nginx,通過kill向master進程發送信號就可以了。如 kill –HUP pid,重啓nginx,或重新加載配置,而不中斷服務。Master進程在接到這個信號後,會先重新加載配置文件,然後再啓動新的worker進程,並向所有老的worker進程發信號,不再接收新的請求,並且在處理完所有未處理完的請求後,退出。新的worker啓動後,就開始接收新的請求。

 

直接給master發信號,是比較老的操作方法,在nginx0.8版本後,可以使用命令行參數,方便管理,如./nginx –s reload ,重啓nginx; ./nginx –s stop,停止nginx。這種方式的內部原理是,執行命令時,會啓動一個新的nginx進程,該進程在解析到reload參數後,知道目標是控制nginx重新加載配置文件,它會向master進程發送信號,接下來的處理,和直接向master進程發送信號一樣。

1.3      Nginx 處理請求

Worker進程是怎麼處理請求的呢?

一個連接請求過來,每個進程都有可能處理這個連接。Worker進程是從master進程fork出來的,在master進程裏,先建立好需要listen的socket(listenfd)後,然後再fork出多個worker進程。所有worker進程的listenfd會在新連接到來時變得可讀,爲了保證只有一個進程處理該連接,所有worker進程在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個進程註冊listenfd讀事件,在讀事件裏調用accept接受該連接。當一個worker進程在accept這個連接之後,開始讀取請求,解析請求,產生數據後,再返回給客戶端,最後才斷開連接,這就是一個完整的請求處理。一個請求,完全由worker處理,且只在一個worker裏處理。

 

2       Nginx基礎概念

2.1      Connection

Nginx中connection是對tcp連接的封裝,包括連接的socket,讀事件,寫事件。

Nginx怎麼處理一個連接的呢?nginx在啓動時,會解析配置文件,得到需要監聽的端口與ip,然後在nginx的master進程裏,先初始化這個監控的socket,然後再fork出多個子進程,子進程競爭accept新的連接。此時,客戶端就可以像nginx發起連接了,當客戶端與服務器通過三次握手建立好一個連接,nginx的某一個子進程會accept成功,得到這個socket,然後創建nginx對連接的封裝,接着,設置讀寫事件處理函數並添加讀寫事件來與客戶端進行數據的交換。最後,nginx或客戶端主動關掉連接。

 

         Nginx也可以作爲客戶端來請求其他server的數據,此時,與其它server創建的連接,也封裝在ngx_connection中。

 

         Nginx中,每個進程會有一個連接數的最大上限,這個上限與系統對fd的限制不一樣。操作系統中,使用ulimit -n,可以得到一個進程所能打開的fd的最大數,即nofile,因爲每個socket會佔用一個fd,所以這個會限制進程的最大連接數,fd用完後,再創建socket,就會失敗。Nginx通過設置worker_connections來設置每個進程支持的最大連接數,如果該值大於nofile,那麼實際的最大連接數是nofile,nginx會有警告。Nginx在實現時,是通過一個連接池來管理的,每個worker進程都有一個獨立的連接池,連接池大小是worker_connections。這裏連接池裏面保存的其實不是真實的連接,只是一個worker_connections大小的ngx_connection_t結構的數組。Nginx通過一個鏈表free_connections來保存所有的空閒ngx_connection_t.每次獲取一個連接時,就從空閒連接鏈表中獲取一個,用完後,再放回空閒連接鏈表裏面。

 

         Worker_connections,表示每個worker所能建立連接的最大值,一個nginx能建立的最大連接數是:worker_connections * worker_processes.因此對於HTTP請求本地資源,最大併發可以是 worker_connections * worker_processes.而如果是HTTP作爲反向代理來說,最大併發數是 worker_connections * worker_processes/2.因爲作爲反向代理服務器,每個併發會建立與客戶端的連接和與後端服務器的連接,佔用2個連接。

 

         如何保證worker進程競爭處理連接的公平呢?

         如果某個進程得到accept的機會比較多,它的空閒連接會很快用完,如果不提前做一些控制,當accept到一個新的tcp連接後,因爲無法得到空閒連接,而且無法將此連接轉交其他進程,最終導致此tcp連接得不到處理。而其他進程有空餘連接,卻沒有處理機會。如何解決這個問題呢?

         Nginx的處理得先打開accept_mutex,此時只有獲得了accept_mutex的進程纔會去添加accept事件,nginx會控制進程是否添加accept事件。Nginx使用一個叫ngx_accept_disabled變量控制是否競爭accept_mutex鎖。這個變量與worker進程的剩餘連接數有關,當該變量大於0時,就不去嘗試獲取鎖,等於讓出獲取連接的機會。這樣就可以控制多進程間連接的平衡了。

2.2      Keep alive

http請求是請求應答式的,如果我們知道每個請求頭與相應體的長度,那麼我們可以在一個連接上面執行多個請求。即長連接。如果當前請求需要有body,那麼nginx就需要客戶端在請求頭中指定content-length來表面body的大小,否則返回400錯誤。那麼響應體的長度呢?http協議中關於響應body長度的確定:

1 對於http1.0 協議來說,如果響應頭中有content-length頭,則以content-length的長度就可以知道body的長度,客戶端在接收body時,可以依照這個長度接收數據,接收完後,就表示該請求完成。如果沒有content-length,客戶端會一直接收數據,直到服務端主動端口連接,才表示body接收完

2 對於http1.1 協議,如果響應頭中transfer-encoding爲chunked傳輸,表示body是流式輸出,body被分成多個塊,每塊的開始會標示出當前塊的長度,此時,body不需要指定長度。如果是非chunked傳輸,而且有Content-length,則按照content-length來接收數據。否則,非chunked且沒有content-length,則客戶端接收數據,知道服務器主動斷開。

 

         客戶端請求頭中connection爲close,表示客戶端要關掉長連接,如果是keep-alive,則客戶端需要打開長連接。客戶端的請求中沒有connection這個頭,根據協議,如果是http1.0,默認是close,如果是http1.1,默認是keep-alive。如果要keep-alive,nginx在輸出完響應體後,會設置當前連接的keepalive屬性,然後等待客戶端下一次請求,nginx設置了keepalive的等待最大時間。一般來說,當客戶端需要多次訪問同一個server時,打開keepalive的優勢非常大。

2.3      Pipe

http1.1中引入Pipeline,就是流水線作業,可以看做是keepalive的昇華。Pipeline也是基於長連接的。目前就是利用一個連接做多次請求,如果客戶端要提交多個請求,對於keepalive,第二個請求,必須要等到第一個請求的響應接收完後,才能發起。得到兩個響應的時間至少是2*RTT。而對於pipeline,客戶端不必等到第一個請求處理完,就可以發起第二個請求。得到兩個響應的時間可能能夠達到1*RTT。Nginx是直接支持pipeline的。Nginx對pipeline中的多個請求的處理不是並行的,而是一個接一個的處理,只是在處理第一個請求的時候,客戶端就可以發起第二個請求。這樣,nginx利用pipeline可以減少從處理完一個請求後到等待第二個請求的請求頭數據的時間。

 

3       Nginx怎麼用(安裝與配置)

具體參見http://seanlook.com/2015/05/17/nginx-install-and-config/

或者http://blog.csdn.net/guodongxiaren/article/details/40950249

 

安裝nginx

yum install nginx-1.6.3

3.1      Nginx.conf 配置

Nginx配置文件主要有4部分,main(全局設置)、server(主機設置)、upstream(上游服務器設置,主要爲反向代理,負載均衡相關配置)和location(url匹配特定位置的設置),每部分包含若干指令。

Main部分的設置影響其他所有部分的設置;

Server部分主要用於指定虛擬機主機域名,ip和端口;

Upstream的指令用於設置一系列的後端服務器,設置反向代理及後端服務器的負載均衡;

Location部分用於匹配網頁位置(如,跟目錄“/”,”/images”等)。

它們之間的關係是,server繼承main,location繼承server,upstream既不會繼承指令也不會被繼承。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章