一、Nginx的模塊
Nginx由內(nèi)核和模塊組成,其中,內(nèi)核的設(shè)計非常微小和簡潔,完成的工作也非常簡單,僅僅通過查找配置文件將客戶端請求映射到一個location block(location是Nginx配置中的一個指令,用于URL匹配),而在這個location中所配置的每個指令將會啟動不同的模塊去完成相應(yīng)的工作。
Nginx的模塊從結(jié)構(gòu)上分為核心模塊、基礎(chǔ)模塊和第三方模塊:
- 核心模塊:HTTP模塊、EVENT模塊和MAIL模塊
- 基礎(chǔ)模塊:HTTP Access模塊、HTTP FastCGI模塊、HTTP Proxy模塊和HTTP Rewrite模塊,
- 第三方模塊:HTTP Upstream Request Hash模塊、Notice模塊和HTTP Access Key模塊。
Nginx的模塊從功能上分為如下三類。
- Handlers(處理器模塊)。此類模塊直接處理請求,并進行輸出內(nèi)容和修改headers信息等操作。Handlers處理器模塊一般只能有一個。
- Filters (過濾器模塊)。此類模塊主要對其他處理器模塊輸出的內(nèi)容進行修改操作,最后由Nginx輸出。
- Proxies (代理類模塊)。此類模塊是Nginx的HTTP Upstream之類的模塊,這些模塊主要與后端一些服務(wù)比如FastCGI等進行交互,實現(xiàn)服務(wù)代理和負載均衡等功能。

Nginx本身做的工作實際很少,當它接到一個HTTP請求時,它僅僅是通過查找配置文件將此次請求映射到一個location block,而此location中所配置的各個指令則會啟動不同的模塊去完成工作,因此模塊可以看做Nginx真正的勞動工作者。通常一個location中的指令會涉及一個handler模塊和多個filter模塊(當然,多個location可以復用同一個模塊)。handler模塊負責處理請求,完成響應(yīng)內(nèi)容的生成,而filter模塊對響應(yīng)內(nèi)容進行處理。
Nginx的模塊直接被編譯進Nginx,因此屬于靜態(tài)編譯方式。啟動Nginx后,Nginx的模塊被自動加載,不像Apache,首先將模塊編譯為一個so文件,然后在配置文件中指定是否進行加載。在解析配置文件時,Nginx的每個模塊都有可能去處理某個請求,但是同一個處理請求只能由一個模塊來完成。
二、Nginx的多進程IO模型
1. IO模型:blocking I/O

從圖宏觀來看,左側(cè)為應(yīng)用程序,右側(cè)為內(nèi)核,當應(yīng)用程序的進程使用recvform函數(shù)發(fā)起對內(nèi)核的調(diào)用,內(nèi)核開始準備數(shù)據(jù),一般來說內(nèi)核需要收到一個完整的數(shù)據(jù)包(如UDP),不會很快,因此應(yīng)用程序的進程處于阻塞等待的狀態(tài),待內(nèi)核將數(shù)據(jù)準備好,它將數(shù)據(jù)從內(nèi)核拷貝到用戶內(nèi)存,內(nèi)核才會返回結(jié)果,然后應(yīng)用程序解除阻塞狀態(tài),去處理下一步請求,從本次過程,做過開發(fā)的基本都能理解,平時程序都是屬于這種阻塞的同步調(diào)用方式。
2. IO模型:nonblocking I/O

從圖可以看出,應(yīng)用程序進程當使用recvform函數(shù)對內(nèi)核發(fā)起調(diào)用時,內(nèi)核立即返回一個數(shù)據(jù)沒有準備好的結(jié)果,進程通過輪詢不斷的去請求內(nèi)核,詢問數(shù)據(jù)是否給老子準備好了,知道內(nèi)核返回結(jié)果,說已經(jīng)準備好了,進程才結(jié)束輪詢。
跟阻塞IO對比,進程本地不必阻塞等待,而是隔三差五的通過輪詢調(diào)用,在這個過程中,會大量的占用CPU的時間,所以一般Web服務(wù)器都不使用這種I/O模型。
3. IO模型:I/O multiplexing (select and poll)

多路復用模型新增了幾個函數(shù):select、poll、epoll(Linux2.6以后的內(nèi)核開始支持),這幾個函數(shù)也會使進程阻塞,但是和阻塞I/O所不同的,這兩個函數(shù)可以同時阻塞多個I/O操作,而且可以同時對多個讀操作,多個寫操作的I/O函數(shù)進行監(jiān)測,直到有數(shù)據(jù)可讀或可寫時,才真正調(diào)用I/O操作函數(shù),說直白一點就是他們可以監(jiān)控多個內(nèi)核的IO操作,一個頂仨,一個select會監(jiān)測多個socket,當內(nèi)核準備好其中一個數(shù)據(jù)時,select會立即通知進行,趕快使用recvform調(diào)用內(nèi)核,讓內(nèi)核抓緊開始拷貝數(shù)據(jù)到用戶內(nèi)存,如此,它比阻塞IO好處就是一次能處理多個連接,而阻塞IO只能處理一個。
其中還有一個點,就是epoll、poll要比select要高級一點,他們是無輪詢的,因為他們用callback,select需要通過遍歷Socket來完成調(diào)度,如果socket多,那肯定是需要浪費CPU時間的,而epoll和poll使用回調(diào)函數(shù),給套接字注冊個回調(diào)函數(shù),當他們活躍時,自動完成相關(guān)操作,就避免了輪詢,這些函數(shù)實際是阻塞進程的。
4. IO模型:signal driven I/O (SIGIO)

信號驅(qū)動IO,應(yīng)用程序進程建立SIGIO處理函數(shù)調(diào)用內(nèi)核,內(nèi)核會立即返回數(shù)據(jù)沒有準備好的信號,進程不再阻塞,待內(nèi)核準備好后,發(fā)送SIGIO信號給用戶進程,然后用戶進程通過阻塞的方式使用recvform函數(shù)調(diào)用內(nèi)核,讓內(nèi)核把數(shù)據(jù)拷貝到用戶內(nèi)存,并返回拷貝結(jié)果??吹酱颂幨遣皇撬圃嘧R,其實這有點回調(diào)的意思了。
5. IO模型:asynchronous I/O (the POSIX aio_functions)

異步IO,也就是AIO,這個過程其實就非常簡單,應(yīng)用程序進程調(diào)用內(nèi)核,內(nèi)核返回數(shù)據(jù)沒有準備好消息,進程繼續(xù)去干別的事情,待內(nèi)核準備好數(shù)據(jù),并且拷貝到用戶內(nèi)存,才通知進程已完成數(shù)據(jù)拷貝,因此,可以看出這種IO方式是效率最高的。
三、nginx的優(yōu)化
1. nginx.conf優(yōu)化
user nginx ;
pid /var/run/nginx.pid;
worker_processes auto; #定義了nginx對外提供web服務(wù)時的worder進程數(shù)。
worker_rlimit_nofile 100000; #worker進程的最大打開文件數(shù)限制
2. Events模塊
events {
worker_connections 2048; #設(shè)置可由一個worker進程同時打開的最大連接數(shù)
multi_accept on; #告訴nginx收到一個新連接通知后接受盡可能多的連接
use epoll; #用于復用客戶端線程的輪詢方法
}
3. HTTP 模塊
http {
server_tokens off; #關(guān)閉在錯誤頁面中的nginx版本
sendfile on; #
tcp_nopush on; #告訴nginx在一個數(shù)據(jù)包里發(fā)送所有頭文件,而不一個接一個的發(fā)送
tcp_nodelay on; #告訴nginx不要緩存數(shù)據(jù),而是一段一段的發(fā)送
#當需要及時發(fā)送數(shù)據(jù)時,就應(yīng)該給應(yīng)用設(shè)置這個屬性,
#這樣發(fā)送一小塊數(shù)據(jù)信息時就不能立即得到返回值。
access_log off;
error_log /var/log/nginx/error.log crit;
...
}
timeout設(shè)置
keepalive_timeout 10; #給客戶端分配keep-alive鏈接超時時間。服務(wù)器將在這個超時時間過后關(guān)閉鏈接。
client_header_timeout 10; #設(shè)置請求頭
client_body_timeout 10; #和請求體(各自)的超時時間。我們也可以把這個設(shè)置低些。
reset_timedout_connection on;
#告訴nginx關(guān)閉不響應(yīng)的客戶端連接。這將會釋放那個客戶端所占有的內(nèi)存空間。
send_timeout 10; #在兩次客戶端讀取操作這段時間內(nèi),客戶端沒有讀取任何數(shù)據(jù),nginx就會關(guān)閉連接。
連接限制
limit_conn_zone $binary_remote_addr zone=addr:5m;
#設(shè)置用于保存各種key(比如當前連接數(shù))的共享內(nèi)存的參數(shù)
limit_conn addr 100; #我們允許每一個IP地址最多同時打開有100個連接
type
include /etc/nginx/mime.types;
default_type text/html;
charset UTF-8;
gzip
gzip on; #采用gzip壓縮的形式發(fā)送數(shù)據(jù)
gzip_disable "msie6"; #為指定的客戶端禁用gzip功能
# gzip_static on;
gzip_proxied any; #允許或者禁止壓縮基于請求和響應(yīng)的響應(yīng)流。我們設(shè)置為any,意味著將會壓縮所有的請求
gzip_min_length 1000; #設(shè)置對數(shù)據(jù)啟用壓縮的最少字節(jié)數(shù)
gzip_comp_level 4; #設(shè)置數(shù)據(jù)的壓縮等級。這個等級可以是1-9之間的任意數(shù)值,9是最慢但是壓縮比最大的
gzip_types text/plain
text/css
application/json
application/x-javascript
text/xml
application/xml
application/xml+rss
text/javascript; #設(shè)置需要壓縮的數(shù)據(jù)格式
file_cache
open_file_cache max=100000 inactive=20s;
#打開緩存的同時也指定了緩存最大數(shù)目,以及緩存的時間。
#我們可以設(shè)置一個相對高的最大時間,這樣我們可以在它們不活動超過20秒后清除掉
open_file_cache_valid 30s; #在open_file_cache中指定檢測正確信息的間隔時間。
open_file_cache_min_uses 2; #定義了open_file_cache中指令參數(shù)不活動時間期間里最小的文件數(shù)。
open_file_cache_errors on; #指定了當搜索一個文件時是否緩存錯誤信息
include /etc/nginx/conf.d/*.conf;
buffer
server_names_hash_bucket_size 128; #服務(wù)器名字的hash表大小
client_header_buffer_size 32k; #用于設(shè)置客戶端請求的Header頭緩沖區(qū)的大小,一般1kb就夠用
large_client_header_buffers 4 64k; #設(shè)置客戶端請求的Header頭緩沖區(qū)大小,默認為4K??蛻舳苏埱笮胁荒艹^設(shè)置的第一個數(shù),請求的Header頭信息不能大于設(shè)置的第二個數(shù),否則會報"Request URI too large"(414)或“Bad request”(400)錯誤。如果客戶端的Cookie信息較大,則需增加緩沖區(qū)大小
client_max_body_size 8m; #上傳文件大小限制
4. proxy_pass
proxy_buffering off;
proxy_buffer_size 128k;
proxy_buffers 100 128k;