144 作者 happy江柳清 關注
2015.10.26 17:23* 字數 2536 閱讀 1753評論 2喜歡 8
本文主要從 Nginx 的進程模塊、事件模塊、http網絡模塊三方面介紹了 Nginx 的底層實現原理,希望你通過本文能對Nginx 的基本實現有一定了解。
進程模塊
Nginx 默認採用守護模式啓動,守護模式讓master進程啓動後在後臺運行,不在窗口上卡住。
Nginx 啓動後會有一個 Master 進程和多個Worker 進程,Master 進程主要用來管理 Worker 進程,對網絡事件進程進行收集和分發,調度哪個模塊可以佔用 CPU 資源,從而處理請求。一般配置Worker進程的個數與機器cpu個數一致,從而打到cpu資源的最大化利用,也避免由於進程資源分配帶來的額外開銷。
進程 icon
圖片來自網絡
Master進程工作原理
Master 進程的工作包括
接收來自外界的信號
向各worker進程發送信號
監控worker進程的運行狀態,當worker進程退出後(異常情況下),會自動重新啓動新的worker進程
驚羣現象
驚羣現象 是指請求到來的時候,只有可以成功accept的那個進程會驚醒,其他進程會繼續阻塞。nginx 採用accept-mutex來解決驚羣問題,當一個請求到達的時候,只有競爭到鎖的worker進程才能處理請求,其他進程結合timer_solution 配置的最大的超時時間,再去獲取監聽鎖
來看源碼
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
if (NGX_THREADS)
if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
}
endif
}
/* 檢測是否啓用mutex,多worker進程下一般都會啓用 */
if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
/* 嘗試獲取鎖,不管成功還是失敗都會立即返回 */
return;
}
if (ngx_accept_mutex_held) {
/* 獲取到鎖之後添加flag */
flags |= NGX_POST_EVENTS;
} else {
/* 如果獲取不到鎖需要結合timer事件設置下一次搶鎖的時間 */
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
delta = ngx_current_msec;
/* 開始epoll收集處理事件 */
(void) ngx_process_events(cycle, timer, flags);
/* delta就是epoll_wait消耗掉的時間 */
delta = ngx_current_msec - delta;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"timer delta: %M", delta);
/* accept事件已經被加入到單獨的任務隊列並會被優先處理 */
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
/* accept事件處理完之後先釋放accept鎖,因爲其它事件的處理可能耗時較長,不要佔着茅坑不睡覺 */
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
if (delta) {
ngx_event_expire_timers();
}
/* 之後可以放心處理其它事件了 */
ngx_event_process_posted(cycle, &ngx_posted_events);
}
Worker進程工作原理
當一個worker進程在accept這個連接之後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開連接,這樣一個完整的請求就是這樣的了。我們可以看到,一個請求,完全由worker進程來處理,而且只在一個worker進程中處理。
採用這種方式的好處:
節省鎖帶來的開銷。對於每個worker進程來說,獨立的進程,不需要加鎖,所以省掉了鎖帶來的開銷,同時在編程以及問題查上時,也會方便很多
獨立進程,減少風險。採用獨立的進程,可以讓互相之間不會影響,一個進程退出後,其它進程還在工作,服務不會中斷,master進程則很快重新啓動新的worker進程
在一次請求裏無需進程切換
相關配置
user nobody nobody;
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
worker_rlimit_nofile 65535;
error_log logs/error.log info;
事件模塊
對於一個基本的 WEB 服務器來說,事件通常有三種類型,網絡事件、信號和定時器。
一個請求的基本過程:建立連接 - 接受連接 - 發送數據,在系統底層就是讀寫事件。
Epoll 模型
Epoll出現在 linux2.6以後,Nginx採用 Epoll 這種異步非阻塞的事件處理機制。這種機制的原理就是把一個完整的請求,劃分成多個事件,比如accept(), recv(),磁盤I/O,send(),每個事件都有不同的模塊進行處理。一個請求由一個worker進程處理,在請求多的時候,無需頻繁的切換進程。
事件模塊 icon
圖片來自網絡
master進程先建好需要listen的socket後,然後再fork出多個woker進程,這樣每個work進程都可以去accept這個socket
當一個client連接到來時,所有accept的work進程都會受到通知,但只有一個進程可以accept成功,其它的則會accept失敗,Nginx提供了一把共享鎖accept_mutex來保證同一時刻只有一個work進程在accept連接,從而解決驚羣問題
當一個worker進程accept這個連接後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開連接,這樣一個完成的請求就結束了
Epoll 是基於一個進程處理多個連接、非阻塞IO的策略,Nginx多使用這種策略。
Select 模型
Select 模型在啓動的時候創建多個進程,放在一個進程池裏,並且進程池裏的進程數會隨着請求數目的增加而增加,對於每一個連接,都是在一個進程內處理完畢。所以Select模型能接收的併發量受到所能開啓的進程數影響,進程之間是互相阻塞的,且頻繁的切換進程造成大量開銷。
Select 是基於一個線程處理一個請求的非阻塞IO的策略,Apache使用這種策略。
相關配置
events {
use epoll;
worker_connections 1024;
}
網絡模塊
最大連接數
當作爲http服務器的時候:
max_clients = worker_processes * worker_connections;
當作爲反向代理的時候:
max_clients = worker_processes * worker_connections/4
負載均衡
nginx的upstream目前支持的5種方式的分配
輪詢(默認)
每個請求按時間順序逐一分配到不同的後端服務器,如果後端服務器down掉,能自動剔除。
upstream backserver {
server host:port;
server host:port;
}
weight
指定輪詢機率,weight和訪問比率成正比,用於後端服務器性能不均的情況
upstream backserver {
server host:port weight=10;
server host:port weight=10;
}
ip_hash
每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個後端服務器,可以解決session的問題
upstream backserver {
ip_hash;
server host:port;
server host:port;
}
fair(第三方)
按後端服務器的響應時間來分配請求,響應時間短的優先分配。
upstream backserver {
server server1;
server server2;
fair;
}
url_hash(第三方)
按訪問url的hash結果來分配請求,使每個url定向到同一個後端服務器,後端服務器爲緩存時比較有效
upstream backserver {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
在需要使用負載均衡的server中增加
proxy_pass http://backserver/ ;
upstream backserver{
ip_hash;
server host:port down; (down 表示單前的server暫時不參與負載)
server host:port weight=2; (weight 默認爲1.weight越大,負載的權重就越大)
server host:port
server host:port backup; (其它所有的非backup機器down或者忙的時候,請求backup機器)
}
代理緩存
代理緩衝區相關配置
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# 通過令牌桶原理實現用戶訪問次數限制
limit_req_zone $http_x_forwarded_for zone=req_one:10m rate=30r/s;
#設置Web緩存區名稱爲cache_web,內存緩存空間大小爲300MB,1天沒有被訪問的內容自動清除,硬盤緩存空間
#大小爲3GB。
proxy_temp_path /xxx/proxy_temp_dir 1 2;
proxy_cache_path /xxx/proxy_cache_dir levels=1:2 keys_zone=cache_web:300m inactive=1d max_size=1g;
訪問控制
location ~ /.ht {
deny all;
}
相關配置
一個http指令下可以配置多個server指令塊,一個server指令塊裏可以根據不同的url做配置
http {
include /xxx/nginx/conf/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr^A $remote_user^A [$time_local]^A $request^A '
'$status^A $body_bytes_sent^A $http_referer^A '
'$http_user_agent^A $http_x_forwarded_for^A '
'$request_body^A $http_X_Cache^A $upstream_http_X_Cache^A '
'$upstream_cache_status^A $http_x_accel_expires^A $dna_device';
access_log /xxx/nginx/logs/access.log main;
sendfile on;
tcp_nopush on;
#server_names_hash_bucket_size 128;
#client_header_buffer_size 8k;
open_file_cache max=10240 inactive=20s;// max打開文件指定緩存數量,inactive指多長時間沒請求就刪除緩存
open_file_cache_valid 30s; // 30s檢查一次緩存的有效信息
open_file_cache_min_uses 1;// 最少使用次數,如果超過這個數字,就一直在緩存中打開
keepalive_timeout 60;
# 代理緩衝區相關配置
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# 通過令牌桶原理實現用戶訪問次數限制
limit_req_zone $http_x_forwarded_for zone=req_one:10m rate=30r/s;
#設置Web緩存區名稱爲cache_web,內存緩存空間大小爲300MB,1天沒有被訪問的內容自動清除,硬盤緩存空間
#大小爲3GB。
proxy_temp_path /xxx/proxy_temp_dir 1 2;
proxy_cache_path /xxx/proxy_cache_dir levels=1:2 keys_zone=cache_web:300m inactive=1d max_size=1g;
upstream www_backend_server {
server host:port;
}
upstream m_backend_server {
server host:port;
}
server {
listen 80;
server_name www.xxx.com;
access_log logs/xxx.access.log main;
location ~ \.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index /index.php;
include /xxx/nginx/conf/fastcgi_params;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
server {
listen 80;
server_name www.xxx.com;
access_log logs/xxx.access.log main;
location / {
index index.html;
root /xxx/htdocs;
}
}
}
其他
啓動流程
時間、正則、錯誤日誌、ssl等初始化
讀入命令行參數
OS相關初始化
讀入並解析配置
核心模塊初始化
創建各種暫時文件和目錄
創建共享內存
打開listen的端口
所有模塊初始化
啓動worker進程
Nginx和PHP交互
通過fastcgi模塊進行交互,交互模式有兩種:
fastcgi_pass unix:/xxx/php/var/php-cgi.sock;
fastcgi_pass 127.0.0.1:9000;
Mail配置
mail {
auth_http 127.0.0.1:80/auth.php;
pop3_capabilities “TOP” “USER”;
imap_capabilities “IMAP4rev1” “UIDPLUS”;
server {
listen 110;
protocol pop3;
proxy on;
}
server {
listen 25;
protocol smtp;
proxy on;
smtp_auth login plain;
xclient off;
}
}
下期預告: Nginx 之擴展模塊的開發