nginx基礎及原理

1、Nginx的模塊與工作原理
Nginx由內核和模塊組成,其中,內核的設計非常微小和簡潔,完成的工作也非常簡單,僅僅通過查找配置文件將客戶端請求映射到一個location block(location是Nginx配置中的一個指令,用於URL匹配),而在這個location中所配置的每個指令將會啓動不同的模塊去完成相應的工作。

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

核心模塊:HTTP模塊、EVENT模塊和MAIL模塊
基礎模塊:HTTP Access模塊、HTTP FastCGI模塊、HTTP Proxy模塊和HTTP Rewrite模塊,
第三方模塊:HTTP Upstream Request Hash模塊、Notice模塊和HTTP Access Key模塊。
用戶根據自己的需要開發的模塊都屬於第三方模塊。正是有了這麼多模塊的支撐,Nginx的功能纔會如此強大。
Nginx的模塊從功能上分爲如下三類。

Handlers(處理器模塊)。此類模塊直接處理請求,並進行輸出內容和修改headers信息等操作。
Handlers處理器模塊一般只能有一個。
Filters (過濾器模塊)。此類模塊主要對其他處理器模塊輸出的內容進行修改操作,最後由Nginx輸出。
Proxies (代理類模塊)。此類模塊是Nginx的HTTP
Upstream之類的模塊,這些模塊主要與後端一些服務比如FastCGI等進行交互,實現服務代理和負載均衡等功能
在這裏插入圖片描述
Nginx本身做的工作實際很少,當它接到一個HTTP請求時,它僅僅是通過查找配置文件將此次請求映射到一個location
block,而此location中所配置的各個指令則會啓動不同的模塊去完成工作,因此模塊可以看做Nginx真正的勞動工作者。通常一個location中的指令會涉及一個handler模塊和多個filter模塊(當然,多個location可以複用同一個模塊)。handler模塊負責處理請求,完成響應內容的生成,而filter模塊對響應內容進行處理。
Nginx的模塊直接被編譯進Nginx,因此屬於靜態編譯方式。啓動Nginx後,Nginx的模塊被自動加載,不像Apache,首先將模塊編譯爲一個so文件,然後在配置文件中指定是否進行加載。在解析配置文件時,Nginx的每個模塊都有可能去處理某個請求,但是同一個處理請求只能由一個模塊來完成

2、Nginx的進程模型
在工作方式上,Nginx分爲單工作進程和多工作進程兩種模式。在單工作進程模式下,除主進程外,還有一個工作進程,工作進程是單線程的;在多工作進程模式下,每個工作進程包含多個線程。Nginx默認爲單工作進程模式。

Nginx在啓動後,會有一個master進程和多個worker進程。
master進程

主要用來管理worker進程,包含:接收來自外界的信號,向各worker進程發送信號,監控worker進程的運行狀態,當worker進程退出後,匯自動重新啓動新的worker進程。

master進程充當整個進程組與用戶的交互接口,同時對進程進行監護。它不需要處理網絡事件,不負責業務的執行,只會通過管理worker進程來實現重啓服務、平滑升級、更換日誌文件、配置文件實時生效等功能。

我們要控制nginx,只需要通過kill向master進程發送信號就行了。比如kill -HUP
pid,則是告訴nginx,從容地重啓nginx,我們一般用這個信號來重啓nginx,或重新加載配置,因爲是從容地重啓,因此服務是不中斷的。master進程在接收到HUP信號後是怎麼做的呢?首先master進程在接到信號後,會先重新加載配置文件,然後再啓動新的worker進程,並向所有老的worker進程發送信號,告訴他們可以光榮退休了。新的worker在啓動後,就開始接收新的請求,而老的worker在收到來自master的信號後,就不再接收新的請求,並且在當前進程中的所有未處理完的請求處理完成後,再退出。當然,直接給master進程發送信號,這是比較老的操作方式,nginx在0.8版本之後,引入了一系列命令行參數,來方便我們管理。比如,./nginx
-s reload,就是來重啓nginx,./nginx -s stop,就是來停止nginx的運行。如何做到的呢?我們還是拿reload來說,我們看到,執行命令時,我們是啓動一個新的nginx進程,而新的nginx進程在解析到reload參數後,就知道我們的目的是控制nginx來重新加載配置文件了,它會向master進程發送信號,然後接下來的動作,就和我們直接向master進程發送信號一樣了。

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進程中處理

nginx的進程模型,可以由下圖來表示:
在這裏插入圖片描述
3、Nginx+FastCGI運行原理
FastCGI是一個可伸縮地、高速地在HTTP server和動態腳本語言間通信的接口。多數流行的HTTP
server都支持FastCGI,包括Apache、Nginx和lighttpd等。同時,FastCGI也被許多腳本語言支持,其中就有PHP。

FastCGI是從CGI發展改進而來的。傳統CGI接口方式的主要缺點是性能很差,因爲每次HTTP服務器遇到動態程序時都需要重新啓動腳本解析器來執行解析,然後將結果返回給HTTP服務器。這在處理高併發訪問時幾乎是不可用的。另外傳統的CGI接口方式安全性也很差,現在已經很少使用了。

FastCGI接口方式採用C/S結構,可以將HTTP服務器和腳本解析服務器分開,同時在腳本解析服務器上啓動一個或者多個腳本解析守護進程。當HTTP服務器每次遇到動態程序時,可以將其直接交付給FastCGI進程來執行,然後將得到的結果返回給瀏覽器。這種方式可以讓HTTP服務器專一地處理靜態請求或者將動態腳本服務器的結果返回給客戶端,這在很大程度上提高了整個應用系統的性能

2、Nginx+FastCGI運行原理

Nginx不支持對外部程序的直接調用或者解析,所有的外部程序(包括PHP)必須通過FastCGI接口來調用。FastCGI接口在Linux下是socket(這個socket可以是文件socket,也可以是ip
socket)。

wrapper:爲了調用CGI程序,還需要一個FastCGI的wrapper(wrapper可以理解爲用於啓動另一個程序的程序),這個wrapper綁定在某個固定socket上,如端口或者文件socket。當Nginx將CGI請求發送給這個socket的時候,通過FastCGI接口,wrapper接收到請求,然後Fork(派生)出一個新的線程,這個線程調用解釋器或者外部程序處理腳本並讀取返回數據;接着,wrapper再將返回的數據通過FastCGI接口,沿着固定的socket傳遞給Nginx;最後,Nginx將返回的數據(html頁面或者圖片)發送給客戶端。這就是Nginx+FastCGI的整個運作過程,如圖1-3所示
在這裏插入圖片描述
上圖即 Nginx+FastCGI運行過程
所以,我們首先需要一個wrapper,這個wrapper需要完成的工作:
通過調用fastcgi(庫)的函數通過socket和ningx通信(讀寫socket是fastcgi內部實現的功能,對wrapper是非透明的)調度thread,進行fork和kill和application(php)進行通信

4、Nginx+PHP正確配置
server {
listen 80;
server_name www.admin.com;
root /vagrant_data/admin/public;

    access_log  /usr/local/nginx/logs/access_admin.log;
    error_log /usr/local/nginx/logs/error_admin.log;

    location / {
        index  index.html index.htm index.php;
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php(.*)$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param  PATH_INFO  $fastcgi_path_info;
        fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
        include        fastcgi_params;
    }

}

5、Nginx爲啥性能高-多進程IO模型
1、nginx採用多進程模型好處
首先,對於每個worker進程來說,獨立的進程,不需要加鎖,所以省掉了鎖帶來的開銷,同時在編程以及問題查找時,也會方便很多。

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

2、nginx多進程事件模型:異步非阻塞
雖然nginx採用多worker的方式來處理請求,每個worker裏面只有一個主線程,那能夠處理的併發數很有限啊,多少個worker就能處理多少個併發,何來高併發呢?非也,這就是nginx的高明之處,nginx採用了異步非阻塞的方式來處理請求,也就是說,nginx是可以同時處理成千上萬個請求的。一個worker進程可以同時處理的請求數只受限於內存大小,而且在架構設計上,不同的worker進程之間處理併發請求時幾乎沒有同步鎖的限制,worker進程通常不會進入睡眠狀態,因此,當Nginx上的進程數與CPU核心數相等時(最好每一個worker進程都綁定特定的CPU核心),進程間切換的代價是最小的。

而apache的常用工作方式(apache也有異步非阻塞版本,但因其與自帶某些模塊衝突,所以不常用),每個進程在一個時刻只處理一個請求,因此,當併發數上到幾千時,就同時有幾千的進程在處理請求了。這對操作系統來說,是個不小的挑戰,進程帶來的內存佔用非常大,進程的上下文切換帶來的cpu開銷很大,自然性能就上不去了,而這些開銷完全是沒有意義的
在這裏插入圖片描述
Nginx進程間的關係

爲什麼nginx可以採用異步非阻塞的方式來處理呢,或者異步非阻塞到底是怎麼回事呢?
我們先回到原點,看看一個請求的完整過程:首先,請求過來,要建立連接,然後再接收數據,接收數據後,再發送數據。

具體到系統底層,就是讀寫事件,而當讀寫事件沒有準備好時,必然不可操作,如果不用非阻塞的方式來調用,那就得阻塞調用了,事件沒有準備好,那就只能等了,等事件準備好了,你再繼續吧。阻塞調用會進入內核等待,cpu就會讓出去給別人用了,對單線程的worker來說,顯然不合適,當網絡事件越多時,大家都在等待呢,cpu空閒下來沒人用,cpu利用率自然上不去了,更別談高併發了。好吧,你說加進程數,這跟apache的線程模型有什麼區別,注意,別增加無謂的上下文切換。所以,在nginx裏面,最忌諱阻塞的系統調用了。不要阻塞,那就非阻塞嘍。非阻塞就是,事件沒有準備好,馬上返回EAGAIN,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。好吧,你過一會,再來檢查一下事件,直到事件準備好了爲止,在這期間,你就可以先去做其它事情,然後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你可以做更多的事情了,但帶來的開銷也是不小的。

轉載自http://www.iswoole.com/article/2100

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