nginx 工做原理總結

1.  Nginx的模塊與工做原理

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

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

核心模塊:HTTP模塊、EVENT模塊和MAIL模塊前端

基礎模塊:HTTP Access模塊、HTTP FastCGI模塊、HTTP Proxy模塊和HTTP Rewrite模塊,linux

第三方模塊:HTTP Upstream Request Hash模塊、Notice模塊和HTTP Access Key模塊。nginx

用戶根據本身的須要開發的模塊都屬於第三方模塊。正是有了這麼多模塊的支撐,Nginx的功能纔會如此強大。apache

Nginx的模塊從功能上分爲以下三類。編程

Handlers(處理器模塊):此模塊直接處理請求,並進行輸出內容和修改headers信息等操做。Handlers處理器模塊通常只能有一個。後端

Filters (過濾器模塊):此模塊主要對其餘處理器模塊輸出的內容進行修改操做,最後由Nginx輸出。瀏覽器

Proxies (代理類模塊):此模塊是Nginx的HTTP Upstream之類的模塊,這些模塊主要與後端一些服務好比FastCGI等進行交互,實現服務代理和負載均衡等功能。安全

圖1-1展現了Nginx模塊常規的HTTP請求和響應的過程。

 

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
FastCGI是一個可伸縮地、高速地在HTTP server和動態腳本語言間通訊的接口。多數流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等。同時,FastCGI也被許多腳本語言支持,其中就有PHP。

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

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

二、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所示。

 

 

      因此,咱們首先須要一個wrapper,這個wrapper須要完成的工做:

經過調用fastcgi(庫)的函數經過socket和ningx通訊(讀寫socket是fastcgi內部實現的功能,對wrapper是非透明的)
調度thread,進行fork和kill
和application(php)進行通訊
三、spawn-fcgi與PHP-FPM
       FastCGI接口方式在腳本解析服務器上啓動一個或者多個守護進程對動態腳本進行解析,這些進程就是FastCGI進程管理器,或者稱爲FastCGI引擎。 spawn-fcgi與PHP-FPM就是支持PHP的兩個FastCGI進程管理器。所以HTTPServer徹底解放出來,能夠更好地進行響應和併發處理。

       spawn-fcgi與PHP-FPM的異同:
       1)spawn-fcgi是HTTP服務器lighttpd的一部分,目前已經獨立成爲一個項目,通常與lighttpd配合使用來支持PHP。可是ligttpd的spwan-fcgi在高併發訪問的時候,會出現內存泄漏甚至自動重啓FastCGI的問題。即:PHP腳本處理器當機,這個時候若是用戶訪問的話,可能就會出現白頁(即PHP不能被解析或者出錯)。

       2)Nginx是個輕量級的HTTP server,必須藉助第三方的FastCGI處理器才能夠對PHP進行解析,所以其實這樣看來nginx是很是靈活的,它能夠和任何第三方提供解析的處理器實現鏈接從而實現對PHP的解析(在nginx.conf中很容易設置)。nginx也可使用spwan-fcgi(須要一同安裝lighttpd,可是須要爲nginx避開端口,一些較早的blog有這方面安裝的教程),可是因爲spawn-fcgi具備上面所述的用戶逐漸發現的缺陷,如今慢慢減小用nginx+spawn-fcgi組合了。

       因爲spawn-fcgi的缺陷,如今出現了第三方(目前已經加入到PHP core中)的PHP的FastCGI處理器PHP-FPM,它和spawn-fcgi比較起來有以下優勢:

       因爲它是做爲PHP的patch補丁來開發的,安裝的時候須要和php源碼一塊兒編譯,也就是說編譯到php core中了,所以在性能方面要優秀一些;

同時它在處理高併發方面也優於spawn-fcgi,至少不會自動重啓fastcgi處理器。所以,推薦使用Nginx+PHP/PHP-FPM這個組合對PHP進行解析。

      相對Spawn-FCGI,PHP-FPM在CPU和內存方面的控制都更勝一籌,並且前者很容易崩潰,必須用crontab進行監控,而PHP-FPM則沒有這種煩惱。
       FastCGI 的主要優勢是把動態語言和HTTP Server分離開來,因此Nginx與PHP/PHP-FPM常常被部署在不一樣的服務器上,以分擔前端Nginx服務器的壓力,使Nginx專注處理靜態請求和轉發動態請求,而PHP/PHP-FPM服務器專注解析PHP動態請求。

四、Nginx+PHP-FPM
      PHP-FPM是管理FastCGI的一個管理器,它做爲PHP的插件存在,在安裝PHP要想使用PHP-FPM時在老php的老版本(php5.3.3以前)就須要把PHP-FPM以補丁的形式安裝到PHP中,並且PHP要與PHP-FPM版本一致,這是必須的)

   PHP-FPM實際上是PHP源代碼的一個補丁,旨在將FastCGI進程管理整合進PHP包中。必須將它patch到你的PHP源代碼中,在編譯安裝PHP後纔可使用。
   PHP5.3.3已經集成php-fpm了,再也不是第三方的包了。PHP-FPM提供了更好的PHP進程管理方式,能夠有效控制內存和進程、能夠平滑重載PHP配置,比spawn-fcgi具備更多優勢,因此被PHP官方收錄了。在./configure的時候帶 –enable-fpm參數便可開啓PHP-FPM。

      fastcgi已經在php5.3.5的core中了,沒必要在configure時添加 --enable-fastcgi了。老版本如php5.2的須要加此項。

      當咱們安裝Nginx和PHP-FPM完後,配置信息:

     PHP-FPM的默認配置php-fpm.conf:

     listen_address  127.0.0.1:9000 #這個表示php的fastcgi進程監聽的ip地址以及端口

      start_servers

      min_spare_servers

      max_spare_servers

 

      Nginx配置運行php: 編輯nginx.conf加入以下語句:

      location ~ \.php$ {
            root html;   
            fastcgi_pass 127.0.0.1:9000; 指定了fastcgi進程偵聽的端口,nginx就是經過這裏與php交互的
            fastcgi_index index.php;
            include fastcgi_params;
             fastcgi_param SCRIPT_FILENAME   /usr/local/nginx/html$fastcgi_script_name;
    }

    Nginx經過location指令,將全部以php爲後綴的文件都交給127.0.0.1:9000來處理,而這裏的IP地址和端口就是FastCGI進程監聽的IP地址和端口。

   

     其總體工做流程:

     1)、FastCGI進程管理器php-fpm自身初始化,啓動主進程php-fpm和啓動start_servers個CGI 子進程。

           主進程php-fpm主要是管理fastcgi子進程,監聽9000端口。

           fastcgi子進程等待來自Web Server的鏈接。

     2)、當客戶端請求到達Web Server Nginx是時,Nginx經過location指令,將全部以php爲後綴的文件都交給127.0.0.1:9000來處理,即Nginx經過location指令,將全部以php爲後綴的文件都交給127.0.0.1:9000來處理。

      3)FastCGI進程管理器PHP-FPM選擇並鏈接到一個子進程CGI解釋器。Web server將CGI環境變量和標準輸入發送到FastCGI子進程。

      4)、FastCGI子進程完成處理後將標準輸出和錯誤信息從同一鏈接返回Web Server。當FastCGI子進程關閉鏈接時,請求便告處理完成。

      5)、FastCGI子進程接着等待並處理來自FastCGI進程管理器(運行在 WebServer中)的下一個鏈接。

5.   Nginx爲啥性能高-多進程IO模型

一、nginx採用多進程模型好處

      首先,對於每一個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,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。好吧,你過一會,再來檢查一下事件,直到事件準備好了爲止,在這期間,你就能夠先去作其它事情,而後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你能夠作更多的事情了,但帶來的開銷也是不小的。

       nginx支持的事件模型以下(nginx的wiki):

       Nginx支持以下處理鏈接的方法(I/O複用方法),這些方法能夠經過use指令指定。

select– 標準方法。 若是當前平臺沒有更有效的方法,它是編譯時默認的方法。你可使用配置參數 –with-select_module 和 –without-select_module 來啓用或禁用這個模塊。
poll– 標準方法。 若是當前平臺沒有更有效的方法,它是編譯時默認的方法。你可使用配置參數 –with-poll_module 和 –without-poll_module 來啓用或禁用這個模塊。
kqueue– 高效的方法,使用於 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X. 使用雙處理器的MacOS X系統使用kqueue可能會形成內核崩潰。
epoll – 高效的方法,使用於Linux內核2.6版本及之後的系統。在某些發行版本中,如SuSE 8.2, 有讓2.4版本的內核支持epoll的補丁。
rtsig – 可執行的實時信號,使用於Linux內核版本2.2.19之後的系統。默認狀況下整個系統中不能出現大於1024個POSIX實時(排隊)信號。這種狀況 對於高負載的服務器來講是低效的;因此有必要經過調節內核參數 /proc/sys/kernel/rtsig-max 來增長隊列的大小。但是從Linux內核版本2.6.6-mm2開始, 這個參數就再也不使用了,而且對於每一個進程有一個獨立的信號隊列,這個隊列的大小能夠用 RLIMIT_SIGPENDING 參數調節。當這個隊列過於擁塞,nginx就放棄它而且開始使用 poll 方法來處理鏈接直到恢復正常。
/dev/poll – 高效的方法,使用於 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+.
eventport – 高效的方法,使用於 Solaris 10. 爲了防止出現內核崩潰的問題, 有必要安裝這個 安全補丁。
        在linux下面,只有epoll是高效的方法

       下面再來看看epoll究竟是如何高效的
       Epoll是Linux內核爲處理大批量句柄而做了改進的poll。 要使用epoll只須要這三個系統調用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44內核中被引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6內核中獲得普遍應用。

        epoll的優勢

支持一個進程打開大數目的socket描述符(FD)
        select 最不能忍受的是一個進程所打開的FD是有必定限制的,由FD_SETSIZE設置,默認值是2048。對於那些須要支持的上萬鏈接數目的IM服務器來講顯 然太少了。這時候你一是能夠選擇修改這個宏而後從新編譯內核,不過資料也同時指出這樣會帶來網絡效率的降低,二是能夠選擇多進程的解決方案(傳統的 Apache方案),不過雖然linux上面建立進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,因此也不是一種完 美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大能夠打開文件的數目,這個數字通常遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左 右,具體數目能夠cat /proc/sys/fs/file-max察看,通常來講這個數目和系統內存關係很大。

IO效率不隨FD數目增長而線性降低
         傳統的select/poll另外一個致命弱點就是當你擁有一個很大的socket集合,不過因爲網絡延時,任一時間只有部分的socket是」活躍」的,但 是select/poll每次調用都會線性掃描所有的集合,致使效率呈現線性降低。可是epoll不存在這個問題,它只會對」活躍」的socket進行操 做—這是由於在內核實現中epoll是根據每一個fd上面的callback函數實現的。那麼,只有」活躍」的socket纔會主動的去調用 callback函數,其餘idle狀態socket則不會,在這點上,epoll實現了一個」僞」AIO,由於這時候推進力在os內核。在一些 benchmark中,若是全部的socket基本上都是活躍的—好比一個高速LAN環境,epoll並不比select/poll有什麼效率,相 反,若是過多使用epoll_ctl,效率相比還有稍微的降低。可是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。

使用mmap加速內核與用戶空間的消息傳遞。
        這 點實際上涉及到epoll的具體實現了。不管是select,poll仍是epoll都須要內核把FD消息通知給用戶空間,如何避免沒必要要的內存拷貝就很 重要,在這點上,epoll是經過內核於用戶空間mmap同一塊內存實現的。而若是你想我同樣從2.5內核就關注epoll的話,必定不會忘記手工 mmap這一步的。

內核微調
         這一點其實不算epoll的優勢了,而是整個linux平臺的優勢。也許你能夠懷疑linux平臺,可是你沒法迴避linux平臺賦予你微調內核的能力。好比,內核TCP/IP協 議棧使用內存池管理sk_buff結構,那麼能夠在運行時期動態調整這個內存pool(skb_head_pool)的大小— 經過echo XXXX>/proc/sys/net/core/hot_list_length完成。再好比listen函數的第2個參數(TCP完成3次握手 的數據包隊列長度),也能夠根據你平臺內存大小動態調整。更甚至在一個數據包面數目巨大但同時每一個數據包自己大小卻很小的特殊系統上嘗試最新的NAPI網卡驅動架構。

      推薦設置worker的個數爲cpu的核數,在這裏就很容易理解了,更多的worker數,只會致使進程來競爭cpu資源了,從而帶來沒必要要的上下文切換。並且,nginx爲了更好的利用多核特性,提供了cpu親緣性的綁定選項,咱們能夠將某一個進程綁定在某一個核上,這樣就不會由於進程的切換帶來cache的失效。像這種小的優化在nginx中很是常見,同時也說明了nginx做者的苦心孤詣。好比,nginx在作4個字節的字符串比較時,會將4個字符轉換成一個int型,再做比較,以減小cpu的指令數等等。

 

轉自:https://blog.csdn.net/hguisu/article/details/8930668 

相關文章
相關標籤/搜索