### [upstream]()機制
Nginx提供的upstream機制,是nginx設計理念的忠實體現。異步、無阻塞,這是nginx的追求,任何對這種設計思想的違反,都會導致nginx達不到它預期的性能,包括nginx提供的fastCGI也是如此。
?
Upstream到底用來干什么呢?就是nginx在正常的請求處理過程中,需要訪問其他SERVER,這時,nginx提供了這樣的機制,把底層的http通訊全部做完。最重要的是,upstream保證了在這個請求中對其他SERVER的通訊,完全是無阻塞和異步的。個人認為,如果nginx沒有提供upstream,當開發者遇到這種情形要么自己寫一套多路復用IO處理機制來做,要么放棄異步去調用connection里的同步方法,就不能稱為真正的高性能異步WEB SERVER了。
?
Upstream實現得其實非常狹隘,因為nginx試圖把upstream做成一種proxy,也就是說nginx會對其他SERVER訪問后,對client的返回也接管,這個很惡心。如果你不需要這個功能,需要在ngx_http_upstream_process_header里,調用你的module處理response后就返回,不要繼續向下執行。
?
Upstream有六個需要module developer實現的方法,分別是:
???ngx_int_t?????????????????????(*create_request)(ngx_http_request_t *r);
???ngx_int_t?????????????????????(*reinit_request)(ngx_http_request_t *r);
???ngx_int_t?????????????????????(*process_header)(ngx_http_request_t *r);
??? void??????????????????????????(*abort_request)(ngx_http_request_t *r);
??? void??????????????????????????(*finalize_request)(ngx_http_request_t *r,
????????????????????????????????????????ngx_int_t rc);
???ngx_int_t?????????????????????(*rewrite_redirect)(ngx_http_request_t *r,
????????????????????????????????????????ngx_table_elt_t *h, size_t prefix);
?
upstream在真正實現HTTP通訊時會調用到這些函數。那么upstream的是如何進行的?首先,我們需要調用ngx_http_upstream_init來開始upstream之旅了。
?
ngx_http_upstream_init首先會去調用create_request函數,這時開發者可以在這里把HTTP請求構造好。構造完請求包后,nginx會去連接remote server,這又是一個異步事件,所以需要注冊回調函數為ngx_http_upstream_handler,也就是說,當連接成功建立后,nginx的epoll會調用ngx_http_upstream_handler來處理建立好的這個連接,同時把發送HTTP請求的處理函數注冊為ngx_http_upstream_send_request_handler。
?
現在與remote server建立好連接了,ngx_http_upstream_handle調用ngx_http_upstream_send_request_handler來發送create_request完成的HTTP請求包了。這里實際完成發送請求任務的是ngx_http_upstream_send_request方法,而ngx_http_upstream_send_request又是調用ngx_output_chain來把請求發送出去。成功以后,開始等待讀事件的來臨,如果有數據返回,則調用ngx_http_upstream_process_header來處理remote server的response了,等到確認接收到完整的response后,會調用我們實現的process_header注冊函數來處理response。upstream處理完后會調用注冊的finalize_request函數來清理開發者需要做的工作。
?
### [內存使用]()
Nginx給用戶提供了內存池功能,所以developer在使用時,應當盡量避免繞過nginx內存池來操作內存。
?
這里不去分析nginx內存池的實現,只簡要的說明如何使用它。
申請一塊內存時,必須先拿到內存池的指針,然后傳入內存塊大小,nginx實際上會移動指針指向內存池中的空閑內存,如果失敗則返回NULL,內存池大小有限且可配,所以我們必須每次申請內存都要檢查是否申請成功。
我們看下最簡單的一個分配buf函數:
~~~
ngx_buf_t *
ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
??? ngx_buf_t*b;
??? b =ngx_calloc_buf(pool);
??? if (b ==NULL) {
??????? returnNULL;
??? }
??? b->start= ngx_palloc(pool, size);
??? if(b->start == NULL) {
??????? returnNULL;
??? }
??? b->pos =b->start;
??? b->last= b->start;
??? b->end =b->last + size;
???b->temporary = 1;
??? return b;
}
~~~
可以看到,很簡單的從內存池中申請到,釋放則由pool自動進行,很好的垃圾回收機制。
### [配置文件的使用]()
在ngx_command_t中,定義好需要讀取的配置項名稱,以及處理該配置項的方法,這樣就可以nginx.conf里放置相應的配置項,在module里使用。
?
例如:
在nginx.conf里加入下行配置:
dmsargname dcname filesize blobid;
?
則需要在module里的ngx_command_t數組里分配如下:
~~~
???????? {
????????????????? ngx_string("dmsargname"),
???????? NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1234,
????????????????? ngx_read_dmsargname,
????????????????? NGX_HTTP_LOC_CONF_OFFSET,
????????????????? offsetof(ngx_webex_DMD_loc_conf_t,argnameFromDC),
????????????????? NULL
???????? },?????
~~~
實際的處理函數為ngx_read_dmsargname。NGX_CONF_TAKE1234意為最多讀取四個參數。
?
讀取參數函數簡易實現:
~~~
static char * /* {{{ ngx_read_datastore */
ngx_read_dmsargname(ngx_conf_t *cf, ngx_command_t*cmd, void *conf)
{
???????? ngx_http_core_loc_conf_t???*clcf;
???????? ngx_webex_DMD_loc_conf_t?*ulcf = conf;
???????? ngx_str_t??????????????????*value;
???????? value =cf->args->elts;
???????? clcf =ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
???????? ulcf->argnameFromDC= value[1];
???????? ulcf->argnameFilesize= value[2];
???????? ulcf->argnameBlobID= value[3];
??? returnNGX_CONF_OK;
}
~~~
?
### [Nginx]()封裝的常用數據結構
這個必須要說下,因為處理http請求時,必須處理head,無論是讀取head還是插入head,都需要和ngx_list_t這個數據結構打交道,nginx框架是把head都放到ngx_list_t??????????????????????headers;數據結構里的。
?
list一共有三種操作:
ngx_list_create();//創建及初始化隊列
ngx_list_init();???????//初始化隊列
ngx_list_push();//找到下一個插入位置的指針并返回
很明顯,設計者為了靈活性,沒有提供遍歷方法,那么首先,應該怎么遍歷它?比如,拿到一個request后,最應該先分析head了。
?
畫個圖看看:
ngx_list_t
<table class="MsoTableGrid" border="1" cellspacing="0" cellpadding="0"><tbody><tr><td width="142" valign="top"><p class="MsoNormal"><span lang="EN-US">pool</span></p></td><td width="142" valign="top"><p class="MsoNormal"><span lang="EN-US">nalloc</span></p></td><td width="142" valign="top"><p class="MsoNormal"><span lang="EN-US">size</span></p></td><td width="142" valign="top"><p class="MsoNormal"><span lang="EN-US">part</span></p></td><td width="142" valign="top"><p class="MsoNormal"><span lang="EN-US">last</span></p></td></tr></tbody></table>
<!--[if gte vml 1]> <![endif]-->
ngx_list_part_s
<table class="MsoTableGrid" border="1" cellspacing="0" cellpadding="0"><tbody><tr><td width="237" valign="top"><p class="MsoNormal"><span lang="EN-US">nelts</span></p></td><td width="237" valign="top"><p class="MsoNormal"><span lang="EN-US">elts</span></p></td><td width="237" valign="top"><p class="MsoNormal"><span lang="EN-US">next</span></p></td></tr></tbody></table>
<!--[if gte vml 1]> <![endif]-->
ngx_table_elt_t
<table class="MsoTableGrid" border="1" cellspacing="0" cellpadding="0"><tbody><tr><td width="178" valign="top"><p class="MsoNormal"><span lang="EN-US">Key</span></p></td><td width="178" valign="top"><p class="MsoNormal"><span lang="EN-US">Value</span></p></td><td width="178" valign="top"><p class="MsoNormal"><span lang="EN-US">Hash</span></p></td><td width="178" valign="top"><p class="MsoNormal"><span lang="EN-US">lowcase</span></p></td></tr></tbody></table>
?
[圖表]()<!--[if supportFields]>?SEQ圖表 /* ARABIC<![endif]-->5<!--[if supportFields]><![endif]-->-nginx封裝的list結構
?
當然ngx_table_elt_t結構只是head里的ngx_list_t所用,實際上每個元素可以是任意類型,由elts指針所指。
?
ngx_list_t里的nalloc和size,分別表示元素桶的數量和每個元素的大小,所以這里大家可以看出,該數據結構是定長內存組成的了吧。元素桶這個概念,大家可以理解為ngx_list_part_s結構,這個結構里可以保存多個元素,元素數量由nelts決定。每個元素是由ngx_list_part_s->elts指向的,長度為ngx_list_t->size
?
給段遍歷list取得每個head的代碼看看:
~~~
???????? part =&r->upstream->headers_in.headers.part;
???????? header= part->elts;
???????? for (i= 0; /* void */; i++) {
???????? ??????? if (i >= part->nelts) {
???????? ??????????? if (part->next == NULL) {
???????? ???????????????break;
???????? ??????????? }
???????? ??????????? part = part->next;
???????? ??????????? header = part->elts;
???????? ??????????? i = 0;
???????? ??????? }
????????????????? if(header[i].hash == 0) {
???????? ??????????? continue;
???????? ??????? }
????????????????? //header[i].key就是head的名字,header[i].value就是head的值了
}
~~~
?
[總結]
開發nginx module,可以非常靈活的實現自己需要的功能。很多時候我們需要修改源碼才能實現自己的特定功能,比如,nginx的thumbnail resize功能,不支持實時的對每一個請求按照指定的size來壓縮,實際上在它的代碼中完全可以做到,只需要稍微修改下源碼而已。
又如upstream,nginx把它當足是個proxy機制,如果我們想讓它只做為異步網絡調用,也只需要在upstream_process_header里做些改動。
?
在需要某些特殊的功能時,我們最應該首先閱讀nginx的源碼,通常都會發現很多意想不到的收獲。
?
Nginx的性能確實不錯,對http協議的處理也很高效,在我們需要高性能webserver時,應該去優先考慮它。