nginx學習筆記(6):程式模型的設計

li27z發表於2017-04-11

nginx程式模型

nginx採用一個master管理程式、多個worker工作程式(還有可選的cache相關程式)的設計方式:

這裡寫圖片描述

啟動nginx後(多程式啟動。nginx也可以單程式啟動),我們可以觀察到系統中的程式狀態如下圖(配置檔案中設定worker程式的個數為1):
這裡寫圖片描述

其中,master程式主要用來管理worker程式,包括接收來自外界的訊號,向各worker程式傳送訊號,監控worker程式的執行狀態,當worker程式退出後(異常情況下),會自動重新啟動新的worker程式。而基本的網路事件,則是放在worker程式中來處理了。

下面,我們就來具體瞭解一下worker程式和master程式的工作機制。

worker程式工作流程

1)
worker程式中迴圈執行ngx_worker_process_cycle方法來控制程式的執行,它會關注以下4個全域性標誌位:

sig_atomic_t ngx_terminate;
sig_atomic_t ngx_quit;
ngx_uint_t ngx_exiting;
sig_atomic_t ngx_reopen;

其中的ngx_terminate、ngx_quit、ngx_reopen標誌位都將由worker程式中處理訊號的方法ngx_signal_handler根據其接收到的訊號來設定。

// ngx_signal_handler方法原型
void ngx_signal_handler(int signo);

這裡寫圖片描述

2)
ngx_worker_process_cycle方法會通過檢查ngx_exiting、ngx_terminate、ngx_quit、ngx_reopen這4個標誌位來決定後續動作:

檔名:ngx_process_cycle.c

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
...
    // 迴圈
    for(; ;) {
        // 如果ngx_exiting為1,程式退出
        if(ngx_exiting) {
        ...
        }

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");

        // ngx_exiting不為1或還有事件要處理則呼叫ngx_process_events_and_timers方法處理事件
        ngx_process_events_and_timers(cycle);

        // 如果ngx_terminate為1,強制結束程式
        if(ngx_terminate) {
        ...
        }

        // 如果ngx_quit為1,優雅地關閉程式
        if(ngx_quit) {
        ...
        }

        // 如果ngx_reopen為1,重新開啟所有檔案
        if(ngx_reopen) {
        ...
        }
    }
}

具體的工作流程如下圖:
這裡寫圖片描述

master程式工作流程

1)
與worker程式的工作機制類似,master程式會通過檢查以下7個標誌位來決定ngx_master_process_cycle方法的執行:

sig_atomic_t ngx_reap;
sig_atomic_t ngx_terminate;
sig_atomic_t ngx_quit;
sig_atomic_t ngx_reconfigure;
sig_atomic_t ngx_reopen;
sig_atomic_t ngx_change_binary;
sig_atomic_t ngx_noaccept;

ngx_signal_handler方法也會根據接收到的訊號設定以上這些標誌位。
這裡寫圖片描述

除此之外,還有一個與訊號無關的標誌位也在master的工作流程中使用到:ngx_uint_t ngx_restart;

2)
在分析master程式的工作流程前,我們需要了解master程式管理子程式的資料結構——ngx_process全域性陣列。定義如下:

// 定義1024個元素的ngx_process陣列,也就是最多隻能有1024個子程式
#define NGX_MAX_PROCESS 1024

// 當前操作的程式在ngx_process陣列中的下標
ngx_int_t ngx_process_slot;

// ngx_process陣列中有意義的ngx_process_t元素中最大的下標
ngx_int_t ngx_last_process;

// 儲存所有子程式的陣列
// 所有子程式相關的狀態資訊都儲存在ngx_process陣列中
ngx_process_t ngx_process[NGX_MAX_PROCESS];
// 陣列元素型別ngx_process_t結構體的定義
typedef struct {
    ngx_pid_t pid;  // 程式ID

    int status;  // 父程式由waitpid系統呼叫獲取到的程式狀態

    // 這是由socketpair系統呼叫產生出的用於程式間通訊的socket控制程式碼
    // 這一對socket控制程式碼可以互相通訊,目前用於master父程式與worker子程式問的通訊
    ngx_socket_t channel[2];

    // 子程式的迴圈執行方法,當父程式呼叫ngx_spawn_process生成子程式時使用
    ngx_spawn_proc_pt proc;

    // 上面的ngx_spawn_proc_pt方法中第2個引數雷要傳遞1個指標,它是可選的
    // 例如,worker子程式就不需要,而cache manage程式就需要ngx_cache_manager_ctx上下文成員
    // 這時,data一般與ngx_spawn_proc_pt方法中第2個引數是等價的
    void *data;

    char *name;  // 程式名稱。作業系統中顯示的程式名稱與name相同

    unsigned respawn:1;  // 標誌位,為1時表示在重新生成子程式

    unsigned just_spawn:1;  // 標誌位,為1時表示正在生成子程式

    unsigned detached:1;  // 標誌位,為1時表示在進行父、子程式分離

    unsigned exiting:1;  // 標誌位,為1時表示程式正在退出

    unsigned exited:1;  // 標誌位,為1時表示程式已經退出
} ngx_process_t;

3)
master程式通過ngx_spawn_process方法啟動一個子程式,其中封裝了fork系統呼叫,並且會從ngx_process陣列中選擇一個還未使用的ngx_process_t元素來儲存這個子程式的相關資訊(如果所有1024個陣列元素中已經沒有空餘的元素,即子程式個數超過了最大值1024,那麼將會返回NGX_INVALID_PID)。因此,ngx_process陣列中元素的初始化在ngx_spawn_process方法中進行。

// ngx_spawn_process方法的定義
// 引數中的函式指標proc是指子程式中將要執行的工作迴圈
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn);

// ngx_spawn_proc_pt的定義
typedef void(*ngx_spawn_proc_pt)(ngx_cycle_t *cycle, void *data);

// worker程式、cache manage/loader程式的工作迴圈也是依照ngx_spawn_proc_pt來定義的
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data);
static void ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data);

4)
ngx_master_process_cycle方法的邏輯如下圖:
這裡寫圖片描述

master程式的工作迴圈如下圖:
這裡寫圖片描述

注意:此工作迴圈並不是在不停地執行以上步驟,而是會通過sigsuspend呼叫使master程式休眠,等待master程式收到訊號後啟用master程式繼續由上面的第一步執行迴圈。

5) master程式對程式的控制有以下兩種方式:
I.手動傳送訊號
master程式接收訊號從而管理眾worker程式,那麼,可以通過kill呼叫向master程式傳送訊號,比如kill -s SIGHUP <nginx master pid>用以通知nginx重讀配置項並重啟:master程式在接收到訊號後,會先重新載入配置,然後再啟動新程式開始接收新請求,並向所有老程式傳送訊號告知不再接收新請求並在處理完所有未處理完的請求後自動退出。

II.自動傳送訊號
可以通過帶命令列引數啟動新程式來傳送訊號給master程式,比如./nginx -s reload用以啟動一個新的nginx程式,而新程式在解析到reload引數後會向master程式傳送訊號(新程式會幫我們把手動傳送訊號中的動作自動完成)。

小結

nginx的這種設計具有以下幾個優點:
1)利用多核系統的併發處理能力
現代作業系統已經支援多核CPU架構,這使得多個程式可以佔用不同的CPU核心來工作。如果只有一個程式在處理請求,則必然會造成CPU資源的浪費。如果多個程式間的地位不平等,則必然會有某一級同一地位的程式成為瓶頸,因此,nginx中所有的worker工作程式都是完全平等的(woker程式個數一般設定為機器CPU核數)。這提高了網路效能,降低了請求的時延。

2)負載均衡
多個worker工作程式間通過程式間通訊來實現負載均衡,也就是說,一個請求到來時,會優先分配到負載較輕的worker程式中處理。這將降低請求的時延,並在一定程度上提高網路效能。

3) 管理程式會負責監控工作程式的狀態,並負責管理其行為
管理程式不會佔用多少系統資源,它只是用來啟動、停止、監控或使用其他行為來控制工作程式。首先,這提高了系統的可靠性,當工作程式出現問題時,管理程式可以啟動新的工作程式來避免效能的下降。其次,管理程式支援nginx服務執行中的程式升級、配置項的修改等操作。這種設計使得動態可擴充套件性、動態定製性、動態可進化性較容易實現。


參考資料:
陶輝.深入理解Nginx 模組開發與架構解析.北京:機械工業出版社,2013
nginx程式模型. http://www.cnblogs.com/liushaodong/archive/2013/02/26/2933511.html
nginx平臺初探. http://tengine.taobao.org/book/chapter_02.html#id1

相關文章