Nginx 之實現原理

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 之擴展模塊的開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章