Nginx 工作原理和優化、漏洞

真實的歸宿發表於2016-07-17

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請求和響應的過程。

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:

      Nginx配置執行php: 編輯nginx.conf加入如下語句:
    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」等等,預設的繼承關係是從外到內,也就是說內層塊會自動獲取外層塊的值作為預設值。

例如:

1)  不應該在location 模組定義index

一旦未來需要加入新的「location」,必然會出現重複定義的「index」指令,這是因為多個「location」是平級的關係,不存在繼承,此時應該在「server」裡定義「index」,藉助繼承關係,「index」指令在所有的「location」中都能生效。
2)     使用try_files
接下來看看「if」指令,說它是大家誤解最深的Nginx指令毫不為過:

很多人喜歡用「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;

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

5.   Nginx為啥效能高-多程式IO模型

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的事件處理模型:

6.   Nginx優化

1. 編譯安裝過程優化

1).減小Nginx編譯後的檔案大小

在編譯Nginx時,預設以debug模式進行,而在debug模式下會插入很多跟蹤和ASSERT之類的資訊,編譯完成後,一個Nginx要有好幾兆位元組。而在編譯前取消Nginx的debug模式,編譯完成後Nginx只有幾百千位元組。因此可以在編譯之前,修改相關原始碼,取消debug模式。具體方法如下:

在Nginx原始碼檔案被解壓後,找到原始碼目錄下的auto/cc/gcc檔案,在其中找到如下幾行:

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

2.為特定的CPU指定CPU型別編譯優化

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

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

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

2).安裝google-perftools

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

至此,google-perftools安裝完成。

3).重新編譯Nginx

為了使Nginx支援google-perftools,需要在安裝過程中新增“–with-google_perftools_module”選項重新編譯Nginx。安裝程式碼如下:

到這裡Nginx安裝完成。

4).為google-perftools新增執行緒目錄

建立一個執行緒目錄,這裡將檔案放在/tmp/tcmalloc下。操作如下:

5).修改Nginx主配置檔案

修改nginx.conf檔案,在pid這行的下面新增如下程式碼:

接著,重啟Nginx即可完成google-perftools的載入。

6).驗證執行狀態

為了驗證google-perftools已經正常載入,可通過如下命令檢視:

由於在Nginx配置檔案中設定worker_processes的值為4,因此開啟了4個Nginx執行緒,每個執行緒會有一行記錄。每個執行緒檔案後面的數字值就是啟動的Nginx的pid值。

至此,利用TCMalloc優化Nginx的操作完成。

3.Nginx核心引數優化

核心引數的優化,主要是在Linux系統中針對Nginx應用而進行的系統核心引數優化。

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

將上面的核心引數值加入/etc/sysctl.conf檔案中,然後執行如下命令使之生效:

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

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

3. 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
找到“1024”
把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;

快取靜態檔案:

7.   錯誤排查

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時間,例如:

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處理不過來就導致了。

2) 如果是 nginx+PHPcgi

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

多加:

8.   Nginx的php漏洞

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

漏洞分析:nginx預設以cgi的方式支援php的執行,譬如在配置檔案當中可以以

的方式支援對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://www.80sec.com/robots.txt/80sec.php

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

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

解決方案:

我們已經嘗試聯絡官方,但是此前你可以通過以下的方式來減少損失

關閉cgi.fix_pathinfo為0

或者

相關文章