Nginx upstream 模塊簡介

2018-08-23 21:03 更新

upstream 模塊簡介

Nginx 模塊一般被分成三大類:handler、filter 和 upstream。前面的章節(jié)中,讀者已經(jīng)了解了 handler、filter。利用這兩類模塊,可以使 Nginx 輕松完成任何單機(jī)工作。而本章介紹的 upstream 模塊,將使 Nginx 跨越單機(jī)的限制,完成網(wǎng)絡(luò)數(shù)據(jù)的接收、處理和轉(zhuǎn)發(fā)。

數(shù)據(jù)轉(zhuǎn)發(fā)功能,為 Nginx 提供了跨越單機(jī)的橫向處理能力,使 Nginx 擺脫只能為終端節(jié)點(diǎn)提供單一功能的限制,而使它具備了網(wǎng)路應(yīng)用級別的拆分、封裝和整合的戰(zhàn)略功能。在云模型大行其道的今天,數(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)到達(dá)一定規(guī)模,并且需要更重視性能的時候,為了達(dá)到所要求的性能目標(biāo),高級語言開發(fā)出的組件必須進(jìn)行結(jié)構(gòu)化修改。此時,對于修改代價(jià)而言,Nginx 的 upstream 模塊呈現(xiàn)出極大的吸引力,因?yàn)樗焐涂?。作為附帶,Nginx 的配置系統(tǒng)提供的層次化和松耦合使得系統(tǒng)的擴(kuò)展性也達(dá)到比較高的程度。

言歸正傳,下面介紹 upstream 的寫法。

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)等具體的工作。

這些回調(diào)函數(shù)如下表所示:

SN描述
create_request生成發(fā)送到后端服務(wù)器的請求緩沖(緩沖鏈),在初始化 upstream 時使用。
reinit_request在某臺后端服務(wù)器出錯的情況,Nginx會嘗試另一臺后端服務(wù)器。Nginx 選定新的服務(wù)器以后,會先調(diào)用此函數(shù),以重新初始化 upstream 模塊的工作狀態(tài),然后再次進(jìn)行 upstream 連接。
process_header處理后端服務(wù)器返回的信息頭部。所謂頭部是與 upstreamserver 通信的協(xié)議規(guī)定的,比如 HTTP 協(xié)議的 header 部分,或者 memcached 協(xié)議的響應(yīng)狀態(tài)部分。
abort_request在客戶端放棄請求時被調(diào)用。不需要在函數(shù)中實(shí)現(xiàn)關(guān)閉后端服務(wù)器連接的功能,系統(tǒng)會自動完成關(guān)閉連接的步驟,所以一般此函數(shù)不會進(jìn)行任何具體工作。
finalize_request正常完成與后端服務(wù)器的請求后調(diào)用該函數(shù),與 abort_request 相同,一般也不會進(jìn)行任何具體工作。
input_filter處理后端服務(wù)器返回的響應(yīng)正文。Nginx 默認(rèn)的 input_filter 會將收到的內(nèi)容封裝成為緩沖區(qū)鏈 ngx_chain。該鏈由 upstream 的 out_bufs 指針域定位,所以開發(fā)人員可以在模塊以外通過該指針 得到后端服務(wù)器返回的正文數(shù)據(jù)。memcached 模塊實(shí)現(xiàn)了自己的 input_filter,在后面會具體分析這個模塊。
input_filter_init初始化 input filter 的上下文。Nginx 默認(rèn)的 input_filter_init 直接返回。

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ù)的功能。作為 Web 服務(wù)器,這種設(shè)計(jì)是可以接受的。

下面,我們開始分析 ngx_http_memcached 模塊,一窺 upstream 的奧秘。

Handler 模塊?

初看 memcached 模塊,大家可能覺得并無特別之處。如果稍微細(xì)看,甚至覺得有點(diǎn)像 handler 模塊,當(dāng)大家看到這段代碼以后,必定疑惑為什么會跟 handler 模塊一模一樣。

        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
        clcf->handler = ngx_http_memcached_handler;

因?yàn)?upstream 模塊使用的就是 handler 模塊的接入方式。同時,upstream 模塊的指令系統(tǒng)的設(shè)計(jì)也是遵循 handler 模塊的基本規(guī)則:配置該模塊才會執(zhí)行該模塊。


        { ngx_string("memcached_pass"),
          NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
          ngx_http_memcached_pass,
          NGX_HTTP_LOC_CONF_OFFSET,
          0,
          NULL }

所以大家覺得眼熟是好事,說明大家對 Handler 的寫法已經(jīng)很熟悉了。

Upstream 模塊

那么,upstream 模塊的特別之處究竟在哪里呢?答案是就在模塊處理函數(shù)的實(shí)現(xiàn)中。upstream 模塊的處理函數(shù)進(jìn)行的操作都包含一個固定的流程。在 memcached 的例子中,可以觀察 ngx_http_memcached_handler 的代碼,可以發(fā)現(xiàn),這個固定的操作流程是:

  1. 創(chuàng)建 upstream 數(shù)據(jù)結(jié)構(gòu)。
        if (ngx_http_upstream_create(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
  1. 設(shè)置模塊的 tag 和 schema。schema 現(xiàn)在只會用于日志,tag 會用于 buf_chain 管理。
        u = r->upstream;

        ngx_str_set(&u->schema, "memcached://");
        u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
  1. 設(shè)置 upstream 的后端服務(wù)器列表數(shù)據(jù)結(jié)構(gòu)。
        mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
        u->conf = &mlcf->upstream;
  1. 設(shè)置 upstream 回調(diào)函數(shù)。在這里列出的代碼稍稍調(diào)整了代碼順序。
        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;
  1. 創(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->rest = NGX_HTTP_MEMCACHED_END;
        ctx->request = r;

        ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);

        u->input_filter_ctx = ctx;
  1. 完成 upstream 初始化并進(jìn)行收尾工作。
        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ù)雜的。這個問題先記下來,等把memcached剖析清楚了,再單獨(dú)討論。

第 6 步是一個常態(tài)。將 count 加 1,然后返回 NGX_DONE。Nginx 遇到這種情況,雖然會認(rèn)為當(dāng)前請求的處理已經(jīng)結(jié)束,但是不會釋放請求使用的內(nèi)存資源,也不會關(guān)閉與客戶端的連接。之所以需要這樣,是因?yàn)?Nginx 建立了 upstream 請求和客戶端請求之間一對一的關(guān)系,在后續(xù)使用 ngx_event_pipe 將 upstream 響應(yīng)發(fā)送回客戶端時,還要使用到這些保存著客戶端信息的數(shù)據(jù)結(jié)構(gòu)。這部分會在后面的原理篇做具體介紹,這里不再展開。

將 upstream 請求和客戶端請求進(jìn)行一對一綁定,這個設(shè)計(jì)有優(yōu)勢也有缺陷。優(yōu)勢就是簡化模塊開發(fā),可以將精力集中在模塊邏輯上,而缺陷同樣明顯,一對一的設(shè)計(jì)很多時候都不能滿足復(fù)雜邏輯的需要。對于這一點(diǎn),將會在后面的原理篇來闡述。

回調(diào)函數(shù)

前面剖析了 memcached 模塊的骨架,現(xiàn)在開始逐個解決每個回調(diào)函數(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ù)重點(diǎn)函數(shù)。memcache 協(xié)議的頭部信息被定義為第一行文本,可以找到這段代碼證明:
        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,提示緩存不夠大。

process_header 的重要職責(zé)是將后端服務(wù)器返回的狀態(tài)翻譯成返回給客戶端的狀態(tài)。例如,在 ngx_http_memcached_process_header 中,有這樣幾段代碼:

        r->headers_out.content_length_n = ngx_atoof(len, p - len - 1);

        u->headers_in.status_n = 200;
        u->state->status = 200;

        u->headers_in.status_n = 404;
        u->state->status = 404;

u->state 用于計(jì)算 upstream 相關(guān)的變量。比如 u->state->status 將被用于計(jì)算變量“upstream_status”的值。u->headers_in 將被作為返回給客戶端的響應(yīng)返回狀態(tài)碼。而第一行則是設(shè)置返回給客戶端的響應(yīng)的長度。

在這個函數(shù)中不能忘記的一件事情是處理完頭部信息以后需要將讀指針 pos 后移,否則這段數(shù)據(jù)也將被復(fù)制到返回給客戶端的響應(yīng)的正文中,進(jìn)而導(dǎo)致正文內(nèi)容不正確。


        u->buffer.pos = p + 1;

process_header 函數(shù)完成響應(yīng)頭的正確處理,應(yīng)該返回 NGX_OK。如果返回 NGX_AGAIN,表示未讀取完整數(shù)據(jù),需要從后端服務(wù)器繼續(xù)讀取數(shù)據(jù)。返回 NGX_DECLINED 無意義,其他任何返回值都被認(rèn)為是出錯狀態(tài),Nginx 將結(jié)束 upstream 請求并返回錯誤信息。

  • ngx_http_memcached_filter_init:修正從后端服務(wù)器收到的內(nèi)容長度。因?yàn)樵谔幚?header 時沒有加上這部分長度。

  • ngx_http_memcached_filter:memcached 模塊是少有的帶有處理正文的回調(diào)函數(shù)的模塊。因?yàn)?memcached 模塊需要過濾正文末尾 CRLF "END" CRLF,所以實(shí)現(xiàn)了自己的 filter 回調(diào)函數(shù)。處理正文的實(shí)際意義是將從后端服務(wù)器收到的正文有效內(nèi)容封裝成 ngx_chain_t,并加在 u->out_bufs 末尾。Nginx 并不進(jìn)行數(shù)據(jù)拷貝,而是建立 ngx_buf_t 數(shù)據(jù)結(jié)構(gòu)指向這些數(shù)據(jù)內(nèi)存區(qū),然后由 ngx_chain_t 組織這些 buf。這種實(shí)現(xiàn)避免了內(nèi)存大量搬遷,也是 Nginx 高效的奧秘之一。

本節(jié)回顧

這一節(jié)介紹了 upstream 模塊的基本組成。upstream 模塊是從 handler 模塊發(fā)展而來,指令系統(tǒng)和模塊生效方式與 handler 模塊無異。不同之處在于,upstream 模塊在 handler 函數(shù)中設(shè)置眾多回調(diào)函數(shù)。實(shí)際工作都是由這些回調(diào)函數(shù)完成的。每個回調(diào)函數(shù)都是在 upstream 的某個固定階段執(zhí)行,各司其職,大部分回調(diào)函數(shù)一般不會真正用到。upstream 最重要的回調(diào)函數(shù)是 create_request、process_header 和 input_filter,他們共同實(shí)現(xiàn)了與后端服務(wù)器的協(xié)議的解析部分。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號