由於性能優秀穩定、配置簡單以及跨平臺,被越來越多的公司和個人所採用,現已成爲市場份額繼Apache之後的第二大Web服務器。各大小網站論壇博客也介紹說明了Nginx從安裝到優化的各種配置。不過看了很多這些相關Nginx的文檔之後,發現一個比較大的問題,就是這些文檔基本也就從兩個方面着手,一是修改Nginx的配置文件,二是調整操作系統的相關內核參數;而且文檔說明也不夠明瞭,缺乏比較系統級別的優化。本文將從Nginx源碼編譯安裝開始,到修改配置文件,調整系統內核參數以及架構四個方面着手分別介紹如何優化。
一. 安裝
(1) 精簡模塊
Nginx由於不斷添加新的功能,附帶的模塊也越來越多。很多操作系統廠商爲了用戶方便安裝管理,都增加了rpm、deb或者其他自有格式軟件包,可以本地甚至在線安裝。不過我不太建議使用這種安裝方式。這雖然簡化了安裝,在線安裝甚至可以自動解決軟件依賴關係,但是安裝後軟件的文件佈局過於分散,不便管理維護;同時也正是由於存在軟件包之間的依賴關係,導致當有安全漏洞、或者其它問題,想要通過更新升級Nginx新版本時卻發現yum、deb源還未發佈新版本(一般都落後於官網發佈的軟件版本)。最重要的是採用非源碼編譯安裝的方式,默認會添加入許多模塊,比如郵件相關、uwsgi、memcache等等,很多網站運行時這些模塊根本未用到,雖然平時佔用的資源很小,但是仍然可能是壓彎駱駝的一根稻草。各種非必需模塊默認安裝運行的同時,也給Web系統帶來了安全隱患。儘量保持軟件的輕裝上陣,是每個運維應當盡力做到的,所以我建議一般常用的服務器軟件使用源碼編譯安裝管理。我一般使用的編譯參數如下,PHP相關模塊fastcgi被保留用作後文優化說明:
./configure \
"--prefix=/App/nginx" \
"--with-http_stub_status_module"\
"--without-http_auth_basic_module"\
"--without-http_autoindex_module"\
"--without-http_browser_module"\
"--without-http_empty_gif_module"\
"--without-http_geo_module" \
"--without-http_limit_conn_module"\
"--without-http_limit_req_module"\
"--without-http_map_module" \
"--without-http_memcached_module"\
"--without-http_proxy_module"\
"--without-http_referer_module"\
"--without-http_scgi_module" \
"--without-http_split_clients_module"\
"--without-http_ssi_module" \
"--without-http_upstream_ip_hash_module"\
"--without-http_upstream_keepalive_module"\
"--without-http_upstream_least_conn_module"\
"--without-http_userid_module"\
"--without-http_uwsgi_module"\
"--without-mail_imap_module" \
"--without-mail_pop3_module" \
"--without-mail_smtp_module" \
"--without-poll_module" \
"--without-select_module" \
"--with-cc-opt='-O2'"
(2) GCC編譯參數優化 [可選項】
編譯參數根據網站是否真正用到的原則增添或者減少,比如我們公司如果需要用到ssi模塊,從而能夠實現訪問shtml頁面,可以將第17行刪除,那麼Nginx將默認安裝。大家可以通過運行 "./configure--help" 查看編譯幫助,決定是否需要安裝哪些模塊。
GCC總共提供了5級編譯優化級別:
-O0: 無優化。
-O和-O1: 使用能減少目標代碼尺寸以及執行時間並且不會使編譯時間明顯增加的優化。在編譯大型程序的時候會顯著增加編譯時內存的使用。
-O2: 包含-O1的優化並增加了不需要在目標文件大小和執行速度上進行折衷的優化。編譯器不執行循環展開以及函數內聯。此選項將增加編譯時間和目標文件的執行性能。
-Os: 可以看成 -O2.5,專門優化目標文件大小,執行所有的不增加目標文件大小的-O2優化選項,並且執行專門減小目標文件大小的優化選項。適用於磁盤空間緊張時使用。但有可能有未知的問題發生,況且目前硬盤容量很大,常用程序無必要使用。
-O3: 打開所有 -O2 的優化選項外增加 -finline-functions、-funswitch-loops、-fgcse-after-reload 優化選項。相對於 -O2 性能並未有較多提高,編譯時間也最長,生成的目標文件也更大更佔內存,有時性能不增反而降低,甚至產生不可預知的問題(包括錯誤),所以並不被大多數軟件安裝推薦,除非有絕對把握方可使用此優化級別。
修改GCC編譯參數,提高編譯優化級別,此方法適用於所有通過GCC編譯安裝的程序,不止Nginx。穩妥起見用 -O2,這也是大多數軟件編譯推薦的優化級別。查看Nginx源碼文件 auto/cc/gcc,搜索NGX_GCC_OPT,默認GCC編譯參數爲-O,可以直接修改內容爲NGX_GCC_OPT="-O2"或者在 ./configure配置時添加--with-cc-opt='-O2'選項。
二. 配置
應用服務器的性能優化主要在合理使用CPU、內存、磁盤IO和網絡IO四個方面,現在我們從Nginx配置文件 nginx.conf 入手進行優化:
(1) 工作進程數的選擇
指令:worker_processes
定義了Nginx對外提供web服務時的工作進程數。最優值取決於許多因素,包括(但不限於)CPU核心的數量、存儲數據的硬盤數量及負載模式。不能確定的時候,將其設置爲可用的CPU內核數將是一個好的開始(設置爲“auto”將嘗試自動檢測它).Shell執行命令 ps ax | grep "nginx: workerprocess" | grep -v "grep" 可以看到運行中的Nginx工作進程數,一般建議設置成服務器邏輯核心數,Shell執行命令 cat/proc/cpuinfo | grep processor | wc -l 可以檢測出服務器邏輯核心總數,偷懶可以直接寫auto,Nginx自適應。
(2) 是否綁定CPU
指令:worker_cpu_affinity
綁定工作進程到對應CPU核心,Nginx默認未開啓CPU綁定。目前的服務器一般爲多核CPU,當併發很大時,服務器各個CPU的使用率可能出現嚴重不均衡的局面,這時候可以考慮使用CPU綁定,以達到CPU使用率相對均勻的狀態,充分發揮多核CPU的優勢。top、htop等程序可以查看所有CPU核心的使用率狀況。綁定樣例:
worker_processes 4;
worker_cpu_affinity 0001 00100100 1000;
(3) 打開文件數限制
指令:worker_rlimit_nofile
設定了每個Nginx工作進程打開的最大文件數,受限於系統的用戶進程打開文件數限制,未設置則使用系統默認值。理論上應該設置爲當前Shell啓動進程的最大打開文件數除以Nginx的工作進程數。由於Nginx的工作進程打開文件數並不一完全均勻,所以可以將其設置成Shell啓動進程的最大打開文件數。Shell執行命令 ulimit -n 可以查看當前登錄Shell會話最大打開文件數數限制.Linux系統用戶進程默認同時打開文件最大數爲1024,這個值太小,訪問量稍大就報“too many openfiles"。Shell執行命令先修改用戶打開文件數限制:
echo "* -nofile 65536">> /etc/security/limits.conf
然後添加入/etc/profile如下兩行內容,修改所有Shell和通過Shell啓動的進程打開文件數限制:
echo "ulimit -n 65536">> /etc/profile
Shell執行命令使當前Shell臨時會話立即生效:
ulimit -n 65536
(4) 驚羣問題
指令:accept_mutex
如果 accept_mutex指令值爲 on 啓用,那麼將輪流喚醒一個工作進程接收處理新的連接,其餘工作進程繼續保持睡眠;如果值爲 off 關閉,那麼將喚醒所有工作進程,由系統通過use指令指定的網絡IO模型調度決定由哪個工作進程處理,未接收到連接請求的工作進程繼續保持睡眠,這就是所謂的“驚羣問題”。Web服務器Apache的進程數很多,成百上千也是時有的事,“驚羣問題”也尤爲明顯。Nginx爲了穩定,參數值保守的設置爲 on 開啓狀態。可以將其設置成Off 提高性能和吞吐量,但這樣也會帶來上下文切換增多或者負載升高等等其它資源更多消耗的後果。
(5) 網絡IO模型
指令:use
定義了Nginx設置用於複用客戶端線程的輪詢方法(也可稱多路複用網絡IO模型)。這自然是選擇效率更高的優先,Linux 2.6+內核推薦使用epoll,FreeBSD推薦使用kqueue,安裝時Nginx會自動選擇。
(6) 連接數
指令:worker_connections
定義了Nginx一個工作進程的最大同時連接數,不僅限於客戶端連接,包括了和後端被代理服務器等其他的連接。官網文檔還指出了該參數值不能超過 worker_rlimit_nofile值,所以建議設置成和 worker_rlimit_nofile 值相等。
(7) 打開文件緩存
指令:open_file_cache
開啓關閉打開文件緩存,默認值 off 關閉,強烈建議開啓,可以避免重新打開同一文件帶來的系統開銷,節省響應時間。如需開啓必須後接參數 max=數字,設置緩存元素的最大數量。當緩存溢出時,使用LRU(最近最少使用)算法刪除緩存中的元素;可選參數 inactive=時間 設置超時,在這段時間內緩存元素如果沒有被訪問,將從緩存中刪除。
示例:open_file_cachemax=65536 inactive=60s。
指令:open_file_cache_valid
設置檢查open_file_cache緩存的元素的時間間隔。
指令:open_file_cache_min_uses
設置在由open_file_cache指令的inactive參數配置的超時時間內,文件應該被訪問的最小次數。如果訪問次數大於等於此值,文件描述符會保留在緩存中,否則從緩存中刪除。
(8) 日誌相關
指令:access_log 和 error_log
當併發很大時,Nginx的訪問日誌和錯誤日誌的保存肯定會造成對磁盤的大量讀寫,也將影響Nginx的性能。併發量越大,IO越高。這時候可以考慮關閉訪問日誌和錯誤日誌,或者將日誌保存到tmpfs文件系統裏,或者減少保存的訪問日誌條目和錯誤日誌的級別,從而避免磁盤IO的影響。關閉日誌使用 access_log off。如必須保存日誌,可以按每日或者每時或者其它時間段對日誌做切割,這也可以減小IO,雖然可能效果不是特別大,不過因爲日誌文件尺寸變小了很多,也方便查閱或歸檔分析日誌。一般線上環境建議錯誤日誌設置爲 error 或者 crit。自定義訪問日誌的條目和錯誤日誌的級別,詳細信息可以參閱官網或者網上其它文檔,按需修改。
(9) 隱藏Nginx版本號
指令:server_tokens
開啓或關閉“Server”響應頭中輸出的Nginx版本號。推介設置爲 off,關閉顯示響應頭的版本號,對性能的提高有小小的裨益,主要還是爲了安全起見,不被駭客找到版本號對應的漏洞,從而被***。
(10) 壓縮相關
指令:gzip
Nginx默認開啓了gzip壓縮功能。有可能很多人認爲,開啓gzip壓縮會增加CPU的處理時間和負載。但是經過我們網站的測試發現,關閉了gzip壓縮功能的Nginx雖然減少了CPU計算,節省了服務器的響應時間,但網站頁面總體響應時間反而加長了,原因在於js和css、xml、json、html等等這些靜態文件的數據傳輸時間的增長大大超過了服務器節省出來的響應時間,得不償失。gzip on 開啓壓縮後,大約可以減少75%的文件尺寸,不但節省了比較多的帶寬流量,也提高了頁面的整體響應時間。所有建議還是開啓。當然也不是所有的靜態文件都需要壓縮,比如靜態圖片和PDF、視頻,文件本身就應當做壓縮處理後保存到服務器。這些文件再次使用gzip壓縮,壓縮的比例並不高,甚至適得其反,壓縮後文件尺寸增大了。CPU壓縮處理這些靜態文件增加佔用的服務器響應時間絕大部分時候會超過了被壓縮減小的文件尺寸減少的數據傳輸時間,不划算。是否需要對Web網站開啓壓縮,以及對哪些文件過濾壓縮,大家可以通過使用HttpWatch、Firebug等等網絡分析工具對比測試。
指令:gzip_comp_level
指定壓縮等級,其值從1到9,數字越大,壓縮率越高,越消耗CPU,負載也越高。9等級無疑壓縮率最高,壓縮後的文件尺寸也最小,但也是最耗CPU資源,負載最高,速度最慢的,這對於用戶訪問有時是無法忍受的。一般推薦使用1-4等級,比較折衷的方案。我們公司網站使用等級2。
指令:gzip_min_length
指定壓縮的文件最小尺寸,單位 bytes 字節,低於該值的不壓縮,超過該值的將被壓縮。我們網站設置爲1k,太小的文件沒必要壓縮,壓縮過小尺寸文件帶來增加的CPU消耗時間和壓縮減少的文件尺寸降低的數據下載時間互相抵消,並有可能增加總體的響應時間。
指令:gzip_types
指定允許壓縮的文件類型,Nginx配置目錄 conf 下的 mime.types 文件存放了Nginx支持的文件類型,text/html類型文件,文件後綴爲html htm shtml默認壓縮。推薦配置:gzip_types text/plain text/css application/jsonapplication/x-javascript text/xml application/xml application/xml+rsstext/javascript。
(11) 瀏覽器緩存
指令:expires
設置HTTP應答中的“Expires”和“Cache-Control”頭標。"Expires"一般結合"Last-Modified"使用。當設置了合理的expires配置時,瀏覽器第一次訪問Web頁面元素,會下載頁面中的的靜態文件到本機臨時緩存目錄下。第二次及之後再次訪問相同URL時將發送帶頭標識"If-Modified-Since"和本地緩存文件時間屬性值的請求給服務器,服務器比對服務器本地文件時間屬性值,如果未修改,服務器直接返回http 304狀態碼,瀏覽器直接調用本地已緩存的文件;如果時間屬性值修改了,重新發送新文件。這樣就避免了從服務器再次傳送文件內容,減小了服務器壓力,節省了帶寬,同時也提高了用戶訪問速度,一舉三得。指令後接數字加時間單位,即爲緩存過期時間;-1 表示永遠過期,不緩存。強烈建議添加expires配置,過期時間的選擇具體分析。我們公司的部分Nginx配置如下:
location ~.+\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~.+\.(js|css|xml|javascript|txt|csv)$
{
expires 30d;
}
或者統一將靜態文件放在固定目錄下再對目錄做location和expires,示例:
location /static/
{
expires 30d;
}
(12) 持久連接
指令:keepalive_timeout
啓用Http的持久連接Keepalive屬性,複用之前已建立的TCP連接接收請求、發送迴應,減少重新建立TCP連接的資源時間開銷。在此的建議是當網站頁面內容以靜態爲主時,開啓持久連接;若主要是動態網頁,且不能被轉化爲靜態頁面,則關閉持久連接。後接數字和時間單位符號。正數爲開啓持久連接,0關閉。
(13) 減少HTTP請求次數
網站頁面中存在大量的圖片、腳本、樣式表、Flash等靜態元素,減少訪問請求次數最大的優點就是減少用戶首次訪問頁面的加載時間。可以採用合併相同類型文件爲一個文件的辦法減少請求次數。這其實屬於Web前端優化範疇,應當由Web前段工程師做好相關靜態文件的規劃管理,而不是由運維來做。不過Nginx也可以通過安裝阿里巴巴提供的Concat或者Google的PageSpeed模塊實現這個合併文件的功能。我們公司並未使用合併功能,具體安裝配置信息請查詢網上相關文檔,這裏不再累述。Concat源代碼網址:https://github.com/alibaba/nginx-http-concat/,PageSpeed源代碼網址:https://github.com/pagespeed/ngx_pagespeed。
(14) PHP相關
Nginx不能直接解析PHP代碼文件,需要調用FastCGI接口轉給PHP解釋器執行,然後將結果返回給Nginx。PHP優化本文暫不介紹。Nginx可以開啓FastCGI的緩存功能,從而提高性能。
指令:fastcgi_temp_path
定義FastCGI緩存文件保存臨時路徑。
指令:fastcgi_cache_path
定義FastCGI緩存文件保存路徑和緩存的其它參數。緩存數據以二進制數據文件形式存儲,緩存文件名和key都是通過對訪問URL使用MD5計算獲得的結果。緩存文件先保存至fastcgi_temp_path指定的臨時目錄下,然後通過重命名操作移至fastcgi_cache_path指定的緩存目錄。levels指定了目錄結構,子目錄數以16爲基數;keys_zone指定了共享內存區名和大小,用於保存緩存key和數據信息;inactive指定了緩存數據保存的時間,當這段時間內未被訪問,將被移出;max_size指定了緩存使用的最大磁盤空間,超過容量時將最近最少使用數據刪除。建議fastcgi_temp_path和fastcgi_cache_path設爲同一分區,同分區移動操作效率更高。示例:
fastcgi_temp_path /tmp/fastcgi_temp;
fastcgi_cache_path /tmp/fastcgi_cachelevels=1:2 keys_zone=cache_fastcgi:16m inactive=30m max_size=1g;
示例中使用/tmp/fastcgi_temp作爲FastCGI緩存的臨時目錄;/tmp/fastcgi_cache作爲FastCGI緩存保存的最終目錄;一級子目錄爲16的一次方16個,二級子目錄爲16的2次方256個;共享內存區名爲cache_fastcgi,佔用內存128MB;緩存過期時間爲30分鐘;緩存數據保存於磁盤的最大空間大小爲1GB。
指令:fastcgi_cache_key
定義FastCGI緩存關鍵字。啓用FastCGI緩存必須加上這個配置,不然訪問所有PHP的請求都爲訪問第一個PHP文件URL的結果。
指令:fastcgi_cache_valid
爲指定的Http狀態碼指定緩存時間。
指令:fastcgi_cache_min_uses
指定經過多少次請求相同的URL將被緩存。
指令:fastcgi_cache_use_stale
指定當連接FastCGI服務器發生錯誤時,哪些情況使用過期數據迴應。
指令:fastcgi_cache
緩存使用哪個共享內存區。
我常用nginx.conf模板,大家根據情況做適當修改:
user nginx nginx;
worker_processes auto;
error_log logs/error.logerror;
pid logs/nginx.pid;
worker_rlimit_nofile 65536;
events
{
use epoll;
accept_mutexoff;
worker_connections 65536;
}
http
{
include mime.types;
default_type text/html;
charset UTF-8;
server_names_hash_bucket_size128;
client_header_buffer_size4k;
large_client_header_buffers4 32k;
client_max_body_size 8m;
open_file_cachemax=65536 inactive=60s;
open_file_cache_valid 80s;
open_file_cache_min_uses 1;
log_format main '$remote_addr- $remote_user [$time_local] "$request" '
'$status$body_bytes_sent "$http_referer" '
'"$http_user_agent""$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
server_tokensoff;
fastcgi_temp_path /tmp/fastcgi_temp;
fastcgi_cache_path/tmp/fastcgi_cache levels=1:2 keys_zone=cache_fastcgi:128m inactive=30mmax_size=1g;
fastcgi_cache_key$request_method://$host$request_uri;
fastcgi_cache_valid200 302 1h;
fastcgi_cache_valid301 1d;
fastcgi_cache_validany 1m;
fastcgi_cache_min_uses1;
fastcgi_cache_use_staleerror timeout http_500 http_503 invalid_header;
keepalive_timeout 60;
gzip on;
gzip_min_length1k;
gzip_buffers 464k;
gzip_http_version1.1;
gzip_comp_level2;
gzip_typestext/plain text/css application/json application/x-javascript text/xmlapplication/xml application/xml+rss text/javascript;
server
{
listen 80;
server_name localhost;
index index.html;
root /App/web;
location~ .+\.(php|php5)$
{
fastcgi_pass unix:/tmp/php.sock;
fastcgi_index index.php;
include fastcgi.conf;
fastcgi_cache cache_fastcgi;
}
location~ .+\.(gif|jpg|jpeg|png|bmp|swf|txt|csv|doc|docx|xls|xlsx|ppt|pptx|flv)$
{
expires30d;
}
location~ .+\.(js|css|html|xml)$
{
expires30d;
}
location/nginx-status
{
stub_statuson;
allow192.168.1.0/24;
allow127.0.0.1;
denyall;
}
}
}
三. 內核
Linux內核參數部分默認值不適合高併發,一般臨時方法可以通過調整/Proc文件系統,或者直接修改/etc/sysctl.conf配置文件永久保存。調整/Proc文件系統,系統重啓後還原至默認值,所以不推薦。Linux內核調優,主要涉及到網絡和文件系統、內存等的優化,下面是我常用的內核調優配置:
grep -q"net.ipv4.tcp_max_tw_buckets" /etc/sysctl.conf || cat >>/etc/sysctl.conf << EOF
########################################
net.core.rmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
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_max_tw_buckets = 10000
net.ipv4.ip_local_port_range = 102465500
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_mem = 786432 10485761572864
fs.aio-max-nr = 1048576
fs.file-max = 6815744
kernel.sem = 250 32000 100 128
vm.swappiness = 10
EOF
sysctl -p
詳細說明大家可以查看我的Linux內核優化文章:http://dongsong.blog.51cto.com/916653/1631085。
四. 架構
Nginx的最大優勢在於處理靜態文件和代理轉發功能,支持7層負載均衡和故障隔離。 動靜分離是每個網站發展到一定規模之後必然的結果。靜態請求則應當最好將其拆分,並啓用獨立的域名,既便於管理的需要,也便於今後能夠快速支持CDN。如果一臺Nginx性能無法滿足,則可以考慮在Nginx前端添加LVS負載均衡,或者F5等硬件負載均衡(費用昂貴,適合土豪公司單位),由多臺Nginx共同分擔網站請求。還可以考慮結合Varnish或者Squid緩存靜態文件實現類似CDN功能。新版Nginx目前已經支持直接讀寫Memcache,可以編譯安裝時候選擇添加此類模塊,從而節省了轉交給PHP或者JPS等動態程序服務器處理時間,提高效率的同時,減小了動態服務器的負載。