nginx http模塊實現原理講解——數據存儲結構

從本節開始,我們將進入http模塊實現原理的講解,關於http模塊,有一個非常重要的點就是其是如何存儲http塊、server塊和location塊的數據的,而且nginx有的配置項是可以在多個配置塊中使用的,當http塊、server塊和location塊中兩個或者兩個以上的配置塊都配置了該配置項的時候,就會有一個問題是,nginx是如何處理這些配置項的。本文主要講解http塊中的各個模塊數據的存儲方式,這將是理解nginx的http模塊的工作方式的重要基石。

1. 核心模塊的存儲方式

        在nginx運行過程中,有一個全局配置結構體ngx_cycle_t,其有一個屬性conf_ctx,這個屬性是存儲nginx所有模塊配置的一個數組,這個數組的長度與nginx模塊的個數相同。不過需要注意的是,conf_ctx數組的第一維只會存儲核心模塊的配置,而其他模塊對應的位置處的數組元素其實是爲NULL。在conf_ctx中,各個核心模塊配置結構體的存儲位置與該模塊在所有模塊(包括非核心模塊)中的相對位置是一致的,如下圖所示爲nginx存儲核心模塊的一個結構示意圖:

        這裏標註的eventshttp只是爲了展示方便而添加的,本質上這個數組的元素的類型是void*的指針,至於該指針指向的具體結構體的類型,則是根據各個核心模塊自身的定義來的。

        在http模塊下,其指向了一個ngx_http_conf_ctx_t類型的結構體,這個結構體的作用就是用來存儲http配置塊中各個配置項的數據的。如下是這個結構體的定義:

typedef struct {
  	// 存儲MAIN級別配置
    void **main_conf;  	// 存儲SRV級別配置
    void **srv_conf;  	// 存儲LOC級別配置
    void **loc_conf;
} ngx_http_conf_ctx_t;

        我們知道,在nginx.conf配置文件中,在http塊下還配置有server塊,而server塊下也是可以有location塊,更有甚者,在location塊下可以有子location塊,如此往復,而這裏的ngx_http_conf_ctx_t結構體的作用就是存儲所有的這些配置所對應的結構體數據。首先,我們需要明確的一點是,在nginx.conf配置文件中,配置項都是由一個個模塊定義的,一個模塊可以定義多個配置項,對於這些配置項的解析工作都是由這個模塊所定義的方法進行的。但是,一般的,一個模塊一般都只會定義一個結構體,這個結構體中的各個屬性則對應於該模塊所定義的各個配置項的數據,也就是說,通過各個模塊所定義的方法,其會將其所定義的配置項對應的配置轉換爲該模塊所定義的結構體。這裏所說的結構體就對應於上面的main_confsrv_confloc_conf中的配置。從上面的定義就可以看出,這三個屬性的類型都是指針類型的數組,而數組的長度就對應於模塊的個數,準確來講,是對應於http模塊的各個。在解析各個http模塊的配置之前,nginx會對各個http模塊在當前類型的模塊(http模塊)中進行相對位置進行標記,每個http模塊的相對位置就對應於上面的三個屬性的數組下標。前面已經講到,每個http模塊都只會有一個配置結構體存儲該模塊所定義的所有配置數據,而這些配置結構體就是存儲在上面的三個數組中的。這樣,我們就能夠理解了,其實上面的結構體的三個屬性,每一個屬性的數組都對應了一個http模塊的配置結構體。

        既然這裏每個模塊都有一個結構體存儲在數組的對應索引位置,那這裏爲什麼需要三個數組呢?比如說,對於ngx_http_core_module,其相對位置在http模塊是第一個,也就是說main_conf[0]srv_conf[0]loc_conf[0]存儲的都是ngx_http_core_module的配置結構體,爲什麼需要三個結構體。這裏我們需要說明的是,對於每個http模塊,其會根據需要將配置項按照可使用範圍劃分爲三類:僅用於http塊,可以用於http塊和server塊,以及可以用於http塊、server塊和location塊。每一類配置項都使用的是一個不同的結構體,比如ngx_http_core_module就定義了ngx_http_core_main_conf_t用於存儲僅用於http塊的配置項,定義了ngx_http_core_srv_conf_t用於存儲用於http塊和server塊的配置項,定義了ngx_http_core_loc_conf_t用於存儲用於http塊、server塊和location塊的配置項。對應於上面的數組就是,main_conf[0]的結構體類型爲ngx_http_core_main_conf_tsrv_conf[0]的結構體類型爲ngx_http_core_srv_conf_tloc_conf[0]對應的結構體類型爲ngx_http_core_loc_conf_t。說到這裏,我們就必須要釐清一個問題了,比如,對於某個配置項,其配置在了http塊中,但是其類型是可以用於http塊、server塊和location塊的,那麼其就會被存儲在loc_conf[0]中,也就是說,上面的一整個結構體,從目前來看,存儲的都是在http塊中解析出來的各個配置項的數據。那麼nginx是如何標記一個配置項是這三種類型中的哪一種呢?這主要是通過ngx_command_t結構體來定義的,如下所示爲三個典型的配置:

{
  ngx_string("variables_hash_max_size"),
  NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
  ngx_conf_set_num_slot,
 	NGX_HTTP_MAIN_CONF_OFFSET,
 	offsetof(ngx_http_core_main_conf_t, variables_hash_max_size), 	NULL},
{
  ngx_string("listen"),
 	NGX_HTTP_SRV_CONF | NGX_CONF_1MORE,
 	ngx_http_core_listen,
 	NGX_HTTP_SRV_CONF_OFFSET, 	0, 	NULL},
{
  ngx_string("root"),
 	NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF
 	  | NGX_CONF_TAKE1,
 	ngx_http_core_root,
 	NGX_HTTP_LOC_CONF_OFFSET, 	0, 	NULL},

        這裏我們以variables_hash_max_sizelistenroot三個指令爲例,這三個指令都是ngx_http_core_module模塊定義的配置項,但是它們存儲的位置則是完全不同的。我們需要注意的就是每個指令的第四個屬性的定義:NGX_HTTP_MAIN_CONF_OFFSETNGX_HTTP_SRV_CONF_OFFSETNGX_HTTP_LOC_CONF_OFFSET。這三個類型的定義有兩重含義,一個是表示這個配置項是僅用於http塊,還是可以用於http塊和server塊,再或者是可以用於http塊、server塊和location塊;另一重含義是定義了這個配置項在上面講的ngx_http_conf_ctx_t中的偏移量,所謂的偏移量指的就是,在知道ngx_http_conf_ctx_t結構體對象的指針地址時,通過這裏的偏移量就可以計算出當前配置項所存儲的數組。這裏我們就需要展示一段代碼,即在ngx_conf_parse()方法中,其主要是用於解析nginx.conf配置文件的,在解析了某個配置項之後,就會在所有的模塊中,找到該配置項的定義,如果找到了配置項,就會嘗試獲取存儲該配置項所對應的結構體,並且會調用該配置項指定的方法進行配置項數據的解析。這裏嘗試獲取該配置項所對應的結構體時,就需要用上上面的偏移量。如下是獲取該配置項的方法:

// 查找配置對象,NGX_DIRECT_CONF常量單純用來指定配置存儲區的尋址方法,只用於core模塊if (cmd->type & NGX_DIRECT_CONF) {
  conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];  // NGX_MAIN_CONF常量有兩重含義,其一是指定指令的使用上下文是main(其實還是指core模塊),
  // 其二是指定配置存儲區的尋址方法。} else if (cmd->type & NGX_MAIN_CONF) {
  conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);  // 除開core模塊,其他類型的模塊都會使用第三種配置尋址方式,也就是根據cmd->conf的值
  // 從cf->ctx中取出對應的配置。舉http模塊爲例,cf->conf的可選值是NGX_HTTP_MAIN_CONF_OFFSET、
  // NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET,
  // 分別對應“http{}”、“server{}”、“location{}”這三個http配置級別。

  // 這個if判斷的作用主要是,cf->ctx的類型是ngx_http_conf_ctx_t,而cmd->conf主要的值可選
  // NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET,
  // 可以看到ngx_http_conf_ctx_t的屬性有main_conf、srv_conf和loc_conf,
  // 其實這裏就是在計算當前的配置對象是存儲在這三個數組中的哪一個數組中,以default_type指令爲例,
  // 其ngx_command_t的配置爲:
  // {ngx_string("default_type"),
  //     NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
  //     ngx_conf_set_str_slot,
  //     NGX_HTTP_LOC_CONF_OFFSET,
  //     offsetof(ngx_http_core_loc_conf_t, default_type),
  //     NULL},
  // 可以看到,其conf屬性的值爲NGX_HTTP_LOC_CONF_OFFSET,則說明其是存儲在loc_conf數組中的,
  // 而該數組中的元素類型爲ngx_http_core_loc_conf_t,因而可以看到,後面ngx_command_t
  // 中offset屬性的值就指定爲了offsetof(ngx_http_core_loc_conf_t, default_type),
  // 這就是在計算default_type屬性在ngx_http_core_loc_conf_t結構體中的位置。
  // 通過下面的if判斷第一步confp = *(void **) ((char *) cf->ctx + cmd->conf);,就可以
  // 計算出當前所使用的結構體是在main_conf、srv_conf
  // 和loc_conf的哪一個數組中,而通過第二步conf = confp[cf->cycle->modules[i]->ctx_index];
  // 的計算,就可以計算出該結構體在數組中的具體位置,並且獲取該結構體數據。
  // 需要注意的是,這種計算方式只適用於http模塊的配置項獲取,因爲只有http模塊的配置結構體是
  // ngx_http_conf_ctx_t類型的} else if (cf->ctx) {
  confp = *(void **) ((char *) cf->ctx + cmd->conf);  if (confp) {
    conf = confp[cf->cycle->modules[i]->ctx_index];
  }
}

        這裏我們需要重點關注最後一個else if分支,這裏就表明了http模塊是如何根據配置項的定義來計算該配置項所對應的結構體的存儲位置的。下面的圖就展示了包含有http塊配置的整體結構:

2. server塊的存儲方式

        上面我們講到,使用ngx_http_conf_ctx_t結構體就可以存儲所有的http塊中的配置項,那麼server塊中的配置項是如何存儲的呢?其主要存儲在ngx_http_core_module模塊的main_conf中,也即上面的main_conf[0]所對應的ngx_http_core_main_conf_t結構體中,該結構體有一個屬性servers,這個屬性的類型爲ngx_array_t,也即一個數組。也就是說,在每個http配置塊下,每個server配置塊都對應於servers數組的一個元素,而數組的元素類型與http塊的一致,還是ngx_http_conf_ctx_t。不過區別在於,由於當前的配置項一定是可用於server塊或者location塊中的,而不是僅僅只能用於http塊中的,因而配置項的類型一定是上面講到的NGX_HTTP_SRV_CONF_OFFSETNGX_HTTP_LOC_CONF_OFFSET之一,而不可能是NGX_HTTP_MAIN_CONF_OFFSET。因而這裏雖然每個server配置塊對應的配置結構體還是ngx_http_conf_ctx_t,但是其main_conf數組是不會有對應的配置項的,而只能從http塊中繼承配置項。既然是繼承,nginx的處理方式是直接將該數組的指針指向http塊對應的ngx_http_conf_ctx_tmain_conf數組。如下所示爲兩個server塊配置的示意圖:

        這個圖稍微看起來有點複雜,但實際上並不複雜,按照配置塊劃分,上面的ngx_http_conf_ctx_t中存儲的就是http塊的配置,而下面的兩個ngx_http_conf_ctx_t存儲的就是兩個server塊中的配置,中間的引用過程是通過http塊的ngx_http_core_module模塊對應的ngx_http_core_main_conf_t.servers進行的。需要注意的一點是,上面的server塊的配置中,main_conf指針都是指向的http塊的對應ngx_http_conf_ctx_tmain_conf屬性。

3. location塊的存儲方式

        對於location塊的存儲,其存儲結構也還是ngx_http_conf_ctx_t,並且由於當前配置項在location塊中的,因而其類型一定不會是NGX_HTTP_MAIN_CONF_OFFSETNGX_HTTP_SRV_CONF_OFFSET,也就是說,解析location配置項得到的數據一定是存儲在loc_conf數組中的。因而,與server塊一樣,location塊對應的ngx_http_conf_ctx_t結構體中的main_confsrv_conf指向的則是當前location所在的http塊的main_conf和所在的server塊的srv_conf數組。

        另外,一個server塊下會有多個location塊,在存儲結構上,這些location塊是以隊列的方式進行組織的,與server塊類似,這個隊列則是存儲在其所在的server塊對應的ngx_http_conf_ctx_tloc_conf[0]中的。這裏的loc_conf[0]的結構體類型爲ngx_http_core_loc_conf_s,其有一個ngx_queue_t類型的屬性locations就是該location隊列。最後需要注意的是,這裏的locations屬性表徵的不僅僅只是server塊下的多個location塊,因爲在location配置塊下還可以繼續配置多個location塊,如此不斷遞歸下去。這些子location塊的類型其實還是ngx_http_core_loc_conf_s,因而也是可以通過locations屬性進行表徵的。如下是加入location配置塊的結構體示意圖:

        圖中展示了兩個location並列組織的情形,其main_confsrv_conf分別指向了http塊的main_conf和當前location塊所在的server塊的srv_conf,並且兩個location塊對應的結構體是以隊列的方式組織在ngx_http_core_loc_conf_t中的。

4. 小結

        本文從ngx_cycle_t結構體開始,介紹了http塊的配置項是如何存儲在ngx_cycle_t中的,並且依次介紹了http塊、server塊和location塊的存儲方式,以及相互之間的組織方式。


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