Nginx瞭解

 Nginx是由俄羅斯軟件工程師Igor Sysoev開發的一個高性能的HTTP和反向代理服務器,具有IMAP/POP3和SMTP服務器功能。Nginx最大的特色是對高併發的支持高效的負載均衡,在高併發的需求場景下,是Apache服務器不錯的替代品。目前,包括新浪、騰訊等知名網站已經開始使用Nginx做爲Web應用服務器。nginx

     Nginx相較於Apache、lighttpd具備佔有內存少,穩定性高等優點,而且依靠併發能力強豐富的模塊庫以及友好靈活的配置而聞名。web

     在Linux操做系統下,nginx使用epoll事件模型,得益於此,nginx在Linux操做系統下效率至關高。同時Nginx在OpenBSD或 FreeBSD操做系統上採用相似於epoll的高效事件模型kqueue。sql

     目前使用FastCGI+Nginx運行PHP應用時若是配置文件設置不合理,並非十分可靠。建議使用NAMP架構(Nginx+Apache+Mysql+PHP),即便用Nginx處理靜態請求,並將動態請求反向代理給Apache處理apache

-----------------------------------------------------------------------------------------------------------------------------------------編程

優勢:服務器

  1. 在高鏈接併發的狀況下,Nginx是Apache服務器不錯的替代品: Nginx在美國是作虛擬主機生意的老闆們常常選擇的軟件平臺之一. 可以支持高達 50,000 個併發鏈接數的響應, 感謝Nginx爲咱們選擇了 epoll and kqueue 做爲開發模型。
  2. Nginx做爲負載均衡服務器: Nginx 既能夠在內部直接支持 Rails 和 PHP 程序對外進行服務, 也能夠支持做爲 HTTP代理 服務器對外進行服務. Nginx採用C進行編寫, 不管是系統資源開銷仍是CPU使用效率都比 Perlbal 要好不少。
  3. 做爲郵件代理服務器: Nginx 同時也是一個很是優秀的郵件代理服務器(最先開發這個產品的目的之一也是做爲郵件代理服務器), Last.fm 描述了成功而且美妙的使用經驗。
  4. Nginx 是一個安裝很是的簡單 , 配置文件 很是簡潔(還可以支持perl語法), Bugs 很是少的服務器: Nginx 啓動特別容易, 而且幾乎能夠作到7*24不間斷運行,即便運行數個月也不須要從新啓動. 你還可以不間斷服務的狀況下進行軟件版本的升級。

=====================================================================================網絡

 

        Nginx在啓動後,在unix系統中會以daemon的方式在後臺運行,後臺進程包含一個master進程多個worker進程。咱們也能夠手動地關掉後臺模式,讓nginx在前臺運行,而且經過配置讓nginx取消master進程,從而可使nginx以單進程方式運行。很顯然,生產環境下咱們確定不會這麼作,因此關閉後臺模式,通常是用來調試用的。因此,咱們能夠看到,nginx是以多進程的方式來工做的,固然nginx也是支持多線程的方式的,只是咱們主流的方式仍是多進程的方式,也是nginx的默認方式。多線程

 

       master進程主要用來管理worker進程,包含:接收來自外界的信號,向各worker進程發送信號,監控worker進程的運行狀態,當worker進程退出後(異常狀況下),會自動從新啓動新的worker進程。架構

       而基本的網絡事件,則是放在worker進程中來處理了。多個worker進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的。一個請求,只可能在一個worker進程中處理,一個worker進程,不可能處理其它進程的請求。worker進程的個數是能夠設置的,通常咱們會設置與機器cpu核數一致,這裏面的緣由與nginx的進程模型以及事件處理模型是分不開的。nginx的進程模型,能夠由下圖來表示:併發

 

 技術分享

 

       在Nginx啓動後,若是咱們要操做nginx,要怎麼作呢?

        master來管理worker進程,因此咱們只須要與master進程通訊就好了。master進程會接收來自外界發來的信號,再根據信號作不一樣的事情。因此咱們要控制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進程之間是平等的,每一個進程,處理請求的機會也是同樣的。當咱們提供80端口的http服務時,一個鏈接請求過來,每一個進程都有可能處理這個鏈接。

       首先,每一個worker進程都是從master進程fork過來,在master進程裏面,先創建好須要listen的socket(listenfd)以後,而後再fork出多個worker進程。全部worker進程的listenfd會在新鏈接到來時變得可讀,爲保證只有一個進程處理該鏈接,全部worker進程在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個進程註冊listenfd讀事件,在讀事件裏調用accept接受該鏈接。當一個worker進程在accept這個鏈接以後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開鏈接,這樣一個完整的請求就是這樣的了。咱們能夠看到,一個請求,徹底由worker進程來處理,並且只在一個worker進程中處理。

 

     nginx採用這種進程模型有什麼好處呢?

    首先,對於每一個worker進程來講,獨立的進程,不須要加鎖,因此省掉了鎖帶來的開銷,同時在編程以及問題查找時,也會方便不少。其次,採用獨立的進程,可讓互相之間不會影響,一個進程退出後,其它進程還在工做,服務不會中斷,master進程則很快啓動新的worker進程。固然,worker進程的異常退出,確定是程序有bug了,異常退出,會致使當前worker上的全部請求失敗,不過不會影響到全部請求,因此下降了風險。固然,好處還有不少,你們能夠慢慢體會。

 

上面講了不少關於nginx的進程模型,接下來,咱們來看看nginx是如何處理事件的。

 

        nginx採用多worker的方式來處理請求,每一個worker裏面只有一個主線程,那可以處理的併發數頗有限啊,多少個worker就能處理多少個併發,何來高併發呢?非也,這就是nginx的高明之處。

       nginx採用了異步非阻塞的方式來處理請求,也就是說,nginx是能夠同時處理成千上萬個請求的。想一想apache的經常使用工做方式(apache也有異步非阻塞版本,但因其與自帶某些模塊衝突,因此不經常使用),每一個請求會獨佔一個工做線程,當併發數上到幾千時,就同時有幾千的線程在處理請求了。這對操做系統來講,是個不小的挑戰,線程帶來的內存佔用很是大,線程的上下文切換帶來的cpu開銷很大,天然性能就上不去了,而這些開銷徹底是沒有意義的。

      爲何nginx能夠採用異步非阻塞的方式來處理呢,或者異步非阻塞究竟是怎麼回事呢?

       咱們先回到原點,看看一個請求的完整過程。

       首先,請求過來,要創建鏈接,而後再接收數據,接收數據後,再發送數據。具體到系統底層,就是讀寫事件,而當讀寫事件沒有準備好時,必然不可操做,若是不用非阻塞的方式來調用,那就得阻塞調用了,事件沒有準備好,那就只能等了,等事件準備好了,你再繼續吧。阻塞調用會進入內核等待,cpu就     會讓出去給別人用了,對單線程的worker來講,顯然不合適,當網絡事件越多時,你們都在等待呢,cpu空閒下來沒人用,cpu利用率天然上不去了,更別談高併發了。

       好吧,你說加進程數,這跟apache的線程模型有什麼區別,注意,別增長無謂的上下文切換。因此,在nginx裏面,最忌諱阻塞的系統調用了。不要阻塞,那就非阻塞嘍。非阻塞就是,事件沒有準備好,立刻返回EAGAIN,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。好吧,你過一會,再來檢查一下事件,直到事件準備好了爲止,在這期間 ,你就能夠先去作其它事情,而後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你能夠作更多的事情了,但帶來的開銷也是不小的。

      因此,纔會有了異步非阻塞的事件處理機制,具體到系統調用就是像select/poll/epoll/kqueue這樣的系統調用。它們提供了一種機制,讓你能夠同時監控多個事件,調用他們是阻塞的,但能夠設置超時時間,在超時時間以內,若是有事件準備好了,就返回。這種機制正好解決了咱們上面的兩個問題,拿epoll爲例(在後面的例子中,咱們多以epoll爲例子,以表明這一類函數),當事件沒準備好時,放到epoll裏面,事件準備好了,咱們就去讀寫,當讀寫返回EAGAIN時,咱們將它再次加入到epoll裏面。這樣,只要有事件準備好了,咱們就去處理它,只有當全部事件都沒準備好時,纔在epoll裏面等着。這樣,咱們就能夠併發處理大量的併發了,固然,這裏的併發請求,是指未處理完的請求,線程只有一個,因此同時能處理的請求固然只有一個了,只是在請求間進行不斷地切換而已,切換也是由於異步事件未準備好,而主動讓出的。這裏的切換是沒有任何代價,你能夠理解爲循環處理多個準備好的事件,事實上就是這樣的。與多線程相比,這種事件處理方式是有很大的優點的,不須要建立線程,每一個請求佔用的內存也不多,沒有上下文切換,事件處理很是的輕量級。併發數再多也不會致使無謂的資源浪費(上下文切換)。更多的併發數,只是會佔用更多的內存而已。 我以前有對鏈接數進行過測試,在24G內存的機器上,處理的併發請求數達到過200萬。如今的網絡服務器基本都採用這種方式,這也是nginx性能高效的主要緣由。

 

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

 

如今,知道了nginx爲何會選擇這樣的進程模型與事件模型了。對於一個基本的web服務器來講,事件一般有三種類型,網絡事件、信號、定時器。從上面的講解中知道,網絡事件經過異步非阻塞能夠很好的解決掉。如何處理信號與定時器?

 

首先,信號的處理。對nginx來講,有一些特定的信號,表明着特定的意義。信號會中斷掉程序當前的運行,在改變狀態後,繼續執行。若是是系統調用,則可能會致使系統調用的失敗,須要重入。關於信號的處理,你們能夠學習一些專業書籍,這裏很少說。對於nginx來講,若是nginx正在等待事件(epoll_wait時),若是程序收到信號,在信號處理函數處理完後,epoll_wait會返回錯誤,而後程序可再次進入epoll_wait調用。

 

另外,再來看看定時器。因爲epoll_wait等函數在調用的時候是能夠設置一個超時時間的,因此nginx藉助這個超時時間來實現定時器。nginx裏面的定時器事件是放在一顆維護定時器的紅黑樹裏面,每次在進入epoll_wait前,先從該紅黑樹裏面拿到全部定時器事件的最小時間,在計算出epoll_wait的超時時間後進入epoll_wait。因此,當沒有事件產生,也沒有中斷信號時,epoll_wait會超時,也就是說,定時器事件到了。這時,nginx會檢查全部的超時事件,將他們的狀態設置爲超時,而後再去處理網絡事件。由此能夠看出,當咱們寫nginx代碼時,在處理網絡事件的回調函數時,一般作的第一個事情就是判斷超時,而後再去處理網絡事件。

相關文章
相關標籤/搜索