Nginx工作原理以及常見錯誤分析

 

 

      NGINX 以高性能的負載均衡器,緩存,和 web 服務器聞名,驅動了全球超過 40% 最繁忙的網站。在大多數場景下,默認的 NGINX 和 Linux 設置可以很好的工作,但要達到最佳性能,有些時候必須做些調整。首先我們先了解其工作原理。

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 的每個模塊都有可能去處理某個請求,但是同一個處理請求只能由一個模塊來完成。 

 

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

 

 

3.  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 需要完成的工作:

  1. 通過調用 fastcgi(庫)的函數通過 socket 和 ningx 通信(讀寫 socket 是 fastcgi 內部實現的功能,對 wrapper 是非透明的)
  2. 調度 thread,進行 fork 和 kill
  3. 和 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 中)的下一個連接。

4.   Nginx+PHP 正確配置

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

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

例如:

server {
    listen 80;
    server_name foo.com;
    root /path;
    location / {
        index index.html index.htm index.php;
        if (!-e $request_filename) {
            rewrite . /index.php last;
        }
    }
    location ~ \.php$ {
        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 $document_root$fastcgi_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;

依照前面的分析,給出一份改良後的版本,是不是比開始的版本清爽了很多:

server {
    listen 80;
    server_name foo.com;
    root /path;
    index index.html index.htm index.php;
    location / {
        try_files $uri $uri/ /index.php;
    }
    location ~ \.php$ {
       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 的事件處理模型:

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);
}
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 文件,在其中找到如下幾行:


 
  1. # debug  
  2. CFLAGS=”$CFLAGS -g” 

註釋掉或刪掉這兩行,即可取消 debug 模式。

2. 爲特定的 CPU 指定 CPU 類型編譯優化

在編譯 Nginx 時,默認的 GCC 編譯參數是 “-O”,要優化 GCC 編譯,可以使用以下兩個參數:


 
  1. --with-cc-opt='-O3' 
  2. --with-cpu-opt=CPU  # 爲特定的 CPU 編譯,有效的值包括:
    pentium, pentiumpro, pentium3, # pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64 

要確定 CPU 類型,可以通過如下命令:


 
  1. [root@localhost home]#cat /proc/cpuinfo | grep "model name" 

2. 利用 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。安裝過程如下:


 
  1. [root@localhost home]#tar zxvf libunwind-0.99-alpha.tar.gz  
  2. [root@localhost home]# cd libunwind-0.99-alpha/  
  3. [root@localhost libunwind-0.99-alpha]#CFLAGS=-fPIC ./configure  
  4. [root@localhost libunwind-0.99-alpha]#make CFLAGS=-fPIC  
  5. [root@localhost libunwind-0.99-alpha]#make CFLAGS=-fPIC install 

2). 安裝 google-perftools

可以從 http://google-perftools.googlecode.com 下載相應的 google-perftools 版本,這裏下載的是 google-perftools-1.8.tar.gz。安裝過程如下:


 
  1. [root@localhost home]#tar zxvf google-perftools-1.8.tar.gz  
  2. [root@localhost home]#cd google-perftools-1.8/  
  3. [root@localhost google-perftools-1.8]# ./configure  
  4. [root@localhost google-perftools-1.8]#make && make install  
  5. [root@localhost google-perftools-1.8]#echo "/usr/
    local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf  
  6. [root@localhost google-perftools-1.8]# ldconfig 

至此,google-perftools 安裝完成。

3). 重新編譯 Nginx

爲了使 Nginx 支持 google-perftools,需要在安裝過程中添加 “–with-google_perftools_module” 選項重新編譯 Nginx。安裝代碼如下:


 
  1. [[email protected]]#./configure \  
  2. >--with-google_perftools_module --with-http_stub_status_module  --prefix=/opt/nginx  
  3. [root@localhost nginx-0.7.65]#make  
  4. [root@localhost nginx-0.7.65]#make install 

到這裏 Nginx 安裝完成。

4). 爲 google-perftools 添加線程目錄

創建一個線程目錄,這裏將文件放在 / tmp/tcmalloc 下。操作如下:


 
  1. [root@localhost home]#mkdir /tmp/tcmalloc  
  2. [root@localhost home]#chmod 0777 /tmp/tcmalloc 

5). 修改 Nginx 主配置文件

修改 nginx.conf 文件,在 pid 這行的下面添加如下代碼:


 
  1. #pid        logs/nginx.pid;  
  2. google_perftools_profiles /tmp/tcmalloc; 

接着,重啓 Nginx 即可完成 google-perftools 的加載。

6). 驗證運行狀態

爲了驗證 google-perftools 已經正常加載,可通過如下命令查看:


 
  1. [root@ localhost home]# lsof -n | grep tcmalloc  
  2. nginx      2395 nobody   9w  REG    8,8       0    1599440 /tmp/tcmalloc.2395  
  3. nginx      2396 nobody   11w REG   8,8       0    1599443 /tmp/tcmalloc.2396  
  4. nginx      2397 nobody   13w REG  8,8        0    1599441  /tmp/tcmalloc.2397  
  5. 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 應用而進行的系統內核參數優化。

下面給出一個優化實例以供參考。

  1. net.ipv4.tcp_max_tw_buckets = 6000 
  2. net.ipv4.ip_local_port_range = 1024 65000  
  3. net.ipv4.tcp_tw_recycle = 1 
  4. net.ipv4.tcp_tw_reuse = 1 
  5. net.ipv4.tcp_syncookies = 1 
  6. net.core.somaxconn = 262144 
  7. net.core.netdev_max_backlog = 262144 
  8. net.ipv4.tcp_max_orphans = 262144 
  9. net.ipv4.tcp_max_syn_backlog = 262144 
  10. net.ipv4.tcp_synack_retries = 1 
  11. net.ipv4.tcp_syn_retries = 1 
  12. net.ipv4.tcp_fin_timeout = 1 
  13. net.ipv4.tcp_keepalive_time = 30 

將上面的內核參數值加入 / etc/sysctl.conf 文件中,然後執行如下命令使之生效:

  1. [root@ localhost home]#/sbin/sysctl -p 

下面對實例中選項的含義進行介紹:

TCP 參數設置:

net.ipv4.tcp_max_tw_buckets :選項用來設定 timewait 的數量,默認是 180 000,這裏設爲 6000。

net.ipv4.ip_local_port_range: 選項用來設定允許系統打開的端口範圍。在高併發情況否則端口號會不夠用。當 NGINX 充當代理時,每個到上游服務器的連接都使用一個短暫或臨時端口。

net.ipv4.tcp_tw_recycle: 選項用於設置啓用 timewait 快速回收.

net.ipv4.tcp_tw_reuse: 選項用於設置開啓重用,允許將 TIME-WAIT sockets 重新用於新的 TCP 連接。

net.ipv4.tcp_syncookies: 選項用於設置開啓 SYN Cookies,當出現 SYN 等待隊列溢出時,啓用 cookies 進行處理。

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(單位是小時)。

緩衝區隊列:

net.core.somaxconn: 選項的默認值是 128, 這個參數用於調節系統同時發起的 tcp 連接數,在高併發的請求中,默認的值可能會導致鏈接超時或者重傳,因此,需要結合併發請求數來調節此值。

 由 NGINX 可接受的數目決定。默認值通常很低,但可以接受,因爲 NGINX 接收連接非常快,但如果網站流量大時,就應該增加這個值。內核日誌中的錯誤消息會提醒這個值太小了,把值改大,直到錯誤提示消失。
注意: 如果設置這個值大於 512,相應地也要改變 NGINX listen 指令的 backlog 參數。

net.core.netdev_max_backlog: 選項表示當每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許發送到隊列的數據包的最大數目。

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。

    <value name="max_requests"> 500 </value>

   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_processes 

    NGINX 工作進程數(默認值是 1)。在大多數情況下,一個 CPU 內核運行一個工作進程最好,建議將這個指令設置成自動就可以。有時可能想增大這個值,比如當工作進程需要做大量的磁盤 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;
}

響應緩衝區:

比如我們 Nginx+Tomcat 代理訪問 JS 無法完全加載,這幾個參數影響:

proxy_buffer_size 128k;
proxy_buffers   32 128k;
proxy_busy_buffers_size 128k;

Nginx 在代理了相應服務後或根據我們配置的 UpStream 和 location 來獲取相應的文件,首先文件會被解析到 nginx 的內存或者臨時文件目錄中,然後由 nginx 再來響應。那麼當 proxy_buffers 和 proxy_buffer_size 以及 proxy_busy_buffers_size 都太小時,會將內容根據 nginx 的配置生成到臨時文件中,但是臨時文件的大小也有默認值。所以當這四個值都過小時就會導致部分文件只加載一部分。所以要根據我們的服務器情況適當的調整 proxy_buffers 和 proxy_buffer_size 以及 proxy_busy_buffers_size、proxy_temp_file_write_size。具體幾個參數的詳細如下:

proxy_buffers   32 128k;  設置了 32 個緩存區,每個的大小是 128k

proxy_buffer_size 128k; 每個緩存區的大小是 128k,當兩個值不一致時沒有找到具體哪個有效,建議和上面的設置一致。

proxy_busy_buffers_size 128k; 設置使用中的緩存區的大小,控制傳輸至客戶端的緩存的最大

proxy_temp_file_write_size 緩存文件的大小

  5. 訪問日誌
    記錄每個請求會消耗 CPU 和 I/O 週期,一種降低這種影響的方式是緩衝訪問日誌。使用緩衝,而不是每條日誌記錄都單獨執行寫操作,NGINX 會緩衝一連串的日誌記錄,使用單個操作把它們一起寫到文件中。
    要啓用訪問日誌的緩存,就涉及到在 access_log 指令中 buffer=size 這個參數。當緩衝區達到 size 值時,NGINX 會把緩衝區的內容寫到日誌中。讓 NGINX 在指定的一段時間後寫緩存,就包含 flush=time 參數。當兩個參數都設置了,當下個日誌條目超出緩衝區值或者緩衝區中日誌條目存留時間超過設定的時間值,NGINX 都會將條目寫入日誌文件。當工作進程重新打開它的日誌文件或退出時,也會記錄下來。要完全禁用訪問日誌記錄的功能,將 access_log 指令設置成 off 參數。

   6. 限流
你可以設置多個限制,防止用戶消耗太多的資源,避免影響系統性能和用戶體驗及安全。 以下是相關的指令:
limit_conn and limit_conn_zone:NGINX 接受客戶連接的數量限制,例如單個 IP 地址的連接。設置這些指令可以防止單個用戶打開太多的連接,消耗超出自己的資源。
limit_rate:傳輸到客戶端響應速度的限制(每個打開多個連接的客戶消耗更多的帶寬)。設置這個限制防止系統過載,確保所有客戶端更均勻的服務質量。
limit_req and limit_req_zone:NGINX 處理請求的速度限制,與 limit_rate 有相同的功能。可以提高安全性,尤其是對登錄頁面,通過對用戶限制請求速率設置一個合理的值,避免太慢的程序覆蓋你的應用請求(比如 DDoS 攻擊)。
max_conns:上游配置塊中服務器指令參數。在上游服務器組中單個服務器可接受最大併發數量。使用這個限制防止上游服務器過載。設置值爲 0(默認值)表示沒有限制。
queue (NGINX Plus) :創建一個隊列,用來存放在上游服務器中超出他們最大 max_cons 限制數量的請求。這個指令可以設置隊列請求的最大值,還可以選擇設置在錯誤返回之前最大等待時間(默認值是 60 秒)。如果忽略這個指令,請求不會放入隊列。

7.   錯誤排查

1、Nginx 502 Bad Gateway:5 作爲網關或者代理工作的服務器嘗試執行請求時,從上游服務器接收到無效的響應。

常見原因:
1、後端服務掛了的情況, 直接 502 (nginx error 日誌:connect() failed (111: Connection refused) )
2、後端服務在重啓
實例:將後端服務關掉,然後向 nginx 發送請求後端接口,nginx 日誌可以看到 502 錯誤。

如果 nginx+php 出現 502, 錯誤分析:

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、504 Gateway Timeout :

nginx 作爲網關或者代理工作的服務器嘗試執行請求時,未能及時從上游服務器(URI 標識出的服務器,例如 HTTP、FTP、LDAP)收到響應。

常見原因:
該接口太耗時,後端服務接收到請求,開始執行,未能在設定時間返回數據給 nginx
後端服務器整體負載太高,接受到請求之後,由於線程繁忙,未能安排給請求的接口,導致未能在設定時間返回數據給 nginx

 

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;

    }

}         

2) 如果是 nginx+PHPcgi 

  錯誤帶有 upstream: "fastcgi://127.0.0.1:9000"。就該 

  多加:

  fastcgi_buffer_size 128k;
  fastcgi_buffers 4 128k;

server {
        listen       80;
        server_name  ddd.com;
        index index.html index.htm index.php;
   
        client_header_buffer_size 128k;
        large_client_header_buffers 4 128k;
        proxy_buffer_size 64k;
        proxy_buffers 8 64k;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 128k;

        location / {

          ......

        }

}         

8.   Nginx 的 php 漏洞

漏洞介紹:nginx 是一款高性能的 web 服務器,使用非常廣泛,其不僅經常被用作反向代理,也可以非常好的支持 PHP 的運行。80sec 發現其中存在一個較爲嚴重的安全問題,默認情況下可能導致服務器錯誤的將任何類型的文件以 PHP 的方式進行解析,這將導致嚴重的安全問題,使得惡意的攻擊者可能攻陷支持 php 的 nginx 服務器。


漏洞分析:nginx 默認以 cgi 的方式支持 php 的運行,譬如在配置文件當中可以以


location ~ .php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
}

的方式支持對 php 的解析,location 對請求進行選擇的時候會使用 URI 環境變量進行選擇,其中傳遞到後端 Fastcgi 的關鍵變量 SCRIPT_FILENAME 由 nginx 生成的 $fastcgi_script_name 決定,而通過分析可以看到 $fastcgi_script_name 是直接由 URI 環境變量控制的,這裏就是產生問題的點。而爲了較好的支持 PATH_INFO 的提取,在 PHP 的配置選項裏存在 cgi.fix_pathinfo 選項,其目的是爲了從 SCRIPT_FILENAME 裏取出真正的腳本名。
那麼假設存在一個 http://www.80sec.com/80sec.jpg,我們以如下的方式去訪問

http://www.80sec.com/80sec.jpg/80sec.php


將會得到一個 URI
/80sec.jpg/80sec.php
經過 location 指令,該請求將會交給後端的 fastcgi 處理,nginx 爲其設置環境變量 SCRIPT_FILENAME,內容爲
/scripts/80sec.jpg/80sec.php
而在其他的 webserver 如 lighttpd 當中,我們發現其中的 SCRIPT_FILENAME 被正確的設置爲
/scripts/80sec.jpg
所以不存在此問題。
後端的 fastcgi 在接受到該選項時,會根據 fix_pathinfo 配置決定是否對 SCRIPT_FILENAME 進行額外的處理,一般情況下如果不對 fix_pathinfo 進行設置將影響使用 PATH_INFO 進行路由選擇的應用,所以該選項一般配置開啓。Php 通過該選項之後將查找其中真正的腳本文件名字,查找的方式也是查看文件是否存在,這個時候將分離出 SCRIPT_FILENAME 和 PATH_INFO 分別爲
/scripts/80sec.jpg和80sec.php
最後,以 / scripts/80sec.jpg 作爲此次請求需要執行的腳本,攻擊者就可以實現讓 nginx 以 php 來解析任何類型的文件了。

POC: 訪問一個 nginx 來支持 php 的站點,在一個任何資源的文件如 robots.txt 後面加上 / 80sec.php,這個時候你可以看到如下的區別:

訪問 http://www.80sec.com/robots.txt
HTTP/1.1 200 OK
Server: nginx/0.6.32
Date: Thu, 20 May 2010 10:05:30 GMT
Content-Type: text/plain
Content-Length: 18
Last-Modified: Thu, 20 May 2010 06:26:34 GMT
Connection: keep-alive
Keep-Alive: timeout=20
Accept-Ranges: bytes

訪問訪問 http://www.80sec.com/robots.txt/80sec.php
HTTP/1.1 200 OK
Server: nginx/0.6.32
Date: Thu, 20 May 2010 10:06:49 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
X-Powered-By: PHP/5.2.6

其中的 Content-Type 的變化說明了後端負責解析的變化,該站點就可能存在漏洞。

漏洞廠商:http://www.nginx.org

解決方案:

我們已經嘗試聯繫官方,但是此前你可以通過以下的方式來減少損失
關閉cgi.fix_pathinfo爲0
或者
if ( $fastcgi_script_name ~ ..*/.*php ) {
return 403;
}

 

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