nginx原理及優化

Nginx工作原理和優化

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

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

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

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

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

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

Nginx的模塊從功能上分爲如下三類。

Handlers(處理器模塊)。此類模塊直接處理請求,並進行輸出內容和修改headers信息等操作。Handlers處理器模塊一般只能有一個。

Filters (過濾器模塊)。此類模塊主要對其他處理器模塊輸出的內容進行修改操作,最後由Nginx輸出。

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

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

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

Nginx本身做的工作實際很少,當它接到一個HTTP請求時,它僅僅是通過查找配置文件將此次請求映射到一個location block,而此location中所配置的各個指令則會啓動不同的模塊去完成工作,因此模塊可以看做Nginx真正的勞動工作者。通常一個location中的指令會涉及一個handler模塊和多個filter模塊(當然,多個location可以複用同一個模塊)。handler模塊負責處理請求,完成響應內容的生成,而filter模塊對響應內容進行處理。

Nginx的模塊直接被編譯進Nginx,因此屬於靜態編譯方式。啓動Nginx後,Nginx的模塊被自動加載,不像Apache,首先將模塊編譯爲一個so文件,然後在配置文件中指定是否進行加載。在解析配置文件時,Nginx的每個模塊都有可能去處理某個請求,但是同一個處理請求只能由一個模塊來完成。

  1. 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的進程模型,可以由下圖來表示:

  1. Nginx+FastCGI運行原理

1、什麼是 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服務器專一地處理靜態請求或者將動態腳本服務器的結果返回給客戶端,這在很大程度上提高了整個應用系統的性能。

2、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)進行通信
3、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動態請求。

4、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中)的下一個連接。
  1. Nginx+PHP正確配置

一般web都做統一入口:把PHP請求都發送到同一個文件上,然後在此文件裏通過解析「REQUEST_URI」實現路由。

Nginx配置文件分爲好多塊,常見的從外到內依次是「http」、「server」、「location」等等,缺省的繼承關係是從外到內,也就是說內層塊會自動獲取外層塊的值作爲缺省值。

例如:

[plain] view plain copy print?
server {
listen 80;
server_name foo.com;
root /path;
location / {
index index.html index.htm index.php;
if (!-e KaTeX parse error: Expected 'EOF', got '}' at position 80: … } }̲ location… {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /path$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}
}

  1. 不應該在location 模塊定義index
    一旦未來需要加入新的「location」,必然會出現重複定義的「index」指令,這是因爲多個「location」是平級的關係,不存在繼承,此時應該在「server」裏定義「index」,藉助繼承關係,「index」指令在所有的「location」中都能生效。

  2. 使用try_files
    

接下來看看「if」指令,說它是大家誤解最深的Nginx指令毫不爲過:
if (!-e $request_filename) {

rewrite . /index.php last;

}

很多人喜歡用「if」指令做一系列的檢查,不過這實際上是「try_files」指令的職責:

try_files $uri $uri/ /index.php;

除此以外,初學者往往會認爲「if」指令是內核級的指令,但是實際上它是rewrite模塊的一部分,加上Nginx配置實際上是聲明式的,而非過程式的,所以當其和非rewrite模塊的指令混用時,結果可能會非你所願。

3)fastcgi_params」配置文件:

include fastcgi_params;

Nginx有兩份fastcgi配置文件,分別是「fastcgi_params」和「fastcgi.conf」,它們沒有太大的差異,唯一的區別是後者比前者多了一行「SCRIPT_FILENAME」的定義:

fastcgi_param SCRIPT_FILENAME documentrootdocument_rootfastcgi_script_name;

注意:$document_root 和 $fastcgi_script_name 之間沒有 /。

原本Nginx只有「fastcgi_params」,後來發現很多人在定義「SCRIPT_FILENAME」時使用了硬編碼的方式,於是爲了規範用法便引入了「fastcgi.conf」。

不過這樣的話就產生一個疑問:爲什麼一定要引入一個新的配置文件,而不是修改舊的配置文件?這是因爲「fastcgi_param」指令是數組型的,和普通指令相同的是:內層替換外層;和普通指令不同的是:當在同級多次使用的時候,是新增而不是替換。換句話說,如果在同級定義兩次「SCRIPT_FILENAME」,那麼它們都會被髮送到後端,這可能會導致一些潛在的問題,爲了避免此類情況,便引入了一個新的配置文件。

此外,我們還需要考慮一個安全問題:在PHP開啓「cgi.fix_pathinfo」的情況下,PHP可能會把錯誤的文件類型當作PHP文件來解析。如果Nginx和PHP安裝在同一臺服務器上的話,那麼最簡單的解決方法是用「try_files」指令做一次過濾:
try_files $uri =404;
依照前面的分析,給出一份改良後的版本,是不是比開始的版本清爽了很多:
[plain] view plain copy print?
server {
listen 80;
server_name foo.com;
root /path;
index index.html index.htm index.php;
location / {
try_files $uri KaTeX parse error: Expected 'EOF', got '}' at position 24: …dex.php; }̲ location… {
try_files $uri =404;
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
}
}
5. Nginx爲啥性能高-多進程IO模型
參考http://mp.weixin.qq.com/s?__biz=MjM5NTg2NTU0Ng==&mid=407889757&idx=3&sn=cfa8a70a5fd2a674a91076f67808273c&scene=23&srcid=0401aeJQEraSG6uvLj69Hfve#rd
1、nginx採用多進程模型好處
首先,對於每個worker進程來說,獨立的進程,不需要加鎖,所以省掉了鎖帶來的開銷,同時在編程以及問題查找時,也會方便很多。
其次,採用獨立的進程,可以讓互相之間不會影響,一個進程退出後,其它進程還在工作,服務不會中斷,master進程則很快啓動新的worker進程。當然,worker進程的異常退出,肯定是程序有bug了,異常退出,會導致當前worker上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。
1、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,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。好吧,你過一會,再來檢查一下事件,直到事件準備好了爲止,在這期間,你就可以先去做其它事情,然後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你可以做更多的事情了,但帶來的開銷也是不小的。
關於IO模型:http://blog.csdn.net/hguisu/article/details/7453390
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網卡驅動架構。

(epoll內容,參考epoll_互動百科)

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

代碼來總結一下nginx的事件處理模型:

[cpp] view plain copy print?
while (true) {
for t in run_tasks:
t.handler();
update_time(&now);
timeout = ETERNITY;
for t in wait_tasks: /* sorted already /
if (t.time <= now) {
t.timeout_handler();
} else {
timeout = t.time - now;
break;
}
nevents = poll_function(events, timeout);
for i in nevents:
task t;
if (events[i].type == READ) {
t.handler = read_handler;
} else { /
events[i].type == WRITE */
t.handler = write_handler;
}
run_tasks_add(t);
}
6. Nginx優化

1. 編譯安裝過程優化

1).減小Nginx編譯後的文件大小

在編譯Nginx時,默認以debug模式進行,而在debug模式下會插入很多跟蹤和ASSERT之類的信息,編譯完成後,一個Nginx要有好幾兆字節。而在編譯前取消Nginx的debug模式,編譯完成後Nginx只有幾百千字節。因此可以在編譯之前,修改相關源碼,取消debug模式。具體方法如下:
在Nginx源碼文件被解壓後,找到源碼目錄下的auto/cc/gcc文件,在其中找到如下幾行:

debug

CFLAGS=”$CFLAGS -g”
註釋掉或刪掉這兩行,即可取消debug模式。

2.爲特定的CPU指定CPU類型編譯優化
在編譯Nginx時,默認的GCC編譯參數是“-O”,要優化GCC編譯,可以使用以下兩個參數:
–with-cc-opt=’-O3’
–with-cpu-opt=CPU #爲特定的 CPU 編譯,有效的值包括:
pentium, pentiumpro, pentium3, # pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64
要確定CPU類型,可以通過如下命令:
cat /proc/cpuinfo | grep “model name”

  1. 利用TCMalloc優化Nginx的性能
    TCMalloc的全稱爲Thread-Caching Malloc,是谷歌開發的開源工具google-perftools中的一個成員。與標準的glibc庫的Malloc相比,TCMalloc庫在內存分配效率和速度上要高很多,這在很大程度上提高了服務器在高併發情況下的性能,從而降低了系統的負載。下面簡單介紹如何爲Nginx添加TCMalloc庫支持。
    要安裝TCMalloc庫,需要安裝libunwind(32位操作系統不需要安裝)和google-perftools兩個軟件包,libunwind庫爲基於64位CPU和操作系統的程序提供了基本函數調用鏈和函數調用寄存器功能。下面介紹利用TCMalloc優化Nginx的具體操作過程。
    1).安裝libunwind庫
    可以從http://download.savannah.gnu.org/releases/libunwind下載相應的libunwind版本,這裏下載的是libunwind-0.99-alpha.tar.gz。安裝過程如下:
    tar zxvf libunwind-0.99-alpha.tar.gz
    cd libunwind-0.99-alpha/
    CFLAGS=-fPIC ./configure
    make CFLAGS=-fPIC
    make CFLAGS=-fPIC install
    2).安裝google-perftools
    可以從http://google-perftools.googlecode.com下載相應的google-perftools版本,這裏下載的是google-perftools-1.8.tar.gz。安裝過程如下:
    #tar zxvf google-perftools-1.8.tar.gz
    cd google-perftools-1.8/
    ./configure
    make && make install
    echo “/usr/local/lib” > /etc/ld.so.conf.d/usr_local_lib.conf
    ldconfig
    至此,google-perftools安裝完成。
    3).重新編譯Nginx
    爲了使Nginx支持google-perftools,需要在安裝過程中添加“–with-google_perftools_module”選項重新編譯Nginx。安裝代碼如下:
    ./configure \

–with-google_perftools_module --with-http_stub_status_module --prefix=/opt/nginx
make && make install
到這裏Nginx安裝完成。
4).爲google-perftools添加線程目錄
創建一個線程目錄,這裏將文件放在/tmp/tcmalloc下。操作如下:
mkdir /tmp/tcmalloc
chmod 0777 /tmp/tcmalloc
5).修改Nginx主配置文件
修改nginx.conf文件,在pid這行的下面添加如下代碼:
#pid logs/nginx.pid;
google_perftools_profiles /tmp/tcmalloc;
接着,重啓Nginx即可完成google-perftools的加載。
6).驗證運行狀態
爲了驗證google-perftools已經正常加載,可通過如下命令查看:
lsof -n | grep tcmalloc
nginx 2395 nobody 9w REG 8,8 0 1599440 /tmp/tcmalloc.2395
nginx 2396 nobody 11w REG 8,8 0 1599443 /tmp/tcmalloc.2396
nginx 2397 nobody 13w REG 8,8 0 1599441 /tmp/tcmalloc.2397
nginx 2398 nobody 15w REG 8,8 0 1599442 /tmp/tcmalloc.2398
由於在Nginx配置文件中設置worker_processes的值爲4,因此開啓了4個Nginx線程,每個線程會有一行記錄。每個線程文件後面的數字值就是啓動的Nginx的pid值。
至此,利用TCMalloc優化Nginx的操作完成。
3.Nginx內核參數優化
內核參數的優化,主要是在Linux系統中針對Nginx應用而進行的系統內核參數優化。
下面給出一個優化實例以供參考。
net.ipv4.tcp_max_tw_buckets = 6000
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_syncookies = 1
net.core.somaxconn = 262144
net.core.netdev_max_backlog = 262144
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_keepalive_time = 30
將上面的內核參數值加入/etc/sysctl.conf文件中,然後執行如下命令使之生效:
/sbin/sysctl -p
下面對實例中選項的含義進行介紹:
net.ipv4.tcp_max_tw_buckets :選項用來設定timewait的數量,默認是180 000,這裏設爲6000。
net.ipv4.ip_local_port_range :選項用來設定允許系統打開的端口範圍。在高併發情況否則端口號會不夠用。
net.ipv4.tcp_tw_recycle :選項用於設置啓用timewait快速回收.
net.ipv4.tcp_tw_reuse :選項用於設置開啓重用,允許將TIME-WAIT sockets重新用於新的TCP連接。
net.ipv4.tcp_syncookies :選項用於設置開啓SYN Cookies,當出現SYN等待隊列溢出時,啓用cookies進行處理。
net.core.somaxconn :選項的默認值是128, 這個參數用於調節系統同時發起的tcp連接數,在高併發的請求中,默認的值可能會導致鏈接超時或者重傳,因此,需要結合併發請求數來調節此值。
net.core.netdev_max_backlog :選項表示當每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許發送到隊列的數據包的最大數目。
net.ipv4.tcp_max_orphans :選項用於設定系統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。如果超過這個數字,孤立連接將立即被複位並打印出警告信息。這個限制只是爲了防止簡單的DoS攻擊。不能過分依靠這個限制甚至人爲減小這個值,更多的情況下應該增加這個值。
net.ipv4.tcp_max_syn_backlog :選項用於記錄那些尚未收到客戶端確認信息的連接請求的最大值。對於有128MB內存的系統而言,此參數的默認值是1024,對小內存的系統則是128。
net.ipv4.tcp_synack_retries 參數的值決定了內核放棄連接之前發送SYN+ACK包的數量。
net.ipv4.tcp_syn_retries 選項表示在內核放棄建立連接之前發送SYN包的數量。
net.ipv4.tcp_fin_timeout 選項決定了套接字保持在FIN-WAIT-2狀態的時間。默認值是60秒。正確設置這個值非常重要,有時即使一個負載很小的Web服務器,也會出現大量的死套接字而產生內存溢出的風險。
net.ipv4.tcp_syn_retries 選項表示在內核放棄建立連接之前發送SYN包的數量。
如果發送端要求關閉套接字,net.ipv4.tcp_fin_timeout選項決定了套接字保持在FIN-WAIT-2狀態的時間。接收端可以出錯並永遠不關閉連接,甚至意外宕機。

net.ipv4.tcp_fin_timeout 的默認值是60秒。需要注意的是,即使一個負載很小的Web服務器,也會出現因爲大量的死套接字而產生內存溢出的風險。FIN-WAIT-2的危險性比FIN-WAIT-1要小,因爲它最多隻能消耗1.5KB的內存,但是其生存期長些。

net.ipv4.tcp_keepalive_time 選項表示當keepalive啓用的時候,TCP發送keepalive消息的頻度。默認值是2(單位是小時)。
4. PHP-FPM的優化
如果您高負載網站使用PHP-FPM管理FastCGI,這些技巧也許對您有用:
1)增加FastCGI進程數
把PHP FastCGI子進程數調到100或以上,在4G內存的服務器上200就可以建議通過壓力測試獲取最佳值。
2)增加 PHP-FPM打開文件描述符的限制
標籤rlimit_files用於設置PHP-FPM對打開文件描述符的限制,默認值爲1024。這個標籤的值必須和Linux內核打開文件數關聯起來,例如,要將此值設置爲65 535,就必須在Linux命令行執行“ulimit -HSn 65536”。

   然後 增加 PHP-FPM打開文件描述符的限制:
 # vi /path/to/php-fpm.conf
找到“<valuename="rlimit_files">1024</value>”

把1024更改爲 4096或者更高.
重啓 PHP-FPM.
ulimit -n 要調整爲65536甚至更大。如何調這個參數,可以參考網上的一些文章。命令行下執行 ulimit -n 65536即可修改。如果不能修改,需要設置 /etc/security/limits.conf,加入

  • hard nofile65536
  • soft nofile 65536
    3)適當增加max_requests
    標籤max_requests指明瞭每個children最多處理多少個請求後便會被關閉,默認的設置是500。
    500
    4. nginx.conf的參數優化
    nginx要開啓的進程數 一般等於cpu的總核數 其實一般情況下開4個或8個就可以。
    每個nginx進程消耗的內存10兆的模樣
    worker_cpu_affinity
    僅適用於linux,使用該選項可以綁定worker進程和CPU(2.4內核的機器用不了)
    假如是8 cpu 分配如下:
    worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000
    00100000 01000000 10000000
    nginx可以使用多個worker進程,原因如下:
    to use SMP
    to decrease latency when workers blockend on disk I/O
    to limit number of connections per process when select()/poll() is
    used The worker_processes and worker_connections from the event sections
    allows you to calculate maxclients value: k max_clients = worker_processes * worker_connections
    worker_rlimit_nofile 102400;
    每個nginx進程打開文件描述符最大數目 配置要和系統的單進程打開文件數一致,linux 2.6內核下開啓文件打開數爲65535,worker_rlimit_nofile就相應應該填寫65535 nginx調度時分配請求到進程並不是那麼的均衡,假如超過會返回502錯誤。我這裏寫的大一點
    use epoll
    Nginx使用了最新的epoll(Linux 2.6內核)和kqueue(freebsd)網絡I/O模型,而Apache則使用的是傳統的select模型。
    處理大量的連接的讀寫,Apache所採用的select網絡I/O模型非常低效。在高併發服務器中,輪詢I/O是最耗時間的操作 目前Linux下能夠承受高併發
    訪問的Squid、Memcached都採用的是epoll網絡I/O模型。
    worker_connections 65535;
    每個工作進程允許最大的同時連接數 (Maxclient = work_processes * worker_connections)
    keepalive_timeout 75
    keepalive超時時間
    這裏需要注意官方的一句話:
    The parameters can differ from each other. Line Keep-Alive:
    timeout=time understands Mozilla and Konqueror. MSIE itself shuts
    keep-alive connection approximately after 60 seconds.

client_header_buffer_size 16k
large_client_header_buffers 4 32k
客戶請求頭緩衝大小
nginx默認會用client_header_buffer_size這個buffer來讀取header值,如果header過大,它會使用large_client_header_buffers來讀取

如果設置過小HTTP頭/Cookie過大 會報400 錯誤 nginx 400 bad request
求行如果超過buffer,就會報HTTP 414錯誤(URI Too Long) nginx接受最長的HTTP頭部大小必須比其中一個buffer大,否則就會報400的HTTP錯誤(Bad Request)。
open_file_cache max 102400

使用字段:http, server, location 這個指令指定緩存是否啓用,如果啓用,將記錄文件以下信息: ·打開的文件描述符,大小信息和修改時間. ·存在的目錄信息. ·在搜索文件過程中的錯誤信息 – 沒有這個文件,無法正確讀取,參考open_file_cache_errors 指令選項:
·max - 指定緩存的最大數目,如果緩存溢出,最長使用過的文件(LRU)將被移除
例: open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;

open_file_cache_errors
語法:open_file_cache_errors on | off 默認值:open_file_cache_errors off 使用字段:http, server, location 這個指令指定是否在搜索一個文件是記錄cache錯誤.
open_file_cache_min_uses

語法:open_file_cache_min_uses number 默認值:open_file_cache_min_uses 1 使用字段:http, server, location 這個指令指定了在open_file_cache指令無效的參數中一定的時間範圍內可以使用的最小文件數,如 果使用更大的值,文件描述符在cache中總是打開狀態.
open_file_cache_valid

語法:open_file_cache_valid time 默認值:open_file_cache_valid 60 使用字段:http, server, location 這個指令指定了何時需要檢查open_file_cache中緩存項目的有效信息.
開啓gzip
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-JavaScript text/css

application/xml;
gzip_vary on;
緩存靜態文件:

location ~* ^.+.(swf|gif|png|jpg|js|css)$ {
root /usr/local/ku6/ktv/show.ku6.com/;
expires 1m;
}

  1. 錯誤排查
    1、Nginx 502 Bad Gateway
    php-cgi進程數不夠用、php執行時間長(mysql慢)、或者是php-cgi進程死掉,都會出現502錯誤
    一般來說Nginx 502 Bad Gateway和php-fpm.conf的設置有關,而Nginx 504 Gateway Time-out則是與nginx.conf的設置有關
    1)、查看當前的PHP FastCGI進程數是否夠用:
    netstat -anpo | grep “php-cgi” | wc -l
      如果實際使用的“FastCGI進程數”接近預設的“FastCGI進程數”,那麼,說明“FastCGI進程數”不夠用,需要增大。
    2)、部分PHP程序的執行時間超過了Nginx的等待時間,可以適當增加
    nginx.conf配置文件中FastCGI的timeout時間,例如:
    http {

    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;

    }
    2、413 Request Entity Too Large
    解決:增大client_max_body_size
    client_max_body_size:指令指定允許客戶端連接的最大請求實體大小,它出現在請求頭部的Content-Length字段. 如果請求大於指定的值,客戶端將收到一個"Request Entity Too Large" (413)錯誤. 記住,瀏覽器並不知道怎樣顯示這個錯誤.
    php.ini中增大
    post_max_size 和upload_max_filesize
    3 Ngnix error.log出現:upstream sent too big header while reading response header from upstream錯誤
    1)如果是nginx反向代理
    proxy是nginx作爲client轉發時使用的,如果header過大,超出了默認的1k,就會引發上述的upstream sent too big header (說白了就是nginx把外部請求給後端server,後端server返回的header 太大nginx處理不過來就導致了。
    server {
    listen 80;
    server_name *.xywy.com ;
    large_client_header_buffers 4 16k;
    location / {
    #添加這3行
    proxy_buffer_size 64k;
    proxy_buffers 32 32k;
    proxy_busy_buffers_size 128k;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章