Nginx中upstream模塊的具體用法
目錄
- upstream模塊簡介
- upstream模塊接口
- memcached模塊分析
- 小結(jié)
upstream模塊簡介
- nginx模塊一般被分成三大類:handler、filter和upstream。前面的章節(jié)中,讀者已經(jīng)了解了handler、filter。利用這兩類模塊,可以使nginx輕松完成任何單機工作。
- 而upstream模塊,將使nginx跨越單機的限制,完成網(wǎng)絡(luò)數(shù)據(jù)的接收、處理和轉(zhuǎn)發(fā)。
- 數(shù)據(jù)轉(zhuǎn)發(fā)功能,為nginx提供了跨越單機的橫向處理能力,使nginx擺脫只能為終端節(jié)點提供單一功能的限制,使它具備了網(wǎng)絡(luò)應(yīng)用級別的拆分、封裝和整合的功能。
- 數(shù)據(jù)轉(zhuǎn)發(fā)是nginx有能力構(gòu)建一個網(wǎng)絡(luò)應(yīng)用的關(guān)鍵組件。當(dāng)然,鑒于開發(fā)成本的問題,一個網(wǎng)絡(luò)應(yīng)用的關(guān)鍵組件一開始往往會采用高級編程語言開發(fā)。但是當(dāng)系統(tǒng)到達一定規(guī)模,并且需要更重視性能的時候,為了達到所要求的性能目標(biāo),高級語言開發(fā)出的組件必須進行結(jié)構(gòu)化修改。
此時,對于修改代價而言,nginx的upstream模塊體現(xiàn)出了它的優(yōu)勢,因為它天生就快。作為附帶,nginx的配置系統(tǒng)提供的層次化和松耦合使得系統(tǒng)的擴展性也達到比較高的程度。
upstream模塊接口
從本質(zhì)上說,upstream屬于handler,只是他不產(chǎn)生自己的內(nèi)容,而是通過請求后端服務(wù)器得到內(nèi)容,所以才稱為upstream(上游)。請求并取得響應(yīng)內(nèi)容的整個過程已經(jīng)被封裝到nginx內(nèi)部,所以upstream模塊只需要開發(fā)若干回調(diào)函數(shù),完成構(gòu)造請求和解析響應(yīng)等具體的工作。
upstream模塊回調(diào)函數(shù)列舉如下:
memcached模塊分析
- memcache是一款高性能的分布式cache系統(tǒng),得到了非常廣泛的應(yīng)用。memcache定義了一套私有通信協(xié)議,使得不能通過HTTP請求來訪問memcache。但協(xié)議本身簡單高效,而且memcache使用廣泛,所以大部分現(xiàn)代開發(fā)語言和平臺都提供了memcache支持,方便開發(fā)者使用memcache。
- nginx提供了ngx_http_memcached模塊,提供從memcache讀取數(shù)據(jù)的功能,而不提供向memcache寫數(shù)據(jù)的功能。
upstream模塊使用的就是handler模塊的接入方式。
同時,upstream模塊的指令系統(tǒng)的設(shè)計也是遵循h(huán)andler模塊的基本規(guī)則:配置該模塊才會執(zhí)行該模塊。
那么,upstream模塊的特別之處究竟在哪里呢?那就是upstream模塊的處理函數(shù),upstream模塊的處理函數(shù)進行的操作都包含一個固定的流程:(以memcached模塊舉例,在memcached的處理函數(shù)ngx_http_memcached_handler中)
創(chuàng)建upstream數(shù)據(jù)結(jié)構(gòu):
ngx_http_upstream_t *u; if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } u = r->upstream;
設(shè)置模塊的tag和schema。schema現(xiàn)在只會用于日志,tag會用于buf_chain管理:
ngx_str_set(&u->schema, "memcached://"); u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
設(shè)置upstream的后端服務(wù)器列表數(shù)據(jù)結(jié)構(gòu):
mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module); u->conf = &mlcf->upstream;
設(shè)置upstream回調(diào)函數(shù):
u->create_request = ngx_http_memcached_create_request; u->reinit_request = ngx_http_memcached_reinit_request; u->process_header = ngx_http_memcached_process_header; u->abort_request = ngx_http_memcached_abort_request; u->finalize_request = ngx_http_memcached_finalize_request; u->input_filter_init = ngx_http_memcached_filter_init; u->input_filter = ngx_http_memcached_filter;
創(chuàng)建并設(shè)置upstream環(huán)境數(shù)據(jù)結(jié)構(gòu):
ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t)); if (ctx == NULL) { ? ? return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx->request = r; ngx_http_set_ctx(r, ctx, ngx_http_memcached_module); u->input_filter_ctx = ctx;
完成upstream初始化并進行收尾工作:
r->main->count++; ngx_http_upstream_init(r); return NGX_DONE;
任何upstream模塊,簡單如memcached,復(fù)雜如proxy、fastcgi都是如此。
不同的upstream模塊在這6步中的最大差別會出現(xiàn)在第2、3、4、5上。
其中第2、4兩步很容易理解,不同的模塊設(shè)置的標(biāo)志和使用的回調(diào)函數(shù)肯定不同。第5步也不難理解。
只有第3步是有點費解的,不同的模塊在取得后端服務(wù)器列表時,策略的差異非常大,有如memcached這樣簡單明了的,也有如proxy那樣邏輯復(fù)雜的。
第6步不同模塊之間通常是一致的。將count加1,然后返回NGX_DONE。
nginx遇到這種情況,雖然會認為當(dāng)前請求的處理已經(jīng)結(jié)束,但是不會釋放請求使用的內(nèi)存資源,也不會關(guān)閉與客戶端的連接。
之所以需要這樣,是因為nginx建立了upstream請求和客戶端請求之間一對一的關(guān)系,在后續(xù)使用ngx_event_pipe將upstream響應(yīng)發(fā)送回客戶端時,還要使用到這些保存著客戶端信息的數(shù)據(jù)結(jié)構(gòu)。
將upstream請求和客戶端請求進行一對一綁定,這個設(shè)計有優(yōu)勢也有缺陷。優(yōu)勢就是簡化模塊開發(fā),可以將精力集中在模塊邏輯上,而缺陷同樣明顯,一對一的設(shè)計很多時候都不能滿足復(fù)雜邏輯的需要。
回調(diào)函數(shù):(依然是以memcached模塊的處理函數(shù)為例)
- ngx_http_memcached_create_request:很簡單的按照設(shè)置的內(nèi)容生成一個key,接著生成一個“get $key”的請求,放在r->upstream->request_bufs里面。
- ngx_http_memcached_reinit_request:無需初始化。
- ngx_http_memcached_abort_request:無需額外操作。
- ngx_http_memcached_finalize_request:無需額外操作。
- ngx_http_memcached_process_header:模塊的業(yè)務(wù)重點函數(shù)。memcache協(xié)議的頭部信息被定義為第一行文本,代碼如下:
#define LF (u_char) "\n" for (p = u->buffer.pos; p < u->buffer.last; p++) { if (*p == LF) { goto found; } }
如果在已讀入緩沖的數(shù)據(jù)中沒有發(fā)現(xiàn)LF(‘\n’)字符,函數(shù)返回NGX_AGAIN,表示頭部未完全讀入,需要繼續(xù)讀取數(shù)據(jù)。nginx在收到新的數(shù)據(jù)以后會再次調(diào)用該函數(shù)。
nginx處理后端服務(wù)器的響應(yīng)頭時只會使用一塊緩存,所有數(shù)據(jù)都在這塊緩存中,所以解析頭部信息時不需要考慮頭部信息跨越多塊緩存的情況。而如果頭部過大,不能保存在這塊緩存中,nginx會返回錯誤信息給客戶端,并記錄error log,提示緩存不夠大。
ngx_http_memcached_process_header的重要職責(zé)是將后端服務(wù)器返回的狀態(tài)翻譯成返回給客戶端的狀態(tài)。例如:
u->headers_in.content_length_n = ngx_atoof(start, p - start); ··· u->headers_in.status_n = 200; u->state->status = 200; ··· u->headers_in.status_n = 404; u->state->status = 404;
u->state用于計算upstream相關(guān)的變量。比如u->state->status將被用于計算變量“upstream_status”的值。u->headers_in將被作為返回給客戶端的響應(yīng)返回狀態(tài)碼。而u->headers_in.content_length_n則是設(shè)置返回給客戶端的響應(yīng)的長度。
在這個函數(shù)中一定要在處理完頭部信息以后需要將讀指針pos后移,否則這段數(shù)據(jù)也將被復(fù)制到返回給客戶端的響應(yīng)的正文中,進而導(dǎo)致正文內(nèi)容不正確。
ngx_http_memcached_process_header函數(shù)完成響應(yīng)頭的正確處理,應(yīng)該返回NGX_OK。如果返回NGX_AGAIN,表示未讀取完整數(shù)據(jù),需要從后端服務(wù)器繼續(xù)讀取數(shù)據(jù)。返回NGX_DECLINED無意義,其他任何返回值都被認為是出錯狀態(tài),nginx將結(jié)束upstream請求并返回錯誤信息。
ngx_http_memcached_filter_init:修正從后端服務(wù)器收到的內(nèi)容長度。因為在處理header時沒有加上這部分長度。
ngx_http_memcached_filter:
memcached模塊是少有的帶有處理正文的回調(diào)函數(shù)的模塊。
因為memcached模塊需要過濾正文末尾CRLF “END” CRLF,所以實現(xiàn)了自己的filter回調(diào)函數(shù)。
處理正文的實際意義是將從后端服務(wù)器收到的正文有效內(nèi)容封裝成ngx_chain_t,并加在u->out_bufs末尾。
nginx并不進行數(shù)據(jù)拷貝,而是建立ngx_buf_t數(shù)據(jù)結(jié)構(gòu)指向這些數(shù)據(jù)內(nèi)存區(qū),然后由ngx_chain_t組織這些buf。這種實現(xiàn)避免了內(nèi)存大量搬遷,也是nginx高效的原因之一。
小結(jié)
- 以上就是upstream模塊的基本組成。upstream模塊是從handler模塊發(fā)展而來,指令系統(tǒng)和模塊生效方式與handler模塊無異。
- 不同之處在于,upstream模塊在handler函數(shù)中設(shè)置眾多回調(diào)函數(shù)。實際工作都是由這些回調(diào)函數(shù)完成的。
- 每個回調(diào)函數(shù)都是在upstream的某個固定階段執(zhí)行,各司其職,大部分回調(diào)函數(shù)一般不會真正用到。
- upstream最重要的回調(diào)函數(shù)是ngx_http_upstream_t->create_request、ngx_http_upstream_t->process_header和ngx_http_upstream_t->input_filter,他們共同實現(xiàn)了與后端服務(wù)器的協(xié)議的解析部分。
到此這篇關(guān)于Nginx中upstream模塊的具體用法的文章就介紹到這了,更多相關(guān)Nginx upstream模塊內(nèi)容請搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!
