Nginx 高性能,與其架構有關。html
Nginx架構: nginx運行時,在unix系統中以daemon形式在後臺運行,後臺進程包含一個master進程和多個worker進程。Nginx以多進程形式工做,也支持多線程方式,丹nginx默認採用多進程方式,也是主流方式。nginx
多進程模式,會有一個master進程和多個worker進程。web
Master進程管理worker進程,包括:算法
接收來自外界的信號;數據庫
向各worker進程發送信號;編程
監控work進程狀態;windows
當worker退出後(異常狀況下),自動從新啓動新worker進程。後端
多個worker進程之間對等,競爭來自客戶端的請求,一個請求,只會在一個worker中處理,一個worker進程不會處理其餘進程的請求。設計模式
Worker進程個數的設置,通常設置與機器cpu核數一致。數組
進程模式的好處:
每一個worker進程相互獨立,無需加鎖,節省鎖開銷;
採用獨立的進程,不會相互影響,一個進程退出,其餘進程服務不會中斷;
Worker異常退出,會致使當前worker上的全部請求失敗,不過不會影響全部請求,下降了風險。
多進程模式對併發的支持
每一個worker只有一個主線程,採用異步非阻塞方式來處理請求,使得nginx能夠同時處理成千上萬個請求。相比Apache,每一個請求會獨佔一個工做線程,併發上千時,就同時有幾千的線程在處理請求,線程帶來的內存佔用很大,線程的上下午切換帶來的cpu開銷也大,性能就上不去了。
異步非阻塞是什麼呢?
一個請求的完整過程:請求過來,創建鏈接,而後接收數據,接收數據後,再發送數據。
具體到系統底層,就是讀寫事件,當讀寫時間沒有準備好時,若是不用非阻塞的方式來調用,就得阻塞調用了,事件沒準備好,就只能等,等事件準備好再繼續。阻塞調用會進入內核等待,讓出cpu,對單線程的worker來講,顯然不合適,當網絡事件越多時,等待不少,cpu利用率上不去。非阻塞就是,事件沒有準備好,立刻返回eagain,表示事件還沒準備好,過會兒再來,過一會,再來檢查一下事件,直到事件準備好爲止,在這期間,你能夠先去作其餘事情,而後再來看看事件好了沒。這時,雖不阻塞了,可是還得不時來檢查事件的狀態,帶來的開銷也不小。因此有了異步非阻塞的事件處理機制,具體到系統調用就是像 select/poll/epoll/kquene這樣的系統調用。提供一種機制,讓你能夠同時監控多個事件,調用他們是阻塞的,可是能夠設置超時時間,在超時時間以內,若是有事件準備好了就返回。這種機制解決了上面的兩個問題,以epoll爲例,當事假沒準備好時,放到epoll裏,事件準備好了,就去讀寫,當讀寫返回eagain時,將它再次加入epoll,這樣,只要有事件準備好了,就去處理它,只有當全部事件都沒有準備好時,纔在epoll裏等着。這樣,就能夠支持大量的併發,這裏的併發請求,是指未處理完的請求,線程只有一個,同時處理的請求只有一個,只是在請求間不斷切換,切換是由於異步事件未準備好,主動讓出的。這裏的切換沒有什麼代價,能夠理解爲在循環處理多個準備好的事件,事實上也是。與多線程相比,這種事件處理方式有很大優點,不需建立線程,每一個請求佔用的內存也不多,沒有上下文切換,事件處理很是輕量級,沒有上下文切換的開銷,更多併發,只會佔更多的內存而已。如今的網絡服務器基本都採用這種方式,也是nginx性能高效的主要緣由。
推薦設置worker數與cpu的核數一致,由於更多的worker,會致使進程競爭cpu資源,從而帶來沒必要要的上下文切換。
怎樣操做運行的nignx呢?master進程會接收來自外界發來的信號,所以要控制nginx,經過kill向master進程發送信號就能夠了。如 kill –HUP pid,重啓nginx,或從新加載配置,而不中斷服務。Master進程在接到這個信號後,會先從新加載配置文件,而後再啓動新的worker進程,並向全部老的worker進程發信號,再也不接收新的請求,而且在處理完全部未處理完的請求後,退出。新的worker啓動後,就開始接收新的請求。
直接給master發信號,是比較老的操做方法,在nginx0.8版本後,可使用命令行參數,方便管理,如./nginx –s reload ,重啓nginx; ./nginx –s stop,中止nginx。這種方式的內部原理是,執行命令時,會啓動一個新的nginx進程,該進程在解析到reload參數後,知道目標是控制nginx從新加載配置文件,它會向master進程發送信號,接下來的處理,和直接向master進程發送信號同樣。
Worker進程是怎麼處理請求的呢?
一個鏈接請求過來,每一個進程都有可能處理這個鏈接。Worker進程是從master進程fork出來的,在master進程裏,先創建好須要listen的socket(listenfd)後,而後再fork出多個worker進程。全部worker進程的listenfd會在新鏈接到來時變得可讀,爲了保證只有一個進程處理該鏈接,全部worker進程在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個進程註冊listenfd讀事件,在讀事件裏調用accept接受該鏈接。當一個worker進程在accept這個鏈接以後,開始讀取請求,解析請求,產生數據後,再返回給客戶端,最後才斷開鏈接,這就是一個完整的請求處理。一個請求,徹底由worker處理,且只在一個worker裏處理。
Nginx中connection是對tcp鏈接的封裝,包括鏈接的socket,讀事件,寫事件。
Nginx怎麼處理一個鏈接的呢?nginx在啓動時,會解析配置文件,獲得須要監聽的端口與ip,而後在nginx的master進程裏,先初始化這個監控的socket,而後再fork出多個子進程,子進程競爭accept新的鏈接。此時,客戶端就能夠像nginx發起鏈接了,當客戶端與服務器經過三次握手創建好一個鏈接,nginx的某一個子進程會accept成功,獲得這個socket,而後建立nginx對鏈接的封裝,接着,設置讀寫事件處理函數並添加讀寫事件來與客戶端進行數據的交換。最後,nginx或客戶端主動關掉鏈接。
Nginx也能夠做爲客戶端來請求其餘server的數據,此時,與其它server建立的鏈接,也封裝在ngx_connection中。
Nginx中,每一個進程會有一個鏈接數的最大上限,這個上限與系統對fd的限制不同。操做系統中,使用ulimit -n,能夠獲得一個進程所能打開的fd的最大數,即nofile,由於每一個socket會佔用一個fd,因此這個會限制進程的最大鏈接數,fd用完後,再建立socket,就會失敗。Nginx經過設置worker_connections來設置每一個進程支持的最大鏈接數,若是該值大於nofile,那麼實際的最大鏈接數是nofile,nginx會有警告。Nginx在實現時,是經過一個鏈接池來管理的,每一個worker進程都有一個獨立的鏈接池,鏈接池大小是worker_connections。這裏鏈接池裏面保存的其實不是真實的鏈接,只是一個worker_connections大小的ngx_connection_t結構的數組。Nginx經過一個鏈表free_connections來保存全部的空閒ngx_connection_t.每次獲取一個鏈接時,就從空閒鏈接鏈表中獲取一個,用完後,再放回空閒鏈接鏈表裏面。
Worker_connections,表示每一個worker所能創建鏈接的最大值,一個nginx能創建的最大鏈接數是:worker_connections * worker_processes.所以對於HTTP請求本地資源,最大併發能夠是 worker_connections * worker_processes.而若是是HTTP做爲反向代理來講,最大併發數是 worker_connections * worker_processes/2.由於做爲反向代理服務器,每一個併發會創建與客戶端的鏈接和與後端服務器的鏈接,佔用2個鏈接。
如何保證worker進程競爭處理鏈接的公平呢?
若是某個進程獲得accept的機會比較多,它的空閒鏈接會很快用完,若是不提早作一些控制,當accept到一個新的tcp鏈接後,由於沒法獲得空閒鏈接,並且沒法將此鏈接轉交其餘進程,最終致使此tcp鏈接得不處處理。而其餘進程有空餘鏈接,卻沒有處理機會。如何解決這個問題呢?
Nginx的處理得先打開accept_mutex,此時只有得到了accept_mutex的進程纔會去添加accept事件,nginx會控制進程是否添加accept事件。Nginx使用一個叫ngx_accept_disabled變量控制是否競爭accept_mutex鎖。這個變量與worker進程的剩餘鏈接數有關,當該變量大於0時,就不去嘗試獲取鎖,等於讓出獲取鏈接的機會。這樣就能夠控制多進程間鏈接的平衡了。
http請求是請求應答式的,若是咱們知道每一個請求頭與相應體的長度,那麼咱們能夠在一個鏈接上面執行多個請求。即長鏈接。若是當前請求須要有body,那麼nginx就須要客戶端在請求頭中指定content-length來表面body的大小,不然返回400錯誤。那麼響應體的長度呢?http協議中關於響應body長度的肯定:
1 對於http1.0 協議來講,若是響應頭中有content-length頭,則以content-length的長度就能夠知道body的長度,客戶端在接收body時,能夠依照這個長度接收數據,接收完後,就表示該請求完成。若是沒有content-length,客戶端會一直接收數據,直到服務端主動端口鏈接,才表示body接收完
2 對於http1.1 協議,若是響應頭中transfer-encoding爲chunked傳輸,表示body是流式輸出,body被分紅多個塊,每塊的開始會標示出當前塊的長度,此時,body不須要指定長度。若是是非chunked傳輸,並且有Content-length,則按照content-length來接收數據。不然,非chunked且沒有content-length,則客戶端接收數據,知道服務器主動斷開。
客戶端請求頭中connection爲close,表示客戶端要關掉長鏈接,若是是keep-alive,則客戶端須要打開長鏈接。客戶端的請求中沒有connection這個頭,根據協議,若是是http1.0,默認是close,若是是http1.1,默認是keep-alive。若是要keep-alive,nginx在輸出完響應體後,會設置當前鏈接的keepalive屬性,而後等待客戶端下一次請求,nginx設置了keepalive的等待最大時間。通常來講,當客戶端須要屢次訪問同一個server時,打開keepalive的優點很是大。
http1.1中引入Pipeline,就是流水線做業,能夠看作是keepalive的昇華。Pipeline也是基於長鏈接的。目前就是利用一個鏈接作屢次請求,若是客戶端要提交多個請求,對於keepalive,第二個請求,必需要等到第一個請求的響應接收完後,才能發起。獲得兩個響應的時間至少是2*RTT。而對於pipeline,客戶端沒必要等到第一個請求處理完,就能夠發起第二個請求。獲得兩個響應的時間可能可以達到1*RTT。Nginx是直接支持pipeline的。Nginx對pipeline中的多個請求的處理不是並行的,而是一個接一個的處理,只是在處理第一個請求的時候,客戶端就能夠發起第二個請求。這樣,nginx利用pipeline能夠減小從處理完一個請求後到等待第二個請求的請求頭數據的時間。
具體參見http://seanlook.com/2015/05/17/nginx-install-and-config/
或者http://blog.csdn.net/guodongxiaren/article/details/40950249
安裝nginx
yum install nginx-1.6.3
Nginx配置文件主要有4部分,main(全局設置)、server(主機設置)、upstream(上游服務器設置,主要爲反向代理,負載均衡相關配置)和location(url匹配特定位置的設置),每部分包含若干指令。
Main部分的設置影響其餘全部部分的設置;
Server部分主要用於指定虛擬機主機域名,ip和端口;
Upstream的指令用於設置一系列的後端服務器,設置反向代理及後端服務器的負載均衡;
Location部分用於匹配網頁位置(如,跟目錄「/」,」/images」等)。
它們之間的關係是,server繼承main,location繼承server,upstream既不會繼承指令也不會被繼承。
和十年前相比,目前的互聯網已經不可思議的普遍應用和普及。從NCSA用Apache搭的web服務器提供的可點擊的文本HTML,已然進化成超過20億人在線的通訊媒介。隨着永久在線的我的電腦,移動終端以及平板電腦的增多,互聯網在快速變化,經濟系統也徹底數字有線化。提供實時可用信息和娛樂的在線服務變得更加複雜精巧。在線業務的安全需求也急劇變化。網站比從前更加複雜,須要在工程上作的更具備健壯性和可伸縮性。
併發老是網站架構最大的挑戰之一。因爲web服務的興起,併發的數量級在不斷增加。熱門網站爲幾十萬甚至幾百萬的同時在線用戶提供服務並不尋常。十年前,併發的主要緣由是因爲客戶端接入速度慢--用戶使用ADSL或者撥號商務。如今,併發是由移動終端和新應用架構所帶來,這些應用一般基於持久鏈接來爲客戶端提供新聞,微博,通知等服務。另外一個重要的因素就是現代瀏覽器行爲變了,他們瀏覽網站的時候會同時打開4到6個鏈接來加快頁面加載速度。
舉例說明一下慢客戶端的問題,假設一個Apache網站產生小於100KB的響應--包含文本或圖片的網頁。生成這個頁面可能須要1秒鐘,可是若是網速只有80kbps(10KB/s),須要花10秒才能把這個頁面發送到客戶端。基本上,web服務器相對快速的推送100KB數據,而後須要等待10秒發送數據以後才能關閉鏈接。那麼如今若是有1000個同時鏈接的客戶端請求相同的頁面,那麼若是爲每一個客戶端分配1MB內存,就須要1000MB內存來爲這1000個客戶端提供這個頁面。實際上,一個典型的基於Apache的web服務器一般爲每一個鏈接分配1MB內存,而移動通訊的有效速度也一般是幾十kbps。雖然藉助於增長操做系統內核socket緩衝區大小,能夠優化發送數據給慢客戶端的場景,可是這並非一個常規的解決方案,而且會帶來沒法預料的反作用。
隨着持久鏈接的使用,併發處理的問題更加明顯。爲了不新建HTTP鏈接所帶來的延時,客戶端須要保持鏈接,這樣web服務器就須要爲每一個鏈接上的客戶端分配必定數量的內存。
所以,爲了處理持續增加的用戶帶來的負載和更高量級的併發,網站須要大量高效的組件。而另外一方面,web服務器軟件運行在諸如硬件(CPU,內存,磁盤),網絡帶寬,應用和數據存儲架構等之上,這些基礎設施顯然也很重要。於是,隨着同時在線數和每秒請求數的增加,web服務器性能也應該可以非線性擴展。
Apache web服務器軟件發源於1990年代,目前在互聯網網站上佔有率第一。Apache的架構適合當時的操做系統和硬件,而且也符合當時的互聯網情況:一個網站一般使用一臺物理服務器運行一個Apache實例。2000年以後,顯然這種單服務器模型已經沒法簡單擴展來知足日益增加的web服務需求。雖然Apache爲新功能開發提供了堅實的基礎,但他爲每一個新鏈接派生一個進程的作法(譯註:Apache從2.4版本起已經支持事件模型),不適合網站的非線性擴展。最終,Apache成爲一個通用的web服務器軟件,聚焦於功能多樣化,第三方擴展開發,以及web應用開發的通用性。然而,當硬件成本愈來愈低,每一個鏈接消耗的CPU和內存愈來愈多,使用這樣功能繁多的單一軟件再也不具備可伸縮性。
於是,當服務器硬件、操做系統和網絡設施再也不成爲網站增加的主要限制因素時,網站開發者開始尋求更高效的手段來架設web服務器。大約十年前,著名軟件工程師Daniel Kegel提出:「是時候讓web服務器支持同時處理10000客戶端了」,而且預言瞭如今稱爲雲服務的技術。Kegel的C10K設想明顯推進了許多人嘗試解決這個問題--經過優化web服務器軟件來支持大規模客戶端鏈接的併發處理,nginx是其中作的最成功者之一。
爲了解決10000個併發鏈接的C10K問題,nginx基於一個徹底不一樣的架構—更適合每秒同時鏈接數和請求數非線性增加。Nginx基於事件模型,而沒有模仿Apache爲每一個請求派生新進程或線程的作法。最終結果就是即便負載增長了,內存和CPU使用事件始終保持可預期。Nginx使用普通的硬件就能在一個服務器上處理數萬的併發鏈接。
Nginx的第一個版本發佈以後,通常被用來同Apache一同部署,HTML、CSS、JavaScript腳本和圖片等靜態內容由nginx處理,來下降Apache應用服務器的併發和延時。隨着開發演進的過程,nginx增長了FastCGI、uswge和SCGI等協議的支持,以及對分佈式內存對象緩存系統如memcached的支持。也增長了其餘有用的功能,例如支持負載均衡和緩存的反向代理。這些附加功能使nginx成爲一個高效的工具集,用於構建可伸縮的web基礎設施。
2012年2月,Apache 2.4.x版本發佈。雖然增長了新的併發處理核心模塊和代理模塊,用於增強可伸縮性和性能,但要說性能、併發能力和資源利用率是否能遇上或超過純事件驅動模型的web服務器還爲時尚早。Apache新版本具備了更好的性能值得高興,對於nginx+Apache的web網站架構,雖然這可以緩解後端潛在的瓶頸,但並不能解決所有問題。
部署nginx最關鍵的好處就是可以高性能高效的處理高併發。同時,還有更多有意思的好處。
最近幾年,web架構擁抱解耦的理念而且將應用層設施從web服務器中分離。雖然如今僅僅是將原先基於LAMP(Linux, Apache, MySQL, PHP, Python or Perl)所構建的網站,變爲基於LEMP(E表示Engine x)的。可是,愈來愈多的實踐是將web服務器推入基礎設施的邊緣,而且用不一樣的方法整合這些相同或更新的應用和數據庫工具集。
Nginx很適合作這些工做。他提供了必要的關鍵功能用於方便將下列功能從應用層剝離到更高效的邊緣web服務器層:併發、長鏈接處理、SSL,靜態內容、壓縮和緩存、鏈接和請求限速,以及HTTP媒體流等。Nginx同時也容許直接整合memcached、Redis或者其餘的NoSQL解決方案,加強爲處理大規模併發用戶的性能。
隨着現代編程語言和開發包普遍使用,愈來愈多的公司改變了應用開發和部署的方式。Nginx已經成爲這些改變範例之中的最重要的部件之一,而且已經幫助許多公司在預算內快速啓動和開發他們的web服務。
Nginx開發始於2002年,2004年基於2-clause BSD受權正式對外發布。自發布起,Nginx用戶就在不斷增加,而且貢獻提議,提交bug報告、建議和評測報告,這極大的幫助和促進了整個社區的發展。
Nginx代碼徹底用C語言從頭寫成,已經移植到許多體系結構和操做系統,包括:Linux、FreeBSD、Solaris、Mac OS X、AIX以及Microsoft Windows。Nginx有本身的函數庫,而且除了zlib、PCRE和OpenSSL以外,標準模塊只使用系統C庫函數。並且,若是不須要或者考慮到潛在的受權衝突,能夠不使用這些第三方庫。
談談關於Windows版本nginx。當nginx在Windows環境下工做時,Windows版本的nginx更像是概念驗證版本,而不是全功能移植。這是因爲目前nginx和Windows內核架構之間交互的某些限制致使。Windows版本ngnix已知的問題包括:低併發鏈接數、性能下降、不支持緩存和帶寬策略。將來Windows版本的nginx的功能會更接近主流版本。
傳統基於進程或線程的模型使用單獨的進程或線程處理併發鏈接,於是會阻塞於網絡或I/O操做。根據不一樣的應用,就內存和CPU而言,這是很是低效的。派生進程或線程須要準備新的運行環境,包括在內存上分配堆和棧、生成一個新的運行上下文。建立這些東西還須要額外的CPU時間,並且過分的上下文切換引發的線程抖動最終會致使性能低下。全部這些複雜性在如Apache web服務器的老架構上一覽無遺。在提供豐富的通用應用功能和優化服務器資源使用之間須要作一個權衡。
最先的時候,nginx但願爲動態增加的網站得到更好的性能,而且密集高效的使用服務器資源,因此其使用了另一個模型。受不斷髮展的在不一樣操做系統上開發基於事件模型的技術驅動,最終一個模塊化,事件驅動,異步,單線程,非阻塞架構成爲nginx代碼的基礎。
Nginx大量使用多路複用和事件通知,而且給不一樣的進程分配不一樣的任務。數量有限的工做進程(Worker)使用高效的單線程循環處理鏈接。每一個worker進程每秒能夠處理數千個併發鏈接、請求。
Nginx worker的代碼包含核心和功能模塊。核心負責維護一個緊湊的事件處理循環,而且在請求處理的每一個階段執行對應的模塊代碼段。模塊完成了大部分展示和應用層功能。包括從網絡和存儲設備讀取、寫入,轉換內容,進行輸出過濾,SSI(server-side include)處理,或者若是啓用代理則轉發請求給後端服務器。
nginx模塊化的架構容許開發者擴展web服務器的功能,而不須要修改nginx核心。Nginx模塊可分爲:核心、事件模塊,階段處理器,協議、變量處理器,過濾器,上游和負載均衡器等。目前,nginx不支持動態加載模塊,即模塊代碼是和nginx核心代碼一塊兒編譯的。模塊動態加載和ABI已經計劃在未來的某個版本開發。更多關於不一樣模塊角色的詳細信息可在14.4章找到。
Nginx在BSD、Linux和Solaris系統上使用kqueue、epoll和event ports等技術,經過事件通知機制來處理網絡鏈接和內容獲取,包括接受、處理和管理鏈接,而且大大加強了磁盤IO性能。目的在於儘量的提供操做系統建議的手段,用於從網絡進出流量,磁盤操做,套接字讀取和寫入,超時等事件中及時異步地獲取反饋。Nginx爲每一個基於Unix的操做系統大量優化了這些多路複用和高級I/O操做的方法。
圖14.1展現了nginx架構的高層設計。
前面提到過,nginx不爲每一個鏈接派生進程或線程,而是由worker進程經過監聽共享套接字接受新請求,而且使用高效的循環來處理數千個鏈接。Nginx不使用仲裁器或分發器來分發鏈接,這個工做由操做系統內核機制完成。監聽套接字在啓動時就完成初始化,worker進程經過這些套接字接受、讀取請求和輸出響應。
事件處理循環是nginx worker代碼中最複雜的部分,它包含複雜的內部調用,而且嚴重依賴異步任務處理的思想。異步操做經過模塊化、事件通知、大量回調函數以及微調定時器等實現。總的來講,基本原則就是儘量作到非阻塞。Nginx worker進程惟一會被阻塞的情形是磁盤性能不足。
因爲nginx不爲每一個鏈接派生進程或線程,因此內存使用在大多數狀況下是很節約而且高效的。同時因爲不用頻繁的生成和銷燬進程或線程,因此nginx也很節省CPU時間。Nginx所作的就是檢查網絡和存儲的狀態,初始化新鏈接並添加到主循環,異步處理直到請求結束才從主循環中釋放並刪除。兼具精心設計的系統調用和諸如內存池等支持接口的精確實現,nginx在極端負載的狀況下一般能作到中低CPU使用率。
nginx派生多個worker進程處理鏈接,因此可以很好的利用多核CPU。一般一個單獨的worker進程使用一個處理器核,這樣能徹底利用多核體系結構,而且避免線程抖動和鎖。在一個單線程的worker進程內部不存在資源匱乏,而且資源控制機制是隔離的。這個模型也容許在物理存儲設備之間進行擴展,提升磁盤利用率以免磁盤I/O致使的阻塞。將工做負載分佈到多個worker進程上最終能使服務器資源被更高效的利用。
針對某些磁盤使用和CPU負載的模式,nginx worker進程數應該進行調整。這裏的規則比較基本,系統管理員應根據負載多嘗試幾種配置。一般推薦:若是負載模式是CPU密集型,例如處理大量TCP/IP協議,使用SSL,或者壓縮數據等,nginx worker進程應該和CPU核心數相匹配;若是是磁盤密集型,例如從存儲中提供多種內容服務,或者是大量的代理服務,worker的進程數應該是1.5到2倍的CPU核心數。一些工程師基於獨立存儲單元的數目來決定worker進程數,雖然這個方法的有效性取決於磁盤存儲配置的類型,。
Nginx開發者在下個版本中要解決的一個主要問題是怎麼避免磁盤I/O引發的阻塞。目前,若是沒有足夠的存儲性能爲一個worker進程的磁盤操做提供服務,這個進程就會阻塞在磁盤讀寫操做上。一些機制和配置指令用於緩解這個磁盤I/O阻塞的場景,最顯著的是sendfile和AIO指令,這一般能夠大幅提高磁盤性能。應該根據數據集(data set),可用內存數,以及底層存儲架構等來規劃安裝nginx。
當前的worker模型的另外一個問題是對嵌入腳本的支持有限。舉例來講,標準的nginx發佈版只支持Perl做爲嵌入腳本語言。這個緣由很簡單:嵌入腳本極可能會在任何操做上阻塞或者異常退出,這兩個行爲都會致使worker進程掛住而同時影響數千個鏈接。將腳本更簡單,更可靠地嵌入nginx,而且更適合普遍應用的工做已經列入計劃。
Nginx在內存中運行多個進程,一個master進程和多個worker進程。同時還有一些特殊用途的進程,例如緩存加載和緩存管理進程。在nginx 1.x版本,全部進程都是單線程的,使用共享內存做爲進程間通訊機制。Master進程使用root用戶權限運行,其餘進程使用非特權用戶權限運行。
master進程負責下列工做:
Worker進程接受、處理來自客戶端的鏈接,提供反向代理和過濾功能以及其餘nginx所具備的全部功能。因爲worker進程是web服務器每日操做的實際執行者,因此對於監控nginx實例行爲,系統管理員應該保持關注worker進程。
緩存加載進程負責檢查磁盤上的緩存數據而且在內存中維護緩存元數據的數據庫。基本上,緩存加載進程使用特定分配好的目錄結構來管理已經存儲在磁盤上的文件,爲nginx提供準備,它會遍歷目錄,檢查緩存內容元數據,當全部數據可用時就更新相關的共享內存項。
緩存管理進程主要負責緩存過時和失效。它在nginx正常工做時常駐內存中,當有異常則由master進程重啓。
Nginx在文件系統上使用分層數據存儲實現緩存。緩存主鍵可配置,而且可以使用不一樣特定請求參數來控制緩存內容。緩存主鍵和元數據存儲在共享內存段中,緩存加載進程、緩存管理進程和worker進程都能訪問。目前不支持在內存中緩存文件,但能夠用操做系統的虛擬文件系統機制進行優化。每一個緩存的響應存儲到文件系統上的不一樣文件,Nginx配置指令控制存儲的層級(分幾級和命名方式)。若是響應須要緩存到緩存目錄,就從URL的MD5哈希值中獲取緩存的路徑和文件名。
將響應內容緩存到磁盤的過程以下:當nginx從後端服務器讀取響應時,響應內容先寫到緩存目錄以外的一個臨時文件。nginx完成請求處理後,就將這個臨時文件重命名並移到緩存目錄。若是用於代理功能的臨時目錄位於另一個文件系統,則臨時文件會被拷貝一次,因此建議將臨時目錄和緩存目錄放到同一個文件系統上。若是須要清除緩存目錄,也能夠很安全地刪除文件。一些第三方擴展能夠遠程控制緩存內容,並且整合這些功能到主發佈版的工做已經列入計劃。
Nginx配置系統來自於Igor Sysoev使用Apache的經驗。他認爲可擴展的配置系統是web服務器的基礎。當維護龐大複雜的包括大量的虛擬服務器、目錄、位置和數據集等配置時,會遇到可伸縮性問題。對於一個相對大點的網站,系統管理員若是沒有在應用層進行恰當的配置,那麼這將會是一個噩夢。
因此,nginx配置爲簡化平常維護而設計,而且提供了簡單的手段用於web服務器未來的擴展。
配置文件是一些文本文件,一般位於/usr/local/etc/nginx
或/etc/nginx
。主配置文件一般命名爲nginx.conf
。爲了保持整潔,部分配置能夠放到單獨的文件中,再自動地被包含到主配置文件。但應該注意的是,nginx目前不支持Apache風格的分佈式配置文件(如.htaccess文件),全部和nginx行爲相關的配置都應該位於一個集中的配置文件目錄中。
Master進程啓動時讀取和校驗這些配置文件。因爲worker進程是從master進程派生的,因此可使用一份編譯好、只讀的配置信息。配置信息結構經過常見的虛擬內存管理機制自動共享。
Nginx配置具備多個不一樣的上下文,如:main, http, server, upstream, location (以及用於郵件代理的 mail ) 等指令塊。這些上下文不重疊,例如,一個location 指令塊是不能放入main指令塊中。而且,爲了不沒必要要的歧義,不存在一個相似於「全局web服務器」的配置。Nginx配置特地作的整潔和富有邏輯性,容許用戶能夠創建包含上千個指令的複雜的配置文件。在一次私人談話中,Sysoev說:「全局服務器配置中的位置、目錄和其餘一些指令是Apache中我所不喜歡的特性,因此這就是不在nginx實現這些的緣由。」
配置語法、格式和定義遵循一個所謂的C風格協定。這種構建配置文件的方法在開源軟件和商業軟件中有普遍的應用。經過設計,C風格配置很適合嵌套描述,富有邏輯性,易於建立、讀取和維護,深受廣大工程師喜歡。同時nginx的C風格配置也易於自動化。
雖然一些nginx配置指令看起來像Apahce配置的一部分,可是設置一個nginx實例是徹底不一樣的體驗。例如,雖然nginx支持重寫規則,可是系統管理員要手工的轉換Apache重寫配置使之適合nginx風格。一樣,重寫引擎的實現也是不同的。
一般來講,nginx設置也提供了幾種原始機制的支持,對於高效的web服務器配置頗有幫助。有必要簡單瞭解下變量和try_files
指令,這些差很少是nginx所獨有的。Nginx開發了變量用於提供附加的更強大的機制來控制運行時的web服務器配置。變量爲快速賦值作了優化,而且在內部預編譯爲索引。賦值是按需計算的,例如,變量的值一般只在這個請求的生命週期中計算一次,然後緩存起來。變量可在不一樣的配置指令中使用,爲描述條件請求處理行爲提供了更多彈性。
try_files
指令對於用更適當的方式逐漸替換if 條件配置語句是很重要的,而且它設計用來快速高效的嘗試不一樣的URI與內容之間的映射。總的來講,try_files
指令很好用,而且及其高效和有用。推薦讀者完整的看看這個指令,並在任何能用的地方用上它。
前面提到過,nginx代碼包含核心和其餘模塊。核心負責提供web服務器的基礎,web和郵件反向代理功能;實現底層網絡協議,構建必要的運行環境,而且保證不一樣模塊之間的無縫交互。可是,大部分協議相關以及應用相關的特性是由其餘模塊完成,而不是核心模塊。
在內部,nginx經過模塊流水線或模塊鏈處理鏈接。換言之,每一個操做都有一個模塊作對應的工做。例如:壓縮,修改內容,執行SSI,經過FastCGI或uwsgi協議同後端應用服務器通訊,以及同memcached通訊等。
在覈心和實際功能模塊之間,有兩個模塊http和mail。這兩個模塊在覈心和底層組件之間提供了附加抽象層。這些模塊處理同各自應用層協議相關的事件序列,如實現HTTP、SMTP或IMAP。與核心一塊兒,這些上層模塊負責以正確的次序調用各自的功能模塊。雖然目前HTTP協議是做爲http模塊的一部分實現的,但未來計劃將其獨立爲一個功能模塊,以支持其餘協議,如SPDY(參考「SPDY: An experimental protocol for a faster web」)。
功能模塊能夠分爲事件模塊,階段處理器,輸出過濾器,變量處理器,協議模塊,上游和負載均衡器等類型。雖然事件模塊和協議也用於mail模塊,可是這些模塊大部分用於補充nginx的HTTP功能。事件模塊提供了基於操做系統的事件通知機制,如kqueue 或 epoll,這些取決於操做系統的能力和構建配置。協議模塊容許nginx經過HTTPS, TLS/SSL, SMTP, POP3 和 IMAP等協議通訊。
一個典型的HTTP請求處理週期以下:1. 客戶端發送HTTP請求。2. nginx核心從配置文件查找匹配該請求的位置,根據這個位置信息選擇適當的階段處理器。3. 若是配置爲反向代理,負載均衡器挑選一個上游服務器用於轉發請求。4. 階段處理器完成工做,而且傳遞每一個輸出緩衝區給第一個過濾器。5. 第一個過濾器傳遞輸出給第二個過濾器。6. 第二個過濾器傳遞輸出給第三個等等。7. 最終響應發送給客戶端。
Nginx模塊是高度可定製化的。它經過一系列指向可執行函數的回調指針來工做。於是,帶來的反作用就是爲第三方開發者加劇了負擔,由於他們必須精確的定義模塊應怎麼運行和什麼時候運行。Nginx的API和開發者文檔都通過優化使之更具備可用性來減輕開發難度。
一些在nginx中插入模塊的例子:
在Worker內部,生成響應的過程以下:
ngx_worker_process_cycle()
事件循環自身(步驟5和6)確保增量產生響應而且流式發送給客戶端。
更詳細的處理HTTP請求過程以下:
當nginx處理一個HTTP請求時,會通過多個處理階段。每一個階段都調用對應的處理器。一般,階段處理器處理一個請求後產生對應的輸出,階段處理器在配置文件的location中定義。
階段處理器通常作四件事情:獲取location配置,產生適當的響應,發送響應頭,發送響應體。處理器函數有一個參數:描述請求的結構體。請求結構體有許多關於客戶端請求的有用信息,例如:請求方法類型,URI和請求頭等。
當讀取完HTTP請求頭以後,nginx查找相關的虛擬服務器配置,若是找到虛擬服務器,請求會通過下面六個階段:
爲了給請求生成必要的響應內容,nginx傳遞請求給匹配的內容處理器。根據location配置,nginx會先嚐試無條件處理器,如perl
,proxy_pass
,flv
,mp4
等。若是這個請求不匹配這幾個內容處理器,將會按下面順序挑選一個處理器:random index
,index
,autoindex
,gzip_static
,static
。
Nginx文檔中有Index模塊的詳細內容,這個模塊只處理結尾爲斜槓的請求。若是不匹配mp4
或autoindex
模塊,則認爲響應內容是磁盤上的一個文件或目錄(即靜態的),這由static
內容處理器完成服務。若是是目錄,將自動重寫URI保證結尾是一個斜槓(從而發起一個HTTP重定向)。
內容處理器產生的內容則被傳遞到過濾器。過濾器也同location相關,一個location可配置多個過濾器。過濾器加工處理器產生的輸出。處理器的執行順序在編譯時決定,對於原生過濾器,順序是已經定義好的,對於第三方過濾器,能夠在編譯階段設置前後順序。當前的nginx實現中,過濾器只能修改輸出的數據,還不能編寫修改輸入的數據的過濾器。輸入過濾器將在未來的版本提供。
過濾器遵循一個特定的設計模式。過濾器被調用後開始工做,調用下一個過濾器直到過濾器鏈中的最後一個。完成以後,nginx結束響應。過濾器不用等待前面的過濾器結束。一旦前一個過濾器提供的輸入已經可用,下一個過濾器即可以啓動本身的工做(很像Unix中的管道)。於是,在從上游服務器接收到全部的響應以前,所生成的輸出響應已經被髮送給客戶端。
過濾器有header filter和body filter,nginx將響應的header和body分別發送給相關的過濾器。
Header filter包含3個基本步驟:
body filter轉換所生成的內容。body filter的一些例子:
通過過濾器鏈以後,響應被髮送到writer。有兩個額外的具備特定功能的過濾器與writer相關,copy filter和postpone filter。Copy filter負責將相關的響應內容填充到內存緩衝區,這些響應內容有可能存儲在反向代理的臨時目錄。Postpone filter用於子請求處理。
子請求是一個處理請求、響應很重要的機制,同時也是nginx最強大的功能之一。經過子請求,Nginx能夠返回另外一個URL的響應,這個URL與客戶端最初請求的URL不一樣。一些web框架稱之爲內部跳轉,但nginx功能更強,不只能運行多個子請求並將這些子請求的響應合併成一個,並且還能嵌套和分級。子請求能夠產生子-子請求,子-子請求能產生子-子-子請求。子請求能夠映射到磁盤文件,其餘處理,或者上游服務器。子請求在根據原始響應數據插入附加內容時頗有用。例如,SSI模塊使用一個過濾器解析返回文檔的內容,而後用指定URL的內容來替換include指令。或者作一個過濾器,可以在一個URL產生的響應內容以後附加一些新的文檔內容。
上游(upstream)和負載均衡器一樣也值得簡單介紹一下。上游用於實現反向代理處理器(proxy_pass
處理器)。上游模塊組裝好請求發送給上游服務器(或稱爲「後端」),而後接收上游服務器返回的響應。這個過程不調用輸出過濾器。上游模塊僅僅設置回調函數,用於當上遊服務器可讀或可寫時調用。回調函數實現下列功能:
若是上游服務器大於一個,負載均衡器模塊可附加在proxy_pass
處理器上,用於提供選擇上游服務器的能力。負載均衡器註冊了一個配置文件指令,提供附加的上游服務器初始化功能(經過DNS解析上游服務器名字等),初始化鏈接結構體,決定如何路由請求,而且更新狀態信息。目前,nginx支持兩種標準的上游服務器負載均衡規則:輪詢和ip哈希。
上游模塊和負載均衡處理機制的算法能檢測上游服務器異常,並將新請求從新路由到可用的上游服務器,還有更多的工做計劃增強這個功能。總之,負載均衡器的改進計劃更多些,下個版本的nginx將大幅度提高在不一樣上游服務器之間分發負載和健康檢測的機制。
還有一些有意思的模塊在配置文件中提供了額外的變量供使用。這些變量經過不一樣的模塊生成和更新,有兩個模塊徹底用於變量:geo
和map
。geo
模塊用於更方便的基於IP地址追蹤客戶端地址,這個模塊能夠根據客戶端IP地址生成任意變量。另外一個map
模塊容許從一個變量生成另外一個變量,提供將主機名和其餘變量方便的進行映射的基本能力。這類模塊稱爲變量處理器。
nginx worker進程實現的內存分配機制從某方面來講來自於Apache。Nginx內存管理的高層描述:對於每一個鏈接,必要的內存緩衝區是動態分配的,用於存儲或操縱請求、響應的頭和體,當鏈接關閉時釋放。很重要的一點是nginx儘量的去避免在內存中拷貝數據,大部分的數據經過指針進行傳遞,而不是調用memcpy。
更深刻一點,當一個模塊產生響應時,這些響應內容放入內存緩衝區,並被添加到一個緩衝區鏈。這個緩衝區鏈一樣適用於子請求處理。因爲根據模塊類型不一樣存在多個處理場景,因此nginx中的緩衝區鏈至關複雜。例如,在實現body filter模塊時,精確的管理緩衝區是很棘手的。這個模塊同一時間只能處理緩衝區鏈中的一個緩衝區,它必須決定是否覆蓋輸入緩衝區,是否用新分配的緩衝區替換這個緩衝區,或者在這個緩衝區以前或以後插入一個新緩衝區。更復雜的狀況,有時一個模塊收到的數據須要多個緩衝區存儲,所以它必須處理一個不完整的緩衝區鏈。可是因爲目前nginx僅提供了底層API用於操縱緩衝區鏈,因此開發者應該真正掌握nginx這一晦澀難懂的部分以後,再去開發第三方模塊。
上面提到的內容中須要注意的一點,內存緩衝區是爲鏈接的整個生命週期分配的,因此對於長鏈接須要消耗額外的內存。同時,對於空閒的keep alive鏈接,nginx僅消耗550字節內存。未來的nginx版本可能進行優化以使長鏈接重用和共用內存緩衝區。
內存分配管理的任務由nginx內存池分配器完成。共享內存區用於存放接受互斥鎖(accept mutex),緩存元數據,SSL會話緩存,以及和帶寬策略管理(限速)相關的信息。Nginx實現了slab分配器用於管理共享內存,提供了一系列鎖機制(互斥鎖和信號量),以容許安全地併發使用共享內存。爲了組織複雜的數據結構,nginx也提供了紅黑樹的實現。紅黑樹用於在內存中保存緩存元數據,查找非正則location定義,以及其餘一些任務。
不幸的是,上述內容從未以一致而且簡單的方式介紹過,以至開發第三方模塊的工做至關複雜。雖然有一些nginx內部實現的好文檔,例如,Evan Miller寫的,可是這些文檔須要作不少還原工做,nginx模塊的開發仍是像變魔術同樣。
雖然開發第三方模塊是如此困難,nginx社區最近仍是涌現大量有用的第三方模塊。例如,將Lua解釋器嵌入nginx,負載均衡附加模塊,完整的Web DAV支持,高級緩存控制,以及其餘本章做者所鼓勵和未來支持的有趣的第三方工做。
Igor Sysoev開始編寫nginx時,大部分構建互聯網的軟件都已經存在,這些軟件的架構通常遵循傳統服務器和網絡硬件、操做系統、以及過去互聯網架構的定義。可是這並未阻止Igor考慮在web服務器領域作進一步的工做。因此,顯然第一個優秀實踐是:總有提高空間。
帶着開發更好web軟件的想法,Igor花了不少時間開發原始代碼結構,並研究在多個操做系統下優化代碼的不一樣手段。十年後,考慮到1.0版本已經通過十年活躍開發,Igor開發了2.0版本原型。很明顯,這個新架構的初始原型和代碼結構,對於軟件的後續開發及其重要。
另外值得提到的一點是聚焦開發。Nginx 的windows版本是個好例子,說明不管在開發者的核心技能或應用目標上避免稀釋開發工做是值得的。一樣努力增強nginx重寫引擎對現存遺留配置的後向兼容能力,也是值得的。
最後特別值得提到的是,儘管nginx開發者社區並不大,nginx的第三方模塊和擴展仍是成爲nginx受歡迎的一個很重要的因素。Nginx用戶社區和做者們很感謝Evan Miller, Piotr Sikora, Valery Kholodkov, Zhang Yichun (agentzh)以及其餘優秀軟件工程師所作的工做。