首頁 > 軟體

Nginx中upstream模組的具體用法

2023-04-04 06:01:53

upstream模組簡介

  • nginx模組一般被分成三大類:handler、filter和upstream。前面的章節中,讀者已經瞭解了handler、filter。利用這兩類模組,可以使nginx輕鬆完成任何單機工作。
  • 而upstream模組,將使nginx跨越單機的限制,完成網路資料的接收、處理和轉發。
  • 資料轉發功能,為nginx提供了跨越單機的橫向處理能力,使nginx擺脫只能為終端節點提供單一功能的限制,使它具備了網路應用級別的拆分、封裝和整合的功能。
  • 資料轉發是nginx有能力構建一個網路應用的關鍵元件。當然,鑑於開發成本的問題,一個網路應用的關鍵元件一開始往往會採用高階程式語言開發。但是當系統到達一定規模,並且需要更重視效能的時候,為了達到所要求的效能目標,高階語言開發出的元件必須進行結構化修改。

此時,對於修改代價而言,nginx的upstream模組體現出了它的優勢,因為它天生就快。作為附帶,nginx的設定系統提供的層次化和鬆耦合使得系統的擴充套件性也達到比較高的程度。

upstream模組介面

從本質上說,upstream屬於handler,只是他不產生自己的內容,而是通過請求後端伺服器得到內容,所以才稱為upstream(上游)。請求並取得響應內容的整個過程已經被封裝到nginx內部,所以upstream模組只需要開發若干回撥函數,完成構造請求和解析響應等具體的工作。

upstream模組回撥函數列舉如下:

函數名稱描述
create_request生成傳送到後端伺服器的請求緩衝(緩衝鏈),在初始化upstream 時使用
reinit_request在某臺後端伺服器出錯的情況,nginx會嘗試另一臺後端伺服器。 nginx選定新的伺服器以後,會先呼叫此函數,以重新初始化 upstream模組的工作狀態,然後再次進行upstream連線
process_header處理後端伺服器返回的資訊頭部。所謂頭部是與upstream server 通訊的協定規定的,比如HTTP協定的header部分,或者memcached 協定的響應狀態部分
abort_request在使用者端放棄請求時被呼叫。不需要在函數中實現關閉後端服務 器連線的功能,系統會自動完成關閉連線的步驟,所以一般此函 數不會進行任何具體工作
finalize_request正常完成與後端伺服器的請求後呼叫該函數,與abort_request 相同,一般也不會進行任何具體工作
input_filter處理後端伺服器返回的響應正文。nginx預設的input_filter會 將收到的內容封裝成為緩衝區鏈ngx_chain。該鏈由upstream的 out_bufs指標域定位,所以開發人員可以在模組以外通過該指標得到後端伺服器返回的正文資料。memcached模組實現了自己的 input_filter,在後面會具體分析這個模組。
input_filter_init初始化input filter的上下文。nginx預設的input_filter_init 直接返回

memcached模組分析

  • memcache是一款高效能的分散式cache系統,得到了非常廣泛的應用。memcache定義了一套私有通訊協定,使得不能通過HTTP請求來存取memcache。但協定本身簡單高效,而且memcache使用廣泛,所以大部分現代開發語言和平臺都提供了memcache支援,方便開發者使用memcache。
  • nginx提供了ngx_http_memcached模組,提供從memcache讀取資料的功能,而不提供向memcache寫資料的功能。

upstream模組使用的就是handler模組的接入方式。

同時,upstream模組的指令系統的設計也是遵循handler模組的基本規則:設定該模組才會執行該模組。

那麼,upstream模組的特別之處究竟在哪裡呢?那就是upstream模組的處理常式,upstream模組的處理常式進行的操作都包含一個固定的流程:(以memcached模組舉例,在memcached的處理常式ngx_http_memcached_handler中)

建立upstream資料結構:

ngx_http_upstream_t            *u;
if (ngx_http_upstream_create(r) != NGX_OK) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
u = r->upstream;

設定模組的tag和schema。schema現在只會用於紀錄檔,tag會用於buf_chain管理:

ngx_str_set(&u->schema, "memcached://");
u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;

設定upstream的後端伺服器列表資料結構:

mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
u->conf = &mlcf->upstream;

設定upstream回撥函數:

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;

建立並設定upstream環境資料結構:

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,複雜如proxy、fastcgi都是如此。
不同的upstream模組在這6步中的最大差別會出現在第2、3、4、5上。

其中第2、4兩步很容易理解,不同的模組設定的標誌和使用的回撥函數肯定不同。第5步也不難理解。

只有第3步是有點費解的,不同的模組在取得後端伺服器列表時,策略的差異非常大,有如memcached這樣簡單明瞭的,也有如proxy那樣邏輯複雜的。

第6步不同模組之間通常是一致的。將count加1,然後返回NGX_DONE。
nginx遇到這種情況,雖然會認為當前請求的處理已經結束,但是不會釋放請求使用的記憶體資源,也不會關閉與使用者端的連線。
之所以需要這樣,是因為nginx建立了upstream請求和使用者端請求之間一對一的關係,在後續使用ngx_event_pipe將upstream響應傳送回使用者端時,還要使用到這些儲存著使用者端資訊的資料結構。
將upstream請求和使用者端請求進行一對一系結,這個設計有優勢也有缺陷。優勢就是簡化模組開發,可以將精力集中在模組邏輯上,而缺陷同樣明顯,一對一的設計很多時候都不能滿足複雜邏輯的需要。

回撥函數:(依然是以memcached模組的處理常式為例)

  • ngx_http_memcached_create_request:很簡單的按照設定的內容生成一個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:模組的業務重點函數。memcache協定的頭部資訊被定義為第一行文字,程式碼如下:
#define LF     (u_char) 'n'
for (p = u->buffer.pos; p < u->buffer.last; p++) {
    if (*p == LF) {
        goto found;
    }
}

如果在已讀入緩衝的資料中沒有發現LF(‘n’)字元,函數返回NGX_AGAIN,表示頭部未完全讀入,需要繼續讀取資料。nginx在收到新的資料以後會再次呼叫該函數。

nginx處理後端伺服器的響應頭時只會使用一塊快取,所有資料都在這塊快取中,所以解析頭部資訊時不需要考慮頭部資訊跨越多塊快取的情況。而如果頭部過大,不能儲存在這塊快取中,nginx會返回錯誤資訊給使用者端,並記錄error log,提示快取不夠大。

ngx_http_memcached_process_header的重要職責是將後端伺服器返回的狀態翻譯成返回給使用者端的狀態。例如:

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相關的變數。比如u->state->status將被用於計算變數“upstream_status”的值。u->headers_in將被作為返回給使用者端的響應返回狀態碼。而u->headers_in.content_length_n則是設定返回給使用者端的響應的長度。

在這個函數中一定要在處理完頭部資訊以後需要將讀指標pos後移,否則這段資料也將被複制到返回給使用者端的響應的正文中,進而導致正文內容不正確。

ngx_http_memcached_process_header函數完成響應頭的正確處理,應該返回NGX_OK。如果返回NGX_AGAIN,表示未讀取完整資料,需要從後端伺服器繼續讀取資料。返回NGX_DECLINED無意義,其他任何返回值都被認為是出錯狀態,nginx將結束upstream請求並返回錯誤資訊。

ngx_http_memcached_filter_init:修正從後端伺服器收到的內容長度。因為在處理header時沒有加上這部分長度。

ngx_http_memcached_filter:
memcached模組是少有的帶有處理正文的回撥函數的模組。
因為memcached模組需要過濾正文末尾CRLF “END” CRLF,所以實現了自己的filter回撥函數。

處理正文的實際意義是將從後端伺服器收到的正文有效內容封裝成ngx_chain_t,並加在u->out_bufs末尾。

nginx並不進行資料拷貝,而是建立ngx_buf_t資料結構指向這些資料記憶體區,然後由ngx_chain_t組織這些buf。這種實現避免了記憶體大量搬遷,也是nginx高效的原因之一。

小結

  • 以上就是upstream模組的基本組成。upstream模組是從handler模組發展而來,指令系統和模組生效方式與handler模組無異。
  • 不同之處在於,upstream模組在handler函數中設定眾多回撥函數。實際工作都是由這些回撥函數完成的。
  • 每個回撥函數都是在upstream的某個固定階段執行,各司其職,大部分回撥函數一般不會真正用到。
  • upstream最重要的回撥函數是ngx_http_upstream_t->create_request、ngx_http_upstream_t->process_header和ngx_http_upstream_t->input_filter,他們共同實現了與後端伺服器的協定的解析部分。

到此這篇關於Nginx中upstream模組的具體用法的文章就介紹到這了,更多相關Nginx upstream模組內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com