成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

【Nginx源碼分析】Nginx配置文件解析(二)

JerryC / 2076人閱讀

摘要:配置文件解析一配置解析流程解析配置的入口函數(shù)是,其輸入?yún)?shù)表示配置文件路徑,如果為表明此時(shí)解析的是指令塊。函數(shù)邏輯比較簡(jiǎn)單,就是讀取完整指令,并調(diào)用函數(shù)處理指令。

運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè)

本文作為nginx配置文件解析的第二篇,開(kāi)始講解nginx配置文件解析的源碼,在閱讀本文之前,希望你已經(jīng)閱讀過(guò)第一篇?!秐ginx配置文件解析(一)》

1.1配置解析流程

解析配置的入口函數(shù)是ngx_conf_parse(ngx_conf_t cf, ngx_str_t filename),其輸入?yún)?shù)filename表示配置文件路徑,如果為NULL表明此時(shí)解析的是指令塊。

那么cf是什么呢?先看看其結(jié)構(gòu)體聲明:

struct ngx_conf_s {
    char                 *name; //當(dāng)前讀取到的指令名稱(chēng)
    ngx_array_t          *args; //當(dāng)前讀取到的指令參數(shù)
 
    ngx_cycle_t          *cycle; //指向全局cycle
    ngx_pool_t           *pool;  //內(nèi)存池
    ngx_conf_file_t      *conf_file; //配置文件
 
    void                 *ctx;   //上下文
    ngx_uint_t            module_type; //模塊類(lèi)型
    ngx_uint_t            cmd_type;   //指令類(lèi)型
 
    ngx_conf_handler_pt   handler; //一般都是NULL,暫時(shí)不管
};

重點(diǎn)需要關(guān)注這些字段:

1)name和args存儲(chǔ)當(dāng)前讀取到的指令信息;

2)ctx上下文,就是我們上面所說(shuō)的指令上下文,想象下如果沒(méi)有ctx我們獲取該指令最終存儲(chǔ)的位置;

3)module_type和cmd_type分別表示模塊類(lèi)型與指令類(lèi)型;讀取到某條指令時(shí),需要遍歷所有模塊的指令數(shù)組,通過(guò)這兩個(gè)字段可以過(guò)濾某些不應(yīng)該解析該配置的模塊與指令。

函數(shù)ngx_conf_parse邏輯比較簡(jiǎn)單,就是讀取完整指令,并調(diào)用函數(shù)ngx_conf_handler處理指令。

函數(shù)ngx_conf_handler主要邏輯是,遍歷類(lèi)型為cf->module_type的模塊,查找該模塊指令數(shù)組中類(lèi)型為cf->cmd_type的指令;如果沒(méi)找到打印錯(cuò)誤日志并返回錯(cuò)誤;如果找到還需要校驗(yàn)指令參數(shù)等是否合法;最后才是調(diào)用set函數(shù)設(shè)置。

這些流程都比較簡(jiǎn)單,難點(diǎn)是如何根據(jù)ctx獲取到該配置最終存儲(chǔ)的位置。下面的代碼需要結(jié)合上圖來(lái)分析。配置肯定是存儲(chǔ)在某個(gè)結(jié)構(gòu)體的,所以需要通過(guò)ctx找到對(duì)應(yīng)結(jié)構(gòu)體。

if (cmd->type & NGX_DIRECT_CONF) {
    //此類(lèi)型的cf->ctx只會(huì)是conf_ctx,直接獲取第index個(gè)元素,說(shuō)明該數(shù)組元素已經(jīng)指向了某個(gè)結(jié)構(gòu)體
    conf = ((void **) cf->ctx)[ngx_modules[i]->index]; 
 
} else if (cmd->type & NGX_MAIN_CONF) {
    //此類(lèi)型的cf->ctx只會(huì)是conf_ctx,獲取的是第index個(gè)元素的地址,原因就在于此時(shí)數(shù)組元素指向NULL
    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);
 
} else if (cf->ctx) {  //此時(shí)cf->ctx可能是events_ctx,http_ctx,srv_ctx或者loc_ctx
 
    //假設(shè)cf->ctx為http_ctx,此時(shí)cmd->conf是字段main_conf,srv_conf或者loc_conf在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量
    confp = *(void **) ((char *) cf->ctx + cmd->conf);
 
    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index]; //一樣是獲取數(shù)組的第ctx_index個(gè)元素,此時(shí)一定是指向某個(gè)結(jié)構(gòu)體
    }
}
 
rv = cmd->set(cf, cmd, conf); //調(diào)用set函數(shù)設(shè)置,注意這里入?yún)onf
1.2 配置文件的解析

函數(shù)ngx_init_cycle會(huì)調(diào)用ngx_conf_parse開(kāi)始配置文件的解析。

解析配置文件首先需要?jiǎng)?chuàng)建配置文件上下文,并初始化結(jié)構(gòu)體ngx_conf_t;

//創(chuàng)建配置文件上下文,并初始化上下文數(shù)組元素
 
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));//ngx_max_module為模塊總數(shù)目
 
//需要遍歷所有核心模塊,并調(diào)用其create_conf創(chuàng)建配置結(jié)構(gòu)體,存儲(chǔ)到上下文數(shù)組
for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->type != NGX_CORE_MODULE) {
        continue;
    }
 
    module = ngx_modules[i]->ctx;
 
    if (module->create_conf) {
        rv = module->create_conf(cycle);
        if (rv == NULL) {
            ngx_destroy_pool(pool);
            return NULL;
        }
        cycle->conf_ctx[ngx_modules[i]->index] = rv;
    }
}
 
//初始化結(jié)構(gòu)體ngx_conf_t
conf.ctx = cycle->conf_ctx;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;

讀者可以查找下代碼,看看哪些核心模塊有create_conf方法。執(zhí)行此步驟之后,可以畫(huà)出下圖:

結(jié)合2.2節(jié)所示的代碼邏輯,可以很容易知道,核心模塊ngx_core_module的配置指令都是帶有NGX_DIRECT_CONF標(biāo)識(shí)的,conf_ctx數(shù)組第0個(gè)元素就指向其配置結(jié)構(gòu)體ngx_core_conf_t。

if (cmd->type & NGX_DIRECT_CONF) {
    conf = ((void **) cf->ctx)[ngx_modules[i]->index];
}
 
rv = cmd->set(cf, cmd, conf);

以配置worker_processes(設(shè)置worker進(jìn)程數(shù)目)為例,其指令結(jié)構(gòu)定義如下:

{ ngx_string("worker_processes"),
  NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
  ngx_set_worker_processes,
  0,
  0,
  NULL }

注意此時(shí)函數(shù)ngx_set_worker_processes入?yún)⒌牡谌齻€(gè)參數(shù)已經(jīng)指向了結(jié)構(gòu)體ngx_core_conf_t,所以可以強(qiáng)制類(lèi)型轉(zhuǎn)換

static char * ngx_set_worker_processes(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
    ngx_core_conf_t  *ccf;
    ccf = (ngx_core_conf_t *) conf;
}
1.3 events指令塊的解析

ngx_events_module模塊(核心模塊)中定義了events指令結(jié)構(gòu),如下:

{ ngx_string("events"),
  NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
  ngx_events_block,
  0,
  0,
  NULL }

events配置指令處理函數(shù)為ngx_events_block;根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:

else if (cmd->type & NGX_MAIN_CONF) {
    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);  //此時(shí)cf->ctx仍然是conf_ctx
}
 
rv = cmd->set(cf, cmd, conf);

即此時(shí)函數(shù)ngx_events_block的第三個(gè)輸入?yún)?shù)是conf_ctx數(shù)組第index個(gè)元素的地址,且該元素指向NULL。

函數(shù)ngx_events_block主要需要處理3件事:1)創(chuàng)建events_ctx上下文;2)調(diào)用所有事件模塊的create_conf方法創(chuàng)建配置結(jié)構(gòu);3)修改cf->ctx (注意解析events塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析events塊中的配置

static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //創(chuàng)建配置上下文events_ctx,只是一個(gè)void*結(jié)構(gòu)
    ctx = ngx_pcalloc(cf->pool, sizeof(void *));
    //數(shù)組,指向所有時(shí)間模塊創(chuàng)建的配置結(jié)構(gòu);ngx_event_max_module為事件模塊數(shù)目
    *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
     
    //conf是conf_ctx數(shù)組某個(gè)元素的地址;即讓該元素指向配置上下文events_ctx
    *(void **) conf = ctx;
 
    //遍歷所有事件模塊,創(chuàng)建配置結(jié)構(gòu)
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }
 
        m = ngx_modules[i]->ctx;
 
        if (m->create_conf) {
            (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
        }
    }
 
    //修改cf的配置上下文,模塊類(lèi)型,指令類(lèi)型;原始cf暫存在pcf變量
    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;
    cf->cmd_type = NGX_EVENT_CONF;
 
    //解析events塊中的配置
    rv = ngx_conf_parse(cf, NULL);
 
    //還原cf
    *cf = pcf;
}

在linux機(jī)器上采用默認(rèn)選項(xiàng)編譯nginx代碼,事件模塊通常只有ngx_event_core_module和ngx_event_core_module,且兩個(gè)模塊都有create_conf方法,執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖:

以ngx_event_core_module模塊中的配置connections為例(設(shè)置連接池連接數(shù)目),其結(jié)構(gòu)定義如下:

{ ngx_string("connections"),
  NGX_EVENT_CONF|NGX_CONF_TAKE1,
  ngx_event_connections,
  0,
  0,
  NULL }

connections配置指令處理函數(shù)為ngx_event_connections;根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:

else if (cf->ctx) {  //此時(shí)cf->ctx是events_ctx
  
    //confp為數(shù)組首地址
    confp = *(void **) ((char *) cf->ctx + cmd->conf);
 
    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index];  //獲取數(shù)組元素
    }
}
 
rv = cmd->set(cf, cmd, conf); //ngx_event_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_event_conf_t

函數(shù)ngx_event_connections實(shí)現(xiàn)較為簡(jiǎn)單,只需要給結(jié)構(gòu)體ngx_event_conf_t相應(yīng)字段賦值即可;注意輸入?yún)?shù)conf指向結(jié)構(gòu)體ngx_event_conf_t,可以直接強(qiáng)制類(lèi)型轉(zhuǎn)換。

static char *
ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_event_conf_t  *ecf = conf;
}
1.4 http指令塊的解析

上面學(xué)習(xí)了events指令塊的解析,http指令塊、server指令塊和location指令塊的解析都是非常類(lèi)似的。

ngx_http_module模塊(核心模塊)中定義了http指令結(jié)構(gòu),如下:

{ ngx_string("http"),
  NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
  ngx_http_block,
  0,
  0,
  NULL }

http配置指令的處理函數(shù)為ngx_http_block,根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:

else if (cmd->type & NGX_MAIN_CONF) {
    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);    //此時(shí)cf->ctx仍然是conf_ctx
}
  
rv = cmd->set(cf, cmd, conf);

即此時(shí)函數(shù)ngx_http_block的第三個(gè)輸入?yún)?shù)是conf_ctx數(shù)組第index個(gè)元素的地址,且該元素指向NULL。

函數(shù)ngx_http_block主要需要處理3件事:1)創(chuàng)建http_ctx上下文;2)調(diào)用所有http模塊的create_main_conf、create_srv_conf和create_loc_conf方法創(chuàng)建配置結(jié)構(gòu);3)修改cf->ctx (注意解析http塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析http塊中的配置

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
    //創(chuàng)建http_ctx配置長(zhǎng)下文
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
 
    //conf是conf_ctx數(shù)組某個(gè)元素的地址,即該元素指向http_ctx配置上下文
    *(ngx_http_conf_ctx_t **) conf = ctx;
 
    //初始化main_conf數(shù)組、srv_conf數(shù)組和loc_conf數(shù)組;ngx_http_max_module為http模塊數(shù)目
    ctx->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
 
    //調(diào)用所有http模塊的create_main_conf方法、create_srv_conf方法和create_loc_conf創(chuàng)建相應(yīng)配置結(jié)構(gòu)
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;
 
        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
        }
 
        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);   
        }
 
        if (module->create_loc_conf) {
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
        }
    }
 
    //修改cf的配置上下文,模塊類(lèi)型,指令類(lèi)型;原始cf暫存在pcf變量
    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_HTTP_MODULE;
    cf->cmd_type = NGX_HTTP_MAIN_CONF;
 
    //解析http塊中的配置
    rv = ngx_conf_parse(cf, NULL);
 
    //還原cf
    *cf = pcf;
}

執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖:

http_ctx配置上下文類(lèi)型為結(jié)構(gòu)體ngx_http_conf_ctx_t,其只有三個(gè)字段main_conf、srv_conf和loc_conf,分別指向一個(gè)數(shù)組,數(shù)組的每個(gè)元素指向的是對(duì)應(yīng)的配置結(jié)構(gòu)。

比如說(shuō)ngx_http_core_module是第一個(gè)http模塊,其create_main_conf方法創(chuàng)建的配置結(jié)構(gòu)為ngx_http_core_main_conf_t。

以ngx_http_core_module模塊的配置keepalive_timeout(該配置可以出現(xiàn)在location塊、server塊和http塊,假設(shè)在http塊中添加該配置)為例,指令結(jié)構(gòu)定義如下:

{ ngx_string("keepalive_timeout"),
  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
  ngx_http_core_keepalive,
  NGX_HTTP_LOC_CONF_OFFSET,
  0,
  NULL }
 
#define NGX_HTTP_LOC_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, loc_conf)

可以看到該指令結(jié)構(gòu)的第四個(gè)參數(shù)不為0了,為loc_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量。

keepalive_timeout配置指令處理函數(shù)為ngx_http_core_keepalive;根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:

else if (cf->ctx) {  //此時(shí)cf->ctx是http_ctx
  
    //cmd->conf為loc_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量;confp為loc_conf數(shù)組首地址
    confp = *(void **) ((char *) cf->ctx + cmd->conf);
 
    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index];  //獲取數(shù)組元素
    }
}
  
rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_http_core_loc_conf_t

函數(shù)ngx_http_core_keepalive實(shí)現(xiàn)較為簡(jiǎn)單,這里不做詳述。

1.5 server指令塊的解析

ngx_http_core_module模塊中定義了server指令結(jié)構(gòu),如下:

{ ngx_string("server"),
  NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
  ngx_http_core_server,
  0,
  0,
  NULL }

server配置指令的處理函數(shù)為ngx_http_core_server,根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:

else if (cf->ctx) {  //此時(shí)cf->ctx是http_ctx
  
    //cmd->conf為0;confp為main_conf數(shù)組首地址
    confp = *(void **) ((char *) cf->ctx + cmd->conf);
 
    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index];  //獲取數(shù)組元素
    }
}
  
rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_http_core_main_conf_t

函數(shù)ngx_http_core_server主要需要處理4件事:1)創(chuàng)建srv_ctx上下文;2)調(diào)用所有http模塊的create_srv_conf和create_loc_conf方法創(chuàng)建配置結(jié)構(gòu);3)將srv_ctx上下文添加到http_ctx配置上下文;4)修改cf->ctx (注意解析http塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析server塊中的配置

static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy){
    //創(chuàng)建srv_ctx配置上下文
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
 
    //cf->ctx為http_ctx配置上下文
    http_ctx = cf->ctx;
 
    //main_conf共用同一個(gè)(server塊中不會(huì)有NGX_HTTP_MAIN_CONF類(lèi)型的配置,所以其實(shí)是不需要main_conf的)
    ctx->main_conf = http_ctx->main_conf;
    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
     
    //遍歷所有http模塊,調(diào)用其create_srv_conf方法和create_loc_conf創(chuàng)建相應(yīng)配置結(jié)構(gòu)
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[i]->ctx;
 
        if (module->create_srv_conf) {
            mconf = module->create_srv_conf(cf);
            ctx->srv_conf[ngx_modules[i]->ctx_index] = mconf;
        }
 
        if (module->create_loc_conf) {
            mconf = module->create_loc_conf(cf);
            ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
        }
    }
 
    //注意這里實(shí)現(xiàn)將srv_ctx上下文添加到http_ctx配置上下文;代碼不好理解,可參考下面的示意圖。
 
    //ngx_http_core_module模塊是第一個(gè)http模塊。獲取其創(chuàng)建的srv_conf類(lèi)型的配置結(jié)構(gòu)ngx_http_core_srv_conf_t;將其ctx字段指向srv_ctx配置上下文
    cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
    cscf->ctx = ctx;
 
    //main_conf是http_ctx上下文的數(shù)組;獲取其創(chuàng)建的main_conf類(lèi)型的配置結(jié)構(gòu)ngx_http_core_main_conf_t;
    //并且將,srv_ctx配置上下文的配置結(jié)構(gòu)ngx_http_core_srv_conf_t添加到http_ctx配置上下文的ngx_http_core_main_conf_t配置結(jié)構(gòu)的servers數(shù)組
    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = ngx_array_push(&cmcf->servers);
    *cscfp = cscf;
 
    //修改cf的配置上下文,模塊類(lèi)型,指令類(lèi)型;原始cf暫存在pcf變量
    pcf = *cf;
    cf->ctx = ctx;
    cf->cmd_type = NGX_HTTP_SRV_CONF;
 
    //解析server塊中的配置;注意此時(shí)配置上下文為srv_ctx
    rv = ngx_conf_parse(cf, NULL);
 
    //還原cf
    *cf = pcf;
}

執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖,這里只畫(huà)出http_ctx與srv_ctx配置上下文的示意圖:

注意上圖紅色的箭頭,按照紅色箭頭的引用,可以從http_ctx配置上下文找到srv_ctx配置上下文;

看到這里可能會(huì)覺(jué)得存儲(chǔ)結(jié)構(gòu)好復(fù)雜,別著急,等解析location指令塊時(shí),圖還會(huì)更復(fù)雜。

但是不用擔(dān)心,這只是解析時(shí)候的存儲(chǔ)結(jié)構(gòu),最終還會(huì)做一些優(yōu)化,查找時(shí)并不是按照這種結(jié)構(gòu)查找的。

至于server指令塊內(nèi)部的配置,比較簡(jiǎn)單,這里不再舉例詳述。

1.6 location指令塊的解析

ngx_http_core_module模塊中定義了location指令結(jié)構(gòu),如下:

{ ngx_string("location"),
  NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
  ngx_http_core_location,
  NGX_HTTP_SRV_CONF_OFFSET,
  0,
  NULL }
 
#define NGX_HTTP_SRV_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, srv_conf)

可以看到,location指令可以出現(xiàn)在server指令塊和location指令塊(即location本身可以嵌套);location配置可以由一個(gè)或者兩個(gè)參數(shù);注意指令結(jié)構(gòu)第四個(gè)參數(shù)不為0了,為srv_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量;指令處理函數(shù)為ngx_http_core_location。根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:

else if (cf->ctx) {  //此時(shí)cf->ctx是srv_ctx
  
    //cmd->conf為srv_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量;confp為srv_conf數(shù)組首地址
    confp = *(void **) ((char *) cf->ctx + cmd->conf);
 
    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index];  //獲取數(shù)組元素
    }
}
  
rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_http_core_srv_conf_t

函數(shù)ngx_http_core_server主要需要處理3件事:1)創(chuàng)建loc_ctx上下文;2)調(diào)用所有http模塊的create_loc_conf方法創(chuàng)建配置結(jié)構(gòu);3)將loc_ctx上下文添加到srv_ctx配置上下文;4)修改cf->ctx (注意解析http塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析location塊中的配置

static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy){
    //創(chuàng)建loc_conf上下文
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
 
    //cf->ctx指向srv_conf上下文
    pctx = cf->ctx;
 
    //main_conf與srv_conf與srv_ctx上下文公用;
    //(location塊中不會(huì)有NGX_HTTP_MAIN_CONF和NGX_HTTP_SRV_CONF類(lèi)型的配置,所以其實(shí)是不需要main_conf和srv_conf的)
    ctx->main_conf = pctx->main_conf;
    ctx->srv_conf = pctx->srv_conf;
    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
 
    //遍歷所有http模塊,調(diào)用其create_loc_conf方法創(chuàng)建相應(yīng)配置結(jié)構(gòu)
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[i]->ctx;
 
        if (module->create_loc_conf) {
            ctx->loc_conf[ngx_modules[i]->ctx_index] = module->create_loc_conf(cf);
        }
    }
 
    //ngx_http_core_module是第一個(gè)http模塊;獲取loc_ctx配置上下文的loc_conf數(shù)組的第一個(gè)元素,即ngx_http_core_loc_conf_t結(jié)構(gòu)
    //將該結(jié)構(gòu)的loc_conf字段指向loc_ctx配置上下文的loc_conf數(shù)組首地址
    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
    clcf->loc_conf = ctx->loc_conf;
 
    獲取srv_ctx配置上下文的loc_conf數(shù)組的第一個(gè)元素,即ngx_http_core_loc_conf_t結(jié)構(gòu)
    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
 
    //將loc_ctx配置上下文的ngx_http_core_loc_conf_t結(jié)構(gòu)添加到srv_ctx配置上下文的ngx_http_core_loc_conf_t的locations字段
    //locations是一個(gè)雙向鏈表,鏈表結(jié)構(gòu)也挺有意思的,有興趣的讀者可以研究下
    if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
    
    }
}

執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖,這里只畫(huà)出srv_ctx和loc_ctx配置上下文的示意圖:

注意上圖紅色的箭頭,按照紅色箭頭的引用,可以從srv_ctx配置上下文找到loc_ctx配置上下文;其實(shí)這句話是不嚴(yán)謹(jǐn)?shù)模瑴?zhǔn)確的說(shuō),從srv_ctx配置上下文只能找到loc_ctx配置上下文的loc_conf數(shù)組。

原因就在于,所有的配置其實(shí)都是存儲(chǔ)在main_conf數(shù)組、srv_conf數(shù)組和loc_conf數(shù)組。而loc_conf配置上下文的main_conf數(shù)組和srv_conf數(shù)組其實(shí)是沒(méi)有存配置的。

所以只需要loc_conf配置上下文的loc_conf數(shù)組即可。

這里還遺留一個(gè)問(wèn)題,location參數(shù)的解析,這也是我們應(yīng)該關(guān)注的重點(diǎn),將在2.9節(jié)講述。至于location指令塊內(nèi)部的配置,比較簡(jiǎn)單,這里不再舉例詳述。

1.7 配置合并

到這一步其實(shí)配置文件已經(jīng)算是解析完成了,但是http相關(guān)存儲(chǔ)結(jié)構(gòu)過(guò)于復(fù)雜。

而且還有一個(gè)問(wèn)題:http_ctx配置上下文和srv_ctx配置上下文都有srv_conf,同時(shí)存儲(chǔ)NGX_HTTP_SRV_CONF類(lèi)型的配置;而http_ctx、srv_ctx和loc_ctx配置上下文都有l(wèi)oc_conf數(shù)組,

同時(shí)存儲(chǔ)NGX_HTTP_LOC_CONF類(lèi)型的配置。那么當(dāng)配置同時(shí)出現(xiàn)在多個(gè)配置上下文中該如何處理,以哪個(gè)為準(zhǔn)呢?

觀察1.1節(jié)nginx模塊的介紹,大多http模塊都有這兩個(gè)方法merge_srv_conf和merge_loc_conf,用于合并不同配置上下文的相同配置。

這里的配置合并其實(shí)就是兩個(gè)srv_conf數(shù)組或者loc_conf數(shù)組的合并。

ngx_http_block函數(shù)中解析完成http塊內(nèi)部所有配置之后,執(zhí)行合并操作。

//此處的ctx是http_ctx配置上下文。不理解的話可以參照上面的示意圖。
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = cmcf->servers.elts;
 
//遍歷所有http模塊(其實(shí)就是遍歷合并srv_conf和loc_conf數(shù)組的每個(gè)元素)
for (m = 0; ngx_modules[m]; m++) {
    if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
        continue;
    }
 
 
    module = ngx_modules[m]->ctx;
    mi = ngx_modules[m]->ctx_index;
 
    //init_main_conf是初始化配置默認(rèn)值的,有些配置沒(méi)有賦值時(shí)需要初始化默認(rèn)值
    if (module->init_main_conf) {
        rv = module->init_main_conf(cf, ctx->main_conf[mi]);
    }
 
    //合并
    rv = ngx_http_merge_servers(cf, cmcf, module, mi);
     
}

合并操作由函數(shù)ngx_http_merge_servers實(shí)現(xiàn):

static char * ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    ngx_http_module_t *module, ngx_uint_t ctx_index) {
 
    //ngx_http_core_srv_conf_t數(shù)組
    cscfp = cmcf->servers.elts;
 
    //cf->ctx指向http_ctx配置上下文
    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
    saved = *ctx;
 
    //遍歷多個(gè)ngx_http_core_srv_conf_t(多個(gè)server配置)
    for (s = 0; s < cmcf->servers.nelts; s++) {
 
        //通過(guò)ngx_http_core_srv_conf_t可以找到每個(gè)srv_ctx配置上下文的srv_conf數(shù)組
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
 
        //合并http_ctx配置上下文的srv_conf數(shù)組中配置到srv_ctx配置上下文的srv_conf數(shù)組
        if (module->merge_srv_conf) {
            rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],cscfp[s]->ctx->srv_conf[ctx_index]);
        }
 
        if (module->merge_loc_conf) {
            //通過(guò)ngx_http_core_srv_conf_t可以找到每個(gè)srv_ctx配置上下文的loc_conf數(shù)組
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;
 
            //合并http_ctx配置上下文的loc_conf數(shù)組中配置到srv_ctx配置上下文的loc_conf數(shù)組
            rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
                                        cscfp[s]->ctx->loc_conf[ctx_index]);
             
            //合并srv_ctx配置上下文的loc_conf數(shù)組中配置到loc_ctx配置上下文的loc_conf數(shù)組
            clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
            rv = ngx_http_merge_locations(cf, clcf->locations, cscfp[s]->ctx->loc_conf, module, ctx_index);
        }
    }
}

函數(shù)ngx_http_merge_locations的實(shí)現(xiàn)與函數(shù)ngx_http_merge_servers基本類(lèi)似,這里不再詳述。合并示意圖如下:

最終http相關(guān)配置存儲(chǔ)在:一個(gè)http_ctx配置上下文的main_conf數(shù)組,多個(gè)srv_ctx配置上下文的srv_conf數(shù)組,多個(gè)loc_ctx配置上下文的loc_conf數(shù)組;為圖中陰影部分。

http_ctx、srv_ctx和loc_ctx之間的引用關(guān)系參考紅色箭頭。

問(wèn)題就在于如何查找到多個(gè)srv_ctx配置上下文的srv_conf數(shù)組,多個(gè)loc_ctx配置上下文的loc_conf數(shù)組,將在第3節(jié)介紹。

1.8 location配置優(yōu)化

location配置的語(yǔ)法規(guī)則是:location [=|~|~*|^~] /uri/ { … },可以簡(jiǎn)單講location配置分為三種類(lèi)型:精確匹配,最大前綴匹配和正則匹配。

分類(lèi)規(guī)則如下:1)以“=”開(kāi)始的為精確匹配;2)以“~”和“~*”開(kāi)始的分別為為區(qū)分大小寫(xiě)的正則匹配和不區(qū)分大小寫(xiě)的正則匹配;3)以“^~”開(kāi)始的是最大前綴匹配;4)參數(shù)只有/uri的是最大前綴匹配。

可以看到類(lèi)型3和類(lèi)型4都是最大類(lèi)型匹配,那么這兩者有什么區(qū)別呢?在查找匹配location時(shí)可以看到。

那么當(dāng)我們配置了多個(gè)locaiton,且請(qǐng)求uri可以滿足多個(gè)location的匹配規(guī)則時(shí),最終選擇哪個(gè)配置呢?不同location類(lèi)型有不同的匹配優(yōu)先級(jí)。

我們先看下location配置的分類(lèi),顯然可以根據(jù)第一個(gè)字符來(lái)分類(lèi),location配置的參數(shù)以及類(lèi)型等信息都存儲(chǔ)在ngx_http_core_loc_conf_t以下幾個(gè)字段:

struct ngx_http_core_loc_conf_s {
    ngx_str_t     name;   //名稱(chēng),即location配置的uri參數(shù)
    ngx_http_regex_t  *regex;  //編譯后的正則表達(dá)式,可標(biāo)識(shí)類(lèi)型2
  
    unsigned      exact_match:1;  //標(biāo)識(shí)以=開(kāi)頭的location配置,類(lèi)型1
    unsigned      noregex:1;   //查找匹配的location配置時(shí)有用。標(biāo)識(shí)匹配到該location之后,不再?lài)L試匹配正則類(lèi)型的locaiton;類(lèi)型3帶有此標(biāo)識(shí)
 
    ngx_http_location_tree_node_t   *static_locations; //通過(guò)命名可以看到這是一棵樹(shù)(存儲(chǔ)的是類(lèi)型為1,3和4的locaiton配置)
    ngx_http_core_loc_conf_t       **regex_locations;   //存儲(chǔ)所有的正則匹配
}

2.7節(jié)解析location指令塊時(shí)提到,srv_ctx上下文的loc_conf數(shù)組,第一個(gè)元素指向類(lèi)型為ngx_http_core_loc_conf_t的結(jié)構(gòu)體,結(jié)構(gòu)體的locations字段時(shí)一個(gè)雙向鏈表,存儲(chǔ)的是當(dāng)前server指令塊內(nèi)部配置的所有l(wèi)ocation。

雙向鏈表節(jié)點(diǎn)定義如下:

typedef struct {
    ngx_queue_t                      queue; //雙向鏈表統(tǒng)一頭部;該結(jié)構(gòu)維護(hù)了prev和next指針;
    ngx_http_core_loc_conf_t        *exact; //類(lèi)型為1和2的location配置存儲(chǔ)在鏈表節(jié)點(diǎn)的此字段
    ngx_http_core_loc_conf_t        *inclusive; //類(lèi)型為3和4的location配置存儲(chǔ)在鏈表節(jié)點(diǎn)的此字段
} ngx_http_location_queue_t;

location已經(jīng)按照類(lèi)型做好了標(biāo)記,且存儲(chǔ)在雙向鏈表,為了實(shí)現(xiàn)location的高效優(yōu)先級(jí)查找,需要給location配置排序,同時(shí)將多個(gè)location配置形成一棵樹(shù)。

這些操作都是由函數(shù)ngx_http_block 執(zhí)行的,且在解析http塊內(nèi)的所有配置之后。

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
     
    //指向比較亂。需要參考上面示意圖的紅色箭頭。
 
    //ctx指向http_ctx配置上下文
    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;
 
    //遍歷所有srv_ctx上下文
    for (s = 0; s < cmcf->servers.nelts; s++) {
 
        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
 
        //該方法實(shí)現(xiàn)了location配置排序,以及將雙向鏈表中正則類(lèi)型的location配置裁剪出來(lái)
        if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
 
        //雙向鏈表中只剩下類(lèi)型1、3和4的location配置了
        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }
}

下面分別分析location配置排序,正則類(lèi)型location配置的裁剪,以及形成location樹(shù):

1)location配置排序由函數(shù)ngx_queue_sort(ngx_queue_t queue, ngx_int_t (cmp)(const ngx_queue_t , const ngx_queue_t ))實(shí)現(xiàn),輸入?yún)?shù)queue為雙向鏈表,cmp為鏈表節(jié)點(diǎn)的比較函數(shù)。ngx_queue_sort函數(shù)按照從小到大排序,且采用穩(wěn)定的排序算法(兩個(gè)元素相等時(shí),排序后順序與排序之前的原始順序相同)

locations雙向鏈表節(jié)點(diǎn)的比較函數(shù)為ngx_http_cmp_locations,通過(guò)該函數(shù)就可以知道location配置的排序規(guī)則,實(shí)現(xiàn)如下:

//與一般比較函數(shù)一樣,返回1表示one大于two;0表示兩者相等;-1表示one小于two
static ngx_int_t ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two) {
    //正則類(lèi)型的配置大于其余類(lèi)型的配置
    if (first->regex && !second->regex) {
        return 1;
    }
    if (!first->regex && second->regex) {
        return -1;
    }
    if (first->regex || second->regex) {
        return 0;
    }
 
    rc = ngx_filename_cmp(first->name.data, second->name.data,
                        ngx_min(first->name.len, second->name.len) + 1);
 
    //按照l(shuí)ocation名稱(chēng),即uri排序;且當(dāng)兩個(gè)uri前綴相同時(shí),保證精確匹配類(lèi)型的location排在前面
    if (rc == 0 && !first->exact_match && second->exact_match) {
        return 1;
    }
 
    return rc;
}

按照上述比較函數(shù)的規(guī)則排序后,正則類(lèi)型的location配置一定是排列在雙向鏈表尾部;精確匹配和最大前綴匹配首先按照uri字母序排列,且當(dāng)兩個(gè)uri前綴相同時(shí),精確匹配類(lèi)型排列在最大前綴匹配的前面。

2)經(jīng)歷了第一步location已經(jīng)排好序了,且正則類(lèi)型的排列在雙向鏈表尾部,這樣就很容易裁剪出所有正則類(lèi)型的location配置了。只需要從頭到尾遍歷雙向鏈表,直至查找到正則類(lèi)型的location配置,并從該位置出將雙向鏈表拆分開(kāi)來(lái)即可。

3)雙向鏈表中只剩下精確匹配類(lèi)型和最大前綴匹配類(lèi)型的location配置了,且都是按照uri字母序排序的,這些配置會(huì)被組織成一棵樹(shù),方便查找。

形成的這棵樹(shù)是一棵三叉樹(shù),每個(gè)節(jié)點(diǎn)node都有三個(gè)子節(jié)點(diǎn),left、tree和right。left一定小于node;right一定大于node;tree與node前綴相同,且tree節(jié)點(diǎn)uri長(zhǎng)度一定大于node節(jié)點(diǎn)uri長(zhǎng)度。

注意只有最大前綴匹配的配置才有tree節(jié)點(diǎn)。

思考下為什么會(huì)有tree節(jié)點(diǎn),且最大前綴匹配才有tree節(jié)點(diǎn)呢?node匹配成功后,tree節(jié)點(diǎn)還有可能匹配成功。

形成樹(shù)過(guò)程這里不做詳述,有興趣的讀者可以研究下函數(shù)ngx_http_init_static_location_trees的實(shí)現(xiàn)。

總結(jié)

至此配置文件解析完成,http、server和location相關(guān)配置最終存儲(chǔ)在main_conf、多個(gè)srv_conf和多個(gè)loc_conf數(shù)組中,但是當(dāng)服務(wù)器接收到客戶端請(qǐng)求時(shí),如何查找對(duì)應(yīng)的srv_conf數(shù)組和loc_conf數(shù)組呢?

將在第三篇《nginx配置文件解析(三)》講解。

希望交流,一起學(xué)習(xí)Nginx PHP Redis 等源碼的朋友請(qǐng)入微信群:

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/40171.html

相關(guān)文章

  • Nginx源碼分析Nginx配置文件解析(一)

    摘要:本文將從源碼從此深入分析配置文件的解析,配置存儲(chǔ),與配置查找。在學(xué)習(xí)配置文件的解析過(guò)程之前,需要先了解一下模塊與指令的一些基本知識(shí)。 運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè) 配置文件是nginx的基礎(chǔ),對(duì)于學(xué)習(xí)nginx源碼甚至開(kāi)發(fā)nginx模塊的同學(xué)來(lái)說(shuō)更是必須深究。本文將從源碼從此深入分析nginx配置文件的解析,配置存儲(chǔ),與配置查找。 看本文之前讀者可以先思考兩個(gè)問(wèn)題: 1.nginx源碼中隨處可以...

    JasonZhang 評(píng)論0 收藏0
  • Nginx源碼分析Nginx的listen處理流程分析

    摘要:四監(jiān)聽(tīng)套接字的使用假設(shè)此處我們使用作為事件處理模塊在增加事件時(shí)用戶可以使用中的字段當(dāng)事件發(fā)生時(shí)該字段也會(huì)帶回。在創(chuàng)建監(jiān)聽(tīng)套接字時(shí)將結(jié)構(gòu)分為級(jí)監(jiān)聽(tīng)套接字地址各級(jí)都是一對(duì)多的關(guān)系。 施洪寶 一. 基礎(chǔ) nginx源碼采用1.15.5 后續(xù)部分僅討論http中的listen配置解析以及優(yōu)化流程 1.1 概述 假設(shè)nginx http模塊的配置如下 http{ server { ...

    yuanzhanghu 評(píng)論0 收藏0
  • Nginx 源碼分析:從模塊到配置(下)

    摘要:每個(gè)模塊由以下幾部分構(gòu)成結(jié)構(gòu)體代表模塊本身,其指針被放入數(shù)組中。結(jié)構(gòu)體用來(lái)表示模塊的配置內(nèi)容,其中部分成員可以通過(guò)配置文件進(jìn)行配置。調(diào)用該中的函數(shù),該函數(shù)最終初始化模塊對(duì)應(yīng)的結(jié)構(gòu)體,完成配置。因此,分析源碼中的配置指令,就是分析結(jié)構(gòu)體。 本篇的上篇 Nginx 源碼分析:從模塊到配置(上),建議閱讀本篇前先閱讀上篇。 關(guān)于模塊 Nginx的架構(gòu)高度模塊化。每個(gè)模塊各司其職,組合在一...

    Chao 評(píng)論0 收藏0
  • 高并發(fā)

    摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢?jiàn)它的使用,在開(kāi)始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽(tīng)名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...

    supernavy 評(píng)論0 收藏0
  • 高并發(fā)

    摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢?jiàn)它的使用,在開(kāi)始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽(tīng)名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...

    ddongjian0000 評(píng)論0 收藏0
  • 高并發(fā)

    摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢?jiàn)它的使用,在開(kāi)始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽(tīng)名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...

    wangdai 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<