文章出處

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

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

     在Linux操作系統下,nginx使用epoll事件模型,得益于此,nginx在Linux操作系統下效率相當高。同時Nginx在OpenBSD或 FreeBSD操作系統上采用類似于epoll的高效事件模型kqueue。

     目前使用FastCGI+Nginx運行PHP應用時如果配置文件設置不合理,并不是十分可靠。建議使用NAMP架構(Nginx+Apache+Mysql+PHP),即使用Nginx處理靜態請求,并將動態請求反向代理給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代碼時,在處理網絡事件的回調函數時,通常做的第一個事情就是判斷超時,然后再去處理網絡事件。

 

我們可以用一段偽代碼來總結一下nginx的事件處理模型:

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()