FPs

高流量負載下Nginx 調優

  • 原文地址: Martin Fjordvald
  • 已獲得原文作者翻譯許可,本文遵循原文許可協議
  • 原文發表時間:April 27,2011

我曾經討論過一些Nginx 最常見的問題,毫無疑問,其中一個問題就是關於如何優化nginx 獲得高性能。不必驚訝,因爲大多數nginx 新用戶都是從Apache 遷移過來,所以他們習慣於調整配置和使用巫術來盡可能讓服務器達到最佳性能。

恩,我得告訴你一些壞消息,你並不能真的顯著優化nginx。沒有什麼配置能讓你的負載降低一半,或讓PHP 運行速度快2 倍。謝天謝地,好消息是nginx 不需要任何調優,因爲直接使用它時就已經被優化過了。最大的優化發生在當你決定運行通過ape-get install ,yum install 或 make install 安裝的nginx。(請注意這些倉庫內容常常過期。Wiki 的安裝頁面上通常有最新的倉庫。)

也就是說,有很多選項會影響nginx 的行爲,但是這些選項的默認值並不全都爲高流量的情況優化過。另外我們也需要考慮nginx 運行的平臺,優化我們的操作系統,因爲它們某些地方也會有瓶頸。

總之,我們沒法優化單個連接的加載時間,但是可以確保nginx 處理高流量的情況時有優化過的理想的環境。當然,我所說的高流量是指每秒幾百個請求,絕大多數人不需要爲這種情況費心思,不過如果你有興趣或者準備處理這種情況,請繼續往下讀吧。

首先我們需要考慮要使用的平臺,因爲nginx 可以運行在Linux,MacOS,FreeBSD,Solaris,Windows 以及其他專業的系統。他們都實現了高性能的基於事件的輪詢方法,可惜的是,nginx 只支持了其中4個。我傾向於喜歡4個中的FreeBSD,但是沒什麼大的區別,相對於選擇最優的系統,選擇你最熟悉的系統更加重要。

如果你沒有猜到,那個不同的系統就是Windows。真的沒有任何理由把運行在Windows 上的Nginx 投入生產環境使用。Windows 採用一個不同的處理事件輪詢的方法,nginx 的作者選擇不支持它。它默認採用低性能的select(),結果性能非常糟糕。

大多數人遇到的第二個最大的瓶頸也與你的操作系統有關。打開一個shell,su 到運行nginx 的用戶,然後運行這個命令:ulimit -a。這些數值都是nginx 運行時的瓶頸。大多數系統下的打開文件數默認被限制得很小,我現在檢查的這個系統它的值設置爲1024。如果nginx運行在這種情況下,當它達到限制的時會記錄錯誤日誌(24: Too many open files)和返回一個錯誤給客戶端。nginx 當然可以處理大大超過1024 個文件描述符,你的操作系統同樣也可以。所以你可以放心的提高這個值。

爲此,你可以通過ulimit 設置這個限制,也可以使用worker rlimit nofile 來定義你需要的打開文件描述符的限制。(這需要以root 啓動nginx,在它dropping privileges 之前)(譯註:nginx 以root 啓動之後,會再啓動多個worker 進程,worker 進程工作在你配置中user 指定的普通用戶,實現參考nginx_src/os/unix/ngx_process_cycle.c

Nginx 瓶頸

關注了OS 的瓶頸之後,是時候深入nginx 自身,看看一些我們可以調整的指標和方法。

Worker 進程

worker 進程 是nginx 的基礎,一旦master 進程綁定了指定的IP/端口,它就會根據複製出指定用戶的worker 進程,然後它們會處理所有任務。Worker 不是多線程的,所以它們不能在CPU之間分攤每個連接。所以這讓運行多個worker 進程有意義,通常一個CPU 核心配一個worker。2-4 個worker 可以應付大多數負載情況,因爲在CPU 成爲問題之前,nginx 會遇到其他瓶頸,通常情況下你的進程只會處於空閒狀態。如果你的nginx 實例在設置4 個worker 之後是CPU Bound(譯註:只佔用大量CPU資源,相對應的有I/O Bound),希望不需要我告訴你需要怎麼做。(譯註:作者意指升級CPU 配置即可。)

當你在處理有很多阻塞的磁盤IO 的情況時,可以創建更多worker 進程。你需要測試特定的配置下加載靜態文件的等待時間,如果這個時間很大的話,嘗試增加worker 進程。

Worker 連接數

Worker 連接數能有效地限制任一worker 在某一刻可以處理的連接數。這個選項極有可能是設計用來防止失控的進程,萬一你的系統配置成允許處理超出硬件能力的負載。正如nginx 開發者Valentine 在郵件列表中指出,如果達到worker_connections 的限制,nginx 可以關閉keep-alive 連接,所以在這裏我們不需要擔心keep-alive 值。相反,我們應該關心nginx 可以處理的當前活躍連接的總數。我們可以處理的最大連接數公式如下:

worker_processes * worker_connections * (K / average $request_time)

其中K 是當前活躍的連接總數。另外,對於K值,我們也需要考慮反向代理,它會多打開一個到你後端服務的連接。

在默認的配置文件中worker_connections 是設置爲1024,如果我們考慮瀏覽器通常會打開2個連接來進行站點資源的管道傳輸,那麼導致我們最多能同時處理512個用戶。使用反向代理的情況下,同時能處理的用戶數會更低,即使你的後端服務能夠儘可能快的響應來釋放連接。

所有關於worker 連接數的事情應該都搞清楚了,如果你的流量在增長,你最終都需要增加每個worker 的總連接數。2048 對於大多數人來說都適用了,說實話,如果你有這種量級的流量,你應該已經很清楚的知道這個指標得設多高。

CPU 親緣性

設置CPU 親緣性實際上是告訴每個worker 使用哪個CPU 核心,並且他們只會使用那個核心。我不想說太多涉及這部分的內容,除了說明你做這個時候必須非常小心。你操作系統的CPU調度器極有可能比你更擅長處理負載均衡。如果你認爲你在CPU 負載方面遇到了問題,想在調度器級別做優化,有可能找到一個可替代的調度器。除非你清晰的知道你在做什麼,不然別碰這個。

Keep Alive

Keep alive 是HTTP 的一個特性,它允許用戶客戶端和你的服務器保持連接來發送若干請求,直到達到設定的超時時間。實際上這個指標不會大大改善我們的nginx,因爲nginx 自身也能處理大量的空閒連接。nginx 的作者聲稱nginx 處理10,000 個空閒連接只會使用2.5MB內存,在我看來確實如此。

我在性能指南中設計這點的理由非常簡單。Keep alive 能大大改善終端用戶感受到的加載時間。這是你能優化的最重要的指標,因爲如果你的網站對用戶來說加載很快,他們會很開心。Amazon 和其他大型在線零售商研究表明,感官上的加載時間和銷量存在直接的關係。

這可能有一點奇怪,爲什麼保持持久的連接影響這麼大,即你要避免創建所有HTTP 連接,這樣影響很大。你可能不需要keep alived 超時時間爲65,但是還是很推薦設置爲10~20 ,正如前面說的,nginx 處理空閒的請求非常輕鬆。

tcp_nodelay 和 tcp_nopush

這兩個指標可能是最難理解的,它們在非常底層的網絡方面影響着nginx。最簡短和淺顯的解釋是,這些指標決定操作系統如何處理網絡緩存和何時刷新緩存傳給終端用戶。我只能建議你,如果不瞭解就別亂動。它們不會顯著的改善或者改變任何什麼,所以最好是讓它們保持默認值。

硬件瓶頸

目前爲止我們已經處理了所有源於nginx 可能的瓶頸,是時候弄明白如何最大限度的榨乾我們的服務器了。要做到這個我們着眼餘硬件層面,因爲這是最有可能找到瓶頸的地方。

服務器主要三個方面有潛在的瓶頸。CPU,內存和IO層面。nginx 對於CPU使用非常高效,所以我可以直接了當的告訴你,這方面不會成爲你的瓶頸。同樣地,nginx 在內存使用方面也非常高效,所以也不太可能成爲瓶頸。剩餘的IO 是我們服務器瓶頸的罪魁禍首。

如果你曾經處理過服務器,那麼你對這塊可能非常熟悉。磁盤驅動器非常,非常慢。從磁盤驅動器中讀取內容可能是你在服務器中成本最高的操作了,所以,自然爲了避免IO 瓶頸,我們需要減少nginx 對磁盤的讀寫。

要做到這個,我們可以更改nginx 的行爲,儘可能減少寫磁盤,以及確保nginx 的內存限制,來避免對磁盤的訪問。

訪問日誌

默認情況下,爲了日誌記錄,nginx會往磁盤上的一個文件寫入每條請求的記錄,你可以依靠它來進行統計分析,安全檢查等,但是這會帶來很大的IO 開銷。所以如果你不需要任何訪問日誌,你可以直接關閉日誌記錄,避免寫磁盤。然而,如果你需要訪問日誌,那麼考慮把日誌保存到內存吧(譯註:使用RAM Disk)。這會比寫入磁盤快很多,而且可以顯著降低IO 開銷。(譯註:還可以採用memory buffer,以及ratio 之類的參數來優化nginx寫日誌。)

如果你只是使用訪問日誌來做統計分析,那麼可以考慮使用類似Google Analytics 來代替,或只記錄請求中的一部分即可。

錯誤日誌

我內心也在猶豫我是否應該包含這個指標,因爲你真的不想要禁用錯誤日誌記錄,特別是考慮到實際上錯誤日誌佔的存儲空間很少。也就是說,關於這個指標有困惑的地方,錯誤日誌等級的參數是你可以指定的,如果設置的太低,它會記錄404 錯誤和其他可能的debug 信息。在生產環境中設置爲warn 等級就夠了,並且能保持較低的IO。

Open File Cache

文件系統中的一個讀請求包含打開和關閉文件,考慮到這是一個阻塞的操作,這是一個不能忽略的方面。所以,對我們來說緩存打開文件描述符非常有益,這就是open file cache的作用。鏈接的wiki 對於如何啓用和配置有非常詳細說明,我建議你讀一讀。

Buffers

你需要調優的最重要的一件事是你允許nginx 使用的buffer 大小。如果buffer 設置的太小,nginx 將不得不存儲上游(upstreams)的響應到一個臨時文件中,這會導致IO 讀寫增加。流量越大問題越嚴重。

client_body_buffer_size 指令用來處理客戶端請求的buffer 大小,也就是外部進入的請求體。它被用來處理POST 數據,也就是一些表單提交,文件上傳之類的。如果你要處理很多大容量的POST 數據提交,你需要確保這個buffer 足夠大。

fastcgi_buffersproxy_buffers 指令用來處理你上游服務器的響應,也就是PHP,Apache 或者其他。意思和上文差不多,如果這些buffer 不夠大,數據在返回給用戶之前會被緩存到磁盤中。注意,在同步傳輸數據給客戶端之前,nginx 能緩存的大小有一個上限,即使是在磁盤上。這個限制是由fastcgi_max_temp_file_sizeproxy_max_temp_file_size 指定的。另外你也可以關閉代理連接的緩存,即把proxy_buffering 設置爲off。(通常不是一個好主意!)

徹底去除磁盤IO

去除磁盤IO 最好的方法自然是不用磁盤,如果你只有少量數據,你可以把它們都放入內存,然後完全擺脫磁盤IO 的限制。你的操作系統默認會緩存頻繁訪問過的磁盤扇區,所以你有越多的內存,IO 就越少。也就是說你可以通過買更多的內存來突破這個限制。當然,你的數據越多,需要的內存越大。

Network IO

爲了好玩,我們假設你有足夠的內存來緩存你所有的數據,這意味着理論上你讀的IO 速度能達到3-6gbps。然而,你並且沒有這麼快的網絡通道。很遺憾,對於我們傳輸數據需要的網絡帶寬,能夠優化的有限。唯一有用的優化方式就是減少傳輸的數據,或者壓縮數據。

感謝nginx 有一個gzip 模塊,它可以讓我們在傳給用戶之前壓縮數據,這可以顯著的降低數據大小。通常來說gzip_comp_level設置爲4~5 就足夠了。沒有必要進一步增加壓縮等級,那樣只會消耗CPU 時間。

你也可以使用各種javascript 和css 壓縮器來壓縮數據。這些和nginx 無關,我相信你通過Google 可以找到更多信息。

Phew

到此,這個主題就結束了。如果你還要更多的優化,是時候考慮增加額外的服務器來擴大你的服務規模,而不是把時間浪費在進一步優化nginx 上,這是另外一個話題,我已經研究了一段時間。如果你對這篇文章只有2400 個字感到奇怪,那麼請休息一下,進一步探索我的博客吧!