大家都知道nginx里面有一個(gè)subrequest的概念,,也就是子請(qǐng)求,,它并不是http標(biāo)準(zhǔn)里面的概念,它是在當(dāng)前請(qǐng)求中發(fā)起的一個(gè)新的請(qǐng)求,,它擁有自己的ngx_http_request_t結(jié)構(gòu),,uri和args。一般來(lái)說(shuō)使用subrequest的效率可能會(huì)有些影響,,因?yàn)樗枰匦聫膕erver rewrite開(kāi)始走一遍request處理的PHASE,,但是它在某些情況下使用能給我們帶來(lái)方便,現(xiàn)在我們比較常用的是用subrequest來(lái)訪問(wèn)一個(gè)upstream的后端,,并給它一個(gè)ngx_http_post_subrequest_t的回調(diào)handler,,這樣有點(diǎn)類似于一個(gè)異步的函數(shù)調(diào)用。對(duì)于從upstream返回的數(shù)據(jù),,subrequest允許根據(jù)創(chuàng)建時(shí)指定的flag,,來(lái)決定由用戶自己處理(回調(diào)handler中)還是由upstream模塊直接發(fā)送到out put filter。簡(jiǎn)單的說(shuō)一下subrequest的行為,,nginx使用subrequest訪問(wèn)某個(gè)location,,產(chǎn)生相應(yīng)的數(shù)據(jù),并插入到nginx輸出鏈的相應(yīng)位置(創(chuàng)建subrequest時(shí)的位置),,下面我用agentzh(章亦春,,之前是公司北京同事,最近離職了,,據(jù)說(shuō)回家專心搞開(kāi)源)的echo模塊(https://github.com/agentzh/echo-nginx-module)來(lái)舉例說(shuō)明一下:
- location /main {
- echo nginx;
- echo_location /sub;
- echo world;
- }
- location /sub {
- echo hello;
- }
訪問(wèn)/main,,將得到如下響應(yīng):
上面的echo_location指令是發(fā)起一個(gè)subrequest來(lái)訪問(wèn)/sub,,echo指定類似shell里面里面的echo,用來(lái)輸出其后的字符串,,順便說(shuō)一下echo模塊還有其他很多的指令,,這個(gè)模塊在測(cè)試的時(shí)候非常有用。
在進(jìn)行源碼解析之前,,先來(lái)想想如果是我們自己要實(shí)現(xiàn)subrequest的上述行為,,該如何來(lái)做?subrequest還可能有自己的subrequest,,而且每個(gè)subrequest輸出數(shù)據(jù)都不一定是按照其創(chuàng)建的順序來(lái)的,,所以這里簡(jiǎn)單的采用鏈表來(lái)做是不好實(shí)現(xiàn)的,于是我們進(jìn)一步聯(lián)想到可以采用樹(shù)的結(jié)構(gòu)來(lái)做,,主請(qǐng)求即為根節(jié)點(diǎn),,每個(gè)節(jié)點(diǎn)可以有自己的子節(jié)點(diǎn),遍歷某節(jié)點(diǎn)表示處理某請(qǐng)求,,自然的可以想到這里可能是用后根(序)遍歷的方法,,沒(méi)錯(cuò),實(shí)際上Igor采用樹(shù)和鏈表結(jié)合的方式實(shí)現(xiàn)了subrequest的功能,,但是由于節(jié)點(diǎn)(請(qǐng)求)產(chǎn)生數(shù)據(jù)的順序不是固定按節(jié)點(diǎn)創(chuàng)建順序(左->右),,而且可能分多次產(chǎn)生數(shù)據(jù),不能簡(jiǎn)單的用后根(序)遍歷,。Igor使用了2個(gè)鏈表的結(jié)構(gòu)來(lái)實(shí)現(xiàn),,第一個(gè)是每個(gè)請(qǐng)求都有的postponed鏈表,一般情況下每個(gè)鏈表節(jié)點(diǎn)保存了該請(qǐng)求的一個(gè)子請(qǐng)求,,該鏈表節(jié)點(diǎn)定義如下:
- struct ngx_http_postponed_request_s {
- ngx_http_request_t *request;
- ngx_chain_t *out;
- ngx_http_postponed_request_t *next;
- };
可以看到它有一個(gè)request字段,,可以用來(lái)保存子請(qǐng)求,另外還有一個(gè)ngx_chain_t類型的out字段,,實(shí)際上一個(gè)請(qǐng)求的postponed鏈表里面除了保存子請(qǐng)求的節(jié)點(diǎn),,還有保存該請(qǐng)求自己產(chǎn)生的數(shù)據(jù)的節(jié)點(diǎn),數(shù)據(jù)保存在out字段,;第二個(gè)是posted_requests鏈表,,它掛載了當(dāng)前需要遍歷的請(qǐng)求(節(jié)點(diǎn)), 該鏈表保存在主請(qǐng)求(根節(jié)點(diǎn))的posted_requests字段,,鏈表節(jié)點(diǎn)定義如下:
- struct ngx_http_posted_request_s {
- ngx_http_request_t *request;
- ngx_http_posted_request_t *next;
- };
在ngx_http_run_posted_requests函數(shù)中會(huì)順序的遍歷主請(qǐng)求的posted_requests鏈表:
- void
- ngx_http_run_posted_requests(ngx_connection_t *c)
- {
- ...
- for ( ;; ) {
-
- if (c->destroyed) {
- return;
- }
-
- r = c->data;
-
- pr = r->main->posted_requests;
-
- if (pr == NULL) {
- return;
- }
- <pre name="code" class="cpp">
- r->main->posted_requests = pr->next;
-
- r = pr->request;
-
- ctx = c->log->data;
- ctx->current_request = r;
-
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
- "http posted request: \"%V?%V\"", &r->uri, &r->args);
-
- r->write_event_handler(r);
- }
- }
ngx_http_run_posted_requests函數(shù)的調(diào)用點(diǎn)我后面會(huì)做說(shuō)明。
OK,,了解了一些實(shí)現(xiàn)的原理,,來(lái)看代碼就簡(jiǎn)單多了,現(xiàn)在正式進(jìn)行subrequest的源碼解析, 首先來(lái)看一下創(chuàng)建subrequest的函數(shù)定義:
- ngx_int_t
- ngx_http_subrequest(ngx_http_request_t *r,
- ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
- ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
參數(shù)r為當(dāng)前的請(qǐng)求,,uri和args為新的要發(fā)起的uri和args,,當(dāng)然args可以為NULL,psr為指向一個(gè)ngx_http_request_t指針的指針,,它的作用就是獲得創(chuàng)建的子請(qǐng)求,,ps的類型為ngx_http_post_subrequest_t,它的定義如下:
- typedef struct {
- ngx_http_post_subrequest_pt handler;
- void *data;
- } ngx_http_post_subrequest_t;
-
- typedef ngx_int_t (*ngx_http_post_subrequest_pt)(ngx_http_request_t *r,
- void *data, ngx_int_t rc);
它就是之前說(shuō)到的回調(diào)handler,,結(jié)構(gòu)里面的handler類型為ngx_http_post_subrequest_pt,,它是函數(shù)指針,,data為傳遞給handler的額外參數(shù),。再來(lái)看一下ngx_http_subrequest函數(shù)的最后一個(gè)是flags,現(xiàn)在的源碼中實(shí)際上只有2種類型的flag,,分別為NGX_HTTP_SUBREQUEST_IN_MEMORY和NGX_HTTP_SUBREQUEST_WAITED,,第一個(gè)就是指定文章開(kāi)頭說(shuō)到的子請(qǐng)求的upstream處理數(shù)據(jù)的方式,第二個(gè)參數(shù)暫時(shí)不清楚它的具體作用,,源碼中它只對(duì)ssi模塊產(chǎn)生影響,,一般用不到。
進(jìn)入ngx_http_subrequest函數(shù)內(nèi)部看看:
- {
- ...
-
- sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
- sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;
-
- sr->unparsed_uri = r->unparsed_uri;
- sr->method_name = ngx_http_core_get_method;
- sr->http_protocol = r->http_protocol;
-
- ngx_http_set_exten(sr);
-
- sr->main = r->main;
-
- sr->parent = r;
-
- sr->post_subrequest = ps;
-
- sr->read_event_handler = ngx_http_request_empty_handler;
- sr->write_event_handler = ngx_http_handler;
-
-
- if (c->data == r && r->postponed == NULL) {
- c->data = sr;
- }
-
- sr->variables = r->variables;
-
- sr->log_handler = r->log_handler;
-
- pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
- if (pr == NULL) {
- return NGX_ERROR;
- }
-
- pr->request = sr;
- pr->out = NULL;
- pr->next = NULL;
-
- if (r->postponed) {
- for (p = r->postponed; p->next; p = p->next) { }
- p->next = pr;
-
- } else {
- r->postponed = pr;
- }
-
- sr->internal = 1;
-
- sr->discard_body = r->discard_body;
- sr->expect_tested = 1;
- sr->main_filter_need_in_memory = r->main_filter_need_in_memory;
-
- sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
-
- tp = ngx_timeofday();
- r->start_sec = tp->sec;
- r->start_msec = tp->msec;
-
- r->main->subrequests++;
-
- r->main->count++;
-
- *psr = sr;
-
- return ngx_http_post_request(sr, NULL);
- }
好了,,子請(qǐng)求創(chuàng)建完畢,一般來(lái)說(shuō)子請(qǐng)求的創(chuàng)建都發(fā)生在某個(gè)請(qǐng)求的content handler或者某個(gè)filter內(nèi),,從上面的函數(shù)可以看到子請(qǐng)求并沒(méi)有馬上被執(zhí)行,,只是被掛載在了主請(qǐng)求的posted_requests鏈表中,那它什么時(shí)候可以執(zhí)行呢,?之前說(shuō)到posted_requests鏈表是在ngx_http_run_posted_requests函數(shù)中遍歷,,那么ngx_http_run_posted_requests函數(shù)又是在什么時(shí)候調(diào)用?它實(shí)際上是在某個(gè)請(qǐng)求的讀(寫(xiě))事件的handler中,執(zhí)行完該請(qǐng)求相關(guān)的處理后被調(diào)用,,比如主請(qǐng)求在走完一遍PHASE的時(shí)候會(huì)調(diào)用ngx_http_run_posted_requests,,這時(shí)子請(qǐng)求得以運(yùn)行。
這時(shí)實(shí)際還有1個(gè)問(wèn)題需要解決,,由于nginx是多進(jìn)程,,是不能夠隨意阻塞的(如果一個(gè)請(qǐng)求阻塞了當(dāng)前進(jìn)程,就相當(dāng)于阻塞了這個(gè)進(jìn)程accept到的所有其他請(qǐng)求,,同時(shí)該進(jìn)程也不能accept新請(qǐng)求),,一個(gè)請(qǐng)求可能由于某些原因需要阻塞(比如訪問(wèn)io),nginx的做法是設(shè)置該請(qǐng)求的一些狀態(tài)并在epoll中添加相應(yīng)的事件,,然后轉(zhuǎn)去處理其他請(qǐng)求,,等到該事件到來(lái)時(shí)再繼續(xù)處理該請(qǐng)求,這樣的行為就意味著一個(gè)請(qǐng)求可能需要多次執(zhí)行機(jī)會(huì)才能完成,,對(duì)于一個(gè)請(qǐng)求的多個(gè)子請(qǐng)求來(lái)說(shuō),,意味著它們完成的先后順序可能和它們創(chuàng)建的順序是不一樣的,所以必須有一種機(jī)制讓提前完成的子請(qǐng)求保存它產(chǎn)生的數(shù)據(jù),,而不是直接輸出到out chain,,同時(shí)也能夠讓當(dāng)前能夠往out chain輸出數(shù)據(jù)的請(qǐng)求及時(shí)的輸出產(chǎn)生的數(shù)據(jù)。作者Igor采用ngx_connection_t中的data字段,,以及一個(gè)body filter,,即ngx_http_postpone_filter,還有ngx_http_finalize_request函數(shù)中的一些邏輯來(lái)解決這個(gè)問(wèn)題,。
下面我用一個(gè)圖來(lái)做說(shuō)明,,下圖是某時(shí)刻某個(gè)主請(qǐng)求和它的所有子孫請(qǐng)求的樹(shù)結(jié)構(gòu):
圖中的root節(jié)點(diǎn)即為主請(qǐng)求,它的postponed鏈表從左至右掛載了3個(gè)節(jié)點(diǎn),,SUB1是它的第一個(gè)子請(qǐng)求,,DATA1是它產(chǎn)生的一段數(shù)據(jù),SUB2是它的第2個(gè)子請(qǐng)求,,而且這2個(gè)子請(qǐng)求分別有它們自己的子請(qǐng)求及數(shù)據(jù),。ngx_connection_t中的data字段保存的是當(dāng)前可以往out chain發(fā)送數(shù)據(jù)的請(qǐng)求,文章開(kāi)頭說(shuō)到發(fā)到客戶端的數(shù)據(jù)必須按照子請(qǐng)求創(chuàng)建的順序發(fā)送,,這里即是按后續(xù)遍歷的方法(SUB11->DATA11->SUB12->DATA12->(SUB1)->DATA1->SUB21->SUB22->(SUB2)->(ROOT)),,上圖中當(dāng)前能夠往客戶端(out chain)發(fā)送數(shù)據(jù)的請(qǐng)求顯然就是SUB11,如果SUB12提前執(zhí)行完成,,并產(chǎn)生數(shù)據(jù)DATA121,,只要前面它還有節(jié)點(diǎn)未發(fā)送完畢,DATA121只能先掛載在SUB12的postponed鏈表下,。這里還要注意一下的是c->data的設(shè)置,,當(dāng)SUB11執(zhí)行完并且發(fā)送完數(shù)據(jù)之后,,下一個(gè)將要發(fā)送的節(jié)點(diǎn)應(yīng)該是DATA11,但是該節(jié)點(diǎn)實(shí)際上保存的是數(shù)據(jù),,而不是子請(qǐng)求,,所以c->data這時(shí)應(yīng)該指向的是擁有改數(shù)據(jù)節(jié)點(diǎn)的SUB1請(qǐng)求。
下面看下源碼具體是怎樣實(shí)現(xiàn)的,,首先是ngx_http_postpone_filter函數(shù):
- static ngx_int_t
- ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in)
- {
- ...
-
- if (r != c->data) {
-
- if (in) {
- ngx_http_postpone_filter_add(r, in);
- return NGX_OK;
- }
- ...
- return NGX_OK;
- }
-
-
- if (r->postponed == NULL) {
-
- if (in || c->buffered) {
- return ngx_http_next_filter(r->main, in);
- }
-
- return NGX_OK;
- }
-
- if (in) {
- ngx_http_postpone_filter_add(r, in);
- }
-
- do {
- pr = r->postponed;
-
- if (pr->request) {
-
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
- "http postpone filter wake \"%V?%V\"",
- &pr->request->uri, &pr->request->args);
-
- r->postponed = pr->next;
-
-
-
- c->data = pr->request;
-
- return ngx_http_post_request(pr->request, NULL);
- }
-
- if (pr->out == NULL) {
- ngx_log_error(NGX_LOG_ALERT, c->log, 0,
- "http postpone filter NULL output",
- &r->uri, &r->args);
-
- } else {
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
- "http postpone filter output \"%V?%V\"",
- &r->uri, &r->args);
-
- if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {
- return NGX_ERROR;
- }
- }
-
- r->postponed = pr->next;
-
- } while (r->postponed);
-
- return NGX_OK;
- }
再來(lái)看ngx_http_finalzie_request函數(shù):
- void
- ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
- {
- ...
-
- if (r != r->main && r->post_subrequest) {
- rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
- }
-
- ...
-
-
- if (r != r->main) {
-
- if (r->buffered || r->postponed) {
-
-
- if (ngx_http_set_write_handler(r) != NGX_OK) {
- ngx_http_terminate_request(r, 0);
- }
-
- return;
- }
- ...
-
- pr = r->parent;
-
-
-
- if (r == c->data) {
-
- r->main->count--;
-
- if (!r->logged) {
-
- clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-
- if (clcf->log_subrequest) {
- ngx_http_log_request(r);
- }
-
- r->logged = 1;
-
- } else {
- ngx_log_error(NGX_LOG_ALERT, c->log, 0,
- "subrequest: \"%V?%V\" logged again",
- &r->uri, &r->args);
- }
-
- r->done = 1;
-
- if (pr->postponed && pr->postponed->request == r) {
- pr->postponed = pr->postponed->next;
- }
-
- c->data = pr;
-
- } else {
-
-
-
- r->write_event_handler = ngx_http_request_finalizer;
-
- if (r->waited) {
- r->done = 1;
- }
- }
-
- if (ngx_http_post_request(pr, NULL) != NGX_OK) {
- r->main->count++;
- ngx_http_terminate_request(r, 0);
- return;
- }
-
- return;
- }
-
-
- if (r->buffered || c->buffered || r->postponed || r->blocked) {
-
- if (ngx_http_set_write_handler(r) != NGX_OK) {
- ngx_http_terminate_request(r, 0);
- }
-
- return;
- }
-
- ...
- }
總結(jié)一下,,nginx的subrequest的代碼實(shí)現(xiàn)還是稍有些難懂,但是如果先了解了它的原理思想,,再看代碼就不難理解了,。