Nginx爲什麼比Apache Httpd高效:原理篇

Nginx才短短几年,就拿下了web服務器大筆江山,衆所周知,Nginx在處理大併發靜態請求方面,效率明顯高於httpd,甚至能輕鬆解決C10K問題。下面我們就來聊聊Web服務器背後的一些原理。

一、進程、線程?

進程是具有一定獨立功能的,在計算機中已經運行的程序的實體。在早期系統中(如linux 2.4以前),進程是基本運作單位,在支持線程的系統中(如windows,linux2.6)中,線程纔是基本的運作單位,而進程只是線程的容器。程序本身只是指令、數據及其組織形式的描述,進程纔是程序(那些指令和數據)的真正運行實例。若干進程有可能與同一個程序相關係,且每個進程皆可以同步(循序)或異步(平行)的方式獨立運行。現代計算機系統可在同一段時間內以進程的形式將多個程序加載到存儲器中,並藉由時間共享(或稱時分複用),以在一個處理器上表現出同時(平行性)運行的感覺。同樣的,使用多線程技術(多線程即每一個線程都代表一個進程內的一個獨立執行上下文)的操作系統或計算機架構,同樣程序的平行線程,可在多 CPU 主機或網絡上真正同時運行(在不同的CPU上)。

二、常見Web服務方式

2.1 三種工作模型比較:

Web服務器要爲用戶提供服務,必須以某種方式,工作在某個套接字上。一般Web服務器在處理用戶請求是,一般有如下三種方式可選擇:多進程方式、多線程方式、異步方式。

  • 多進程方式:爲每個請求啓動一個進程來處理。由於在操作系統中,生成進程、銷燬進程、進程間切換都很消耗CPU和內存,當負載高是,性能會明顯降低。

優點: 穩定性!由於採用獨立進程處理獨立請求,而進程之間是獨立的,單個進程問題不會影響其他進程,因此穩定性最好。

缺點: 資源佔用!當請求過大時,需要大量的進程處理請求,進程生成、切換開銷很大,而且進程間資源是獨立的,造成內存重複利用。

  • 多線程方式:一個進程中用多個線程處理用戶請求。由於線程開銷明顯小於進程,而且部分資源還可以共享,因此效率較高。

優點:開銷較小!線程間部分數據是共享的,且線程生成與線程間的切換所需資源開銷比進程間切換小得多。

缺點:穩定性!線程切換過快可能造成線程抖動,且線程過多會造成服務器不穩定。

  • 異步方式:使用非阻塞方式處理請求,是三種方式中開銷最小的。但異步方式雖然效率高,但要求也高,因爲多任務之間的調度如果出現問題,就可能出現整體故障,因此使用異步工作的,一般是一些功能相對簡單,但卻符合服務器任務調度、且代碼中沒有影響調度的錯誤代碼存在的程序。

優點:性能最好!一個進程或線程處理多個請求,不需要額外開銷,性能最好,資源佔用最低。

缺點:穩定性!某個進程或線程出錯,可能導致大量請求無法處理,甚至導致整個服務宕機。

2.2 一個Web請求的處理過程:

io

即進程向內核進行系統調用申請IO,內核將資源從IO調度到內核的buffer中(wait階段),內核還需將數據從內核buffer中複製(copy階段)到web服務器進程所在的用戶空間,纔算完成一次IO調度。這幾個階段都是需要時間的。根據wait和copy階段的處理等待的機制不同,可將I/O動作分爲如下五種模式:

  • 阻塞I/O

  • 非阻塞I/O

  • I/O複用(select和poll)

  • 信號(事件)驅動I/O(SIGIO)

  • 異步I/O(aio)

    3.1 I/O模型簡介

    這裏有必要先解釋一下阻塞、非阻塞,同步、異步、I/O的概念。

3.1.1 阻塞和非阻塞:

阻塞和非阻塞指的是執行一個操作是等操作結束再返回,還是馬上返回。

比如餐館的服務員爲用戶點菜,當有用戶點完菜後,服務員將菜單給後臺廚師,此時有兩種方式:

  • 第一種:就在出菜窗口等待,直到廚師炒完菜後將菜送到窗口,然後服務員再將菜送到用戶手中;

  • 第二種:等一會再到窗口來問廚師,某個菜好了沒?如果沒有先處理其他事情,等會再去問一次;

第一種就是阻塞方式,第二種則是非阻塞的。

3.1.2 同步和異步:

同步和異步又是另外一個概念,它是事件本身的一個屬性。還拿前面點菜爲例,服務員直接跟廚師打交道,菜出來沒出來,服務員直接指導,但只有當廚師將菜送到服務員手上,這個過程纔算正常完成,這就是同步的事件。同樣是點菜,有些餐館有專門的傳菜人員,當廚師炒好菜後,傳菜員將菜送到傳菜窗口,並通知服務員,這就變成異步的了。其實異步還可以分爲兩種:帶通知的和不帶通知的。前面說的那種屬於帶通知的。有些傳菜員幹活可能主動性不是很夠,不會主動通知你,你就需要時不時的去關注一下狀態。這種就是不帶通知的異步。

對於同步的事件,你只能以阻塞的方式去做。而對於異步的事件,阻塞和非阻塞都是可以的。非阻塞又有兩種方式:主動查詢和被動接收消息。被動不意味着一定不好,在這裏它恰恰是效率更高的,因爲在主動查詢裏絕大部分的查詢是在做無用功。對於帶通知的異步事件,兩者皆可。而對於不帶通知的,則只能用主動查詢。

3.1.3 全異步I/O

回到I/O,不管是I還是O,對外設(磁盤)的訪問都可以分成請求和執行兩個階段。請求就是看外設的狀態信息(比如是否準備好了),執行纔是真正的I/O操作。在Linux 2.6之前,只有“請求”是異步事件,2.6之後才引入AIO(asynchronous I/O )把“執行”異步化。別看Linux/Unix是用來做服務器的,這點上比Windows落後了好多,IOCP(Windows上的AIO,效率極高)在Win2000上就有了。所以學linux的別老覺得Windows這裏不好那裏不好(Windows的多線程機制也由於linux)。

3.1.4 I/O的五種模型

根據以上分析,I/O可分爲五種模型:

sigio

3.2.5 異步I/O(aio)

當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者的輸入輸出操作。具體過程如下圖:

<a href="http://www.toxingwang.com/wp-content/uploads/2013/11/aio.png" class="cboxElement" rel="example4" 1712"="" style="text-decoration: none; color: rgb(1, 150, 227);">aio

3.2.6 I/O 模型總結(如下圖)

io模型

從上圖中我們可以看出,可以看出,越往後,阻塞越少,理論上效率也是最優。其五種I/O模型中,前三種屬於同步I/O,後兩者屬於異步I/O。

同步I/O:

阻塞I/O
非阻塞I/O
I/O複用(select和poll)

異步I/O:

信號驅動I/O(SIGIO) (半異步)
異步I/O(aio) (真正的異步)

異步 I/O 和 信號驅動I/O的區別:

信號驅動 I/O 模式下,內核可以複製的時候通知給我們的應用程序發送SIGIO 消息。
異步 I/O 模式下,內核在所有的操作都已經被內核操作結束之後纔會通知我們的應用程序。

3.3 Linux I/O模型的具體實現

3.3.1 主要實現方式有以下幾種:

  • select

  • poll

  • epoll

  • kqueue

  • /dev/poll

  • iocp

注,其中iocp是Windows實現的,select、poll、epoll是Linux實現的,kqueue是FreeBSD實現的,/dev/poll是SUN的Solaris實現的。select、poll對應第3種(I/O複用)模型,iocp對應第5種(異步I/O)模型,那麼epoll、kqueue、/dev/poll呢?其實也同select屬於同一種模型,只是更高級一些,可以看作有了第4種(信號驅動I/O)模型的某些特性,如callback機制。

3.3.2 爲什麼epoll、kqueue、/dev/poll比select高級?

答案是,他們無輪詢。因爲他們用callback取代了。想想看,當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll、kqueue、/dev/poll做的。這樣子說可能不好理解,那麼我說一個現實中的例子,假設你在大學讀書,住的宿舍樓有很多間房間,你的朋友要來找你。select版宿管大媽就會帶着你的朋友挨個房間去找,直到找到你爲止。而epoll版宿管大媽會先記下每位同學的房間號,你的朋友來時,只需告訴你的朋友你住在哪個房間即可,不用親自帶着你的朋友滿大樓找人。如果來了10000個人,都要找自己住這棟樓的同學時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高併發服務器中,輪詢I/O是最耗時間的操作之一,select、epoll、/dev/poll的性能誰的性能更高,同樣十分明瞭。

3.3.3 Windows or *nix (IOCP or kqueue、epoll、/dev/poll)?

誠然,Windows的IOCP非常出色,目前很少有支持asynchronous I/O的系統,但是由於其系統本身的侷限性,大型服務器還是在UNIX下。而且正如上面所述,kqueue、epoll、/dev/poll 與 IOCP相比,就是多了一層從內核copy數據到應用層的阻塞,從而不能算作asynchronous I/O類。但是,這層小小的阻塞無足輕重,kqueue、epoll、/dev/poll 已經做得很優秀了。

3.3.4 總結一些重點

只有IOCP(windows實現)是asynchronous I/O,其他機制或多或少都會有一點阻塞。

select(Linux實現)低效是因爲每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善

epoll(Linux實現)、kqueue(FreeBSD實現)、/dev/poll(Solaris實現)是Reacor模式,IOCP是Proactor模式。

Apache 2.2.9之前只支持select模型,2.2.9之後支持epoll模型

Nginx 支持epoll模型

Java nio包是select模型

四、Apache Httpd的工作模式

4.1 apache三種工作模式

我們都知道Apache有三種工作模塊,分別爲prefork、worker、event。

  • prefork:多進程,每個請求用一個進程響應,這個過程會用到select機制來通知。

  • worker:多線程,一個進程可以生成多個線程,每個線程響應一個請求,但通知機制還是select不過可以接受更多的請求。

  • event:基於異步I/O模型,一個進程或線程,每個進程或線程響應多個用戶請求,它是基於事件驅動(也就是epoll機制)實現的。

4.2 prefork的工作原理

如果不用“--with-mpm”顯式指定某種MPM,prefork就是Unix平臺上缺省的MPM.它所採用的預派生子進程方式也是 Apache1.3中採用的模式。prefork本身並沒有使用到線程,2.0版使用它是爲了與1.3版保持兼容性;另一方面,prefork用單獨的子進程來處理不同的請求,進程之間是彼此獨立的,這也使其成爲最穩定的MPM之一。

4.3 worker的工作原理

相對於prefork,worker是2.0版中全新的支持多線程和多進程混合模型的MPM。由於使用線程來處理,所以可以處理相對海量的請求,而系統資源的開銷要小於基於進程的服務器。但是,worker也使用了多進程,每個進程又生成多個線程,以獲得基於進程服務器的穩定性,這種MPM的工作方 式將是Apache2.0的發展趨勢。

4.4 event 基於事件機制的特性

一個進程響應多個用戶請求,利用callback機制,讓套接字複用,請求過來後進程並不處理請求,而是直接交由其他機制來處理,通過epoll機制來通知請求是否完成;在這個過程中,進程本身一直處於空閒狀態,可以一直接收用戶請求。可以實現一個進程程響應多個用戶請求。支持持海量併發連接數,消耗更少的資源。

五、如何提高Web服務器的併發連接處理能力

有幾個基本條件:

  • 基於線程,即一個進程生成多個線程,每個線程響應用戶的每個請求。

  • 基於事件的模型,一個進程處理多個請求,並且通過epoll機制來通知用戶請求完成。

  • 基於磁盤的AIO(異步I/O)

  • 支持mmap內存映射,mmap傳統的web服務器,進行頁面輸入時,都是將磁盤的頁面先輸入到內核緩存中,再由內核緩存中複製一份到web服務器上,mmap機制就是讓內核緩存與磁盤進行映射,web服務器,直接複製頁面內容即可。不需要先把磁盤的上的頁面先輸入到內核緩存去。

剛好,Nginx 支持以上所有特性。所以Nginx官網上說,Nginx支持50000併發,是有依據的。

六、Nginx優異之處

6.1 簡介

傳統上基於進程或線程模型架構的web服務通過每進程或每線程處理併發連接請求,這勢必會在網絡和I/O操作時產生阻塞,其另一個必然結果則是對內存或CPU的利用率低下。生成一個新的進程/線程需要事先備好其運行時環境,這包括爲其分配堆內存和棧內存,以及爲其創建新的執行上下文等。這些操作都需要佔用CPU,而且過多的進程/線程還會帶來線程抖動或頻繁的上下文切換,系統性能也會由此進一步下降。另一種高性能web服務器/web服務器反向代理:Nginx(Engine X),nginx的主要着眼點就是其高性能以及對物理計算資源的高密度利用,因此其採用了不同的架構模型。受啓發於多種操作系統設計中基於“事件”的高級處理機制,nginx採用了模塊化、事件驅動、異步、單線程及非阻塞的架構,並大量採用了多路複用及事件通知機制。在nginx中,連接請求由爲數不多的幾個僅包含一個線程的進程worker以高效的迴環(run-loop)機制進行處理,而每個worker可以並行處理數千個的併發連接及請求。

6.2 Nginx 工作原理

Nginx會按需同時運行多個進程:一個主進程(master)和幾個工作進程(worker),配置了緩存時還會有緩存加載器進程(cache loader)和緩存管理器進程(cache manager)等。所有進程均是僅含有一個線程,並主要通過“共享內存”的機制實現進程間通信。主進程以root用戶身份運行,而worker、cache loader和cache manager均應以非特權用戶身份運行。

主進程主要完成如下工作:

  • 讀取並驗正配置信息;

  • 創建、綁定及關閉套接字;

  • 啓動、終止及維護worker進程的個數;

  • 無須中止服務而重新配置工作特性;

  • 控制非中斷式程序升級,啓用新的二進制程序並在需要時回滾至老版本;

  • 重新打開日誌文件;

  • 編譯嵌入式perl腳本;

  • worker進程主要完成的任務包括:

  • 接收、傳入並處理來自客戶端的連接;

  • 提供反向代理及過濾功能;

  • nginx任何能完成的其它任務;

注:如果負載以CPU密集型應用爲主,如SSL或壓縮應用,則worker數應與CPU數相同;如果負載以IO密集型爲主,如響應大量內容給客戶端,則worker數應該爲CPU個數的1.5或2倍。

6.3 Nginx 架構

Nginx的代碼是由一個核心和一系列的模塊組成, 核心主要用於提供Web Server的基本功能,以及Web和Mail反向代理的功能;還用於啓用網絡協議,創建必要的運行時環境以及確保不同的模塊之間平滑地進行交互。不過,大多跟協議相關的功能和某應用特有的功能都是由nginx的模塊實現的。這些功能模塊大致可以分爲事件模塊、階段性處理器、輸出過濾器、變量處理器、協議、upstream和負載均衡幾個類別,這些共同組成了nginx的http功能。事件模塊主要用於提供OS獨立的(不同操作系統的事件機制有所不同)事件通知機制如kqueue或epoll等。協議模塊則負責實現nginx通過http、tls/ssl、smtp、pop3以及imap與對應的客戶端建立會話。在Nginx內部,進程間的通信是通過模塊的pipeline或chain實現的;換句話說,每一個功能或操作都由一個模塊來實現。例如,壓縮、通過FastCGI或uwsgi協議與upstream服務器通信,以及與memcached建立會話等。

6.4 Nginx 基礎功能

  • 處理靜態文件,索引文件以及自動索引;

  • 反向代理加速(無緩存),簡單的負載均衡和容錯;

  • FastCGI,簡單的負載均衡和容錯;

  • 模塊化的結構。過濾器包括gzipping, byte ranges, chunked responses, 以及 SSI-filter 。在SSI過濾器中,到同一個 proxy 或者 FastCGI 的多個子請求併發處理;

  • SSL 和 TLS SNI 支持;

6.5 Nginx IMAP/POP3 代理服務功能

  • 使用外部 HTTP 認證服務器重定向用戶到 IMAP/POP3 後端;

  • 使用外部 HTTP 認證服務器認證用戶後連接重定向到內部的 SMTP 後端;

  • 認證方法:

  • POP3: POP3 USER/PASS, APOP, AUTH LOGIN PLAIN CRAM-MD5;

  • IMAP: IMAP LOGIN;

  • SMTP: AUTH LOGIN PLAIN CRAM-MD5;

  • SSL 支持;

  • 在 IMAP 和 POP3 模式下的 STARTTLS 和 STLS 支持;

6.6 Nginx 支持的操作系統

  • FreeBSD 3.x, 4.x, 5.x, 6.x i386; FreeBSD 5.x, 6.x amd64;

  • Linux 2.2, 2.4, 2.6 i386; Linux 2.6 amd64;

  • Solaris 8 i386; Solaris 9 i386 and sun4u; Solaris 10 i386;

  • MacOS X (10.4) PPC;

  • Windows 編譯版本支持 windows 系列操作系統;

6.7 Nginx 結構與擴展

  • 一個主進程和多個工作進程,工作進程運行於非特權用戶;

  • kqueue (FreeBSD 4.1+), epoll (Linux 2.6+), rt signals (Linux 2.2.19+), /dev/poll (Solaris 7 11/99+), select, 以及 poll 支持;

  • kqueue支持的不同功能包括 EV_CLEAR, EV_DISABLE (臨時禁止事件), NOTE_LOWAT, EV_EOF, 有效數據的數目,錯誤代碼;

  • sendfile (FreeBSD 3.1+), sendfile (Linux 2.2+), sendfile64 (Linux 2.4.21+), 和 sendfilev (Solaris 8 7/01+) 支持;

  • 輸入過濾 (FreeBSD 4.1+) 以及 TCP_DEFER_ACCEPT (Linux 2.4+) 支持;

  • 10,000 非活動的 HTTP keep-alive 連接僅需要 2.5M 內存。

  • 最小化的數據拷貝操作;

6.8 Nginx 其他HTTP功能

  • 基於IP 和名稱的虛擬主機服務;

  • Memcached 的 GET 接口;

  • 支持 keep-alive 和管道連接;

  • 靈活簡單的配置;

  • 重新配置和在線升級而無須中斷客戶的工作進程;

  • 可定製的訪問日誌,日誌寫入緩存,以及快捷的日誌回捲;

  • 4xx-5xx 錯誤代碼重定向;

  • 基於 PCRE 的 rewrite 重寫模塊;

  • 基於客戶端 IP 地址和 HTTP 基本認證的訪問控制;

  • PUT, DELETE, 和 MKCOL 方法;

  • 支持 FLV (Flash 視頻);

  • 帶寬限制;

6.9 爲什麼選擇Nginx

  • 在高連接併發的情況下,Nginx是Apache服務器不錯的替代品: Nginx在美國是做虛擬主機生意的老闆們經常選擇的軟件平臺之一. 能夠支持高達 50,000 個併發連接數的響應, 感謝Nginx爲我們選擇了 epoll and kqueue 作爲開發模型。

  • Nginx作爲負載均衡服務器: Nginx 既可以在內部直接支持 Rails 和 PHP 程序對外進行服務, 也可以支持作爲 HTTP代理 服務器對外進行服務. Nginx採用C進行編寫, 不論是系統資源開銷還是CPU使用效率都比 Perlbal 要好很多。

  • 作爲郵件代理服務器: Nginx 同時也是一個非常優秀的郵件代理服務器(最早開發這個產品的目的之一也是作爲郵件代理服務器), Last.fm 描述了成功並且美妙的使用經驗.

  • Nginx 安裝非常的簡單 , 配置文件非常簡潔(還能夠支持perl語法),Bugs 非常少的服務器: Nginx 啓動特別容易, 並且幾乎可以做到7*24不間斷運行,即使運行數個月也不需要重新啓動. 你還能夠 不間斷服務的情況下進行軟件版本的升級 。

  • Nginx 的誕生主要解決C10K問題


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