tft每日頭條

 > 生活

 > nginxstream模塊詳解

nginxstream模塊詳解

生活 更新时间:2024-11-26 10:20:08

文章有點長分二章分享給大家、後續持續更新需要的朋友可以點擊關注我哦。。。。

第一章 r filter 模塊

1.1 過濾模塊簡介

1. 執行時間和内容

過濾(filter)模塊是過濾響應頭和内容的模塊,可以對回複的頭和内容進行處理。它的處理

時間在獲取回複内容之後,向用戶發送響應之前。它的處理過程分為兩個階段,過濾 HTTP

回複的頭部和主體,在這兩個階段可以分别對頭部和主體進行修改。

在代碼中有類似的函數:

ngx_http_top_header_filter(r);

ngx_http_top_body_filter(r, in);

就是分别對頭部和主體進行過濾的函數。所有模塊的響應内容要返回給客戶端,都必須調用

這兩個接口。

更多c/c Linux服務器高階知識、電子書籍、視頻等等請後台私信【架構】獲取

知識點有C/C ,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等等。

nginxstream模塊詳解(Nginx源碼分析之r)1

nginxstream模塊詳解(Nginx源碼分析之r)2

2. 執行順序

過濾模塊的調用是有順序的,它的順序在編譯的時候就決定了。控制編譯的腳本位于

auto/modules 中,當你編譯完 Nginx 以後,可以在 objs 目錄下面看到一個 ngx_modules.c 的

文件。打開這個文件,有類似的代碼:

ngx_module_t *ngx_modules[] = {

...

&ngx_http_write_filter_module,

&ngx_http_header_filter_module,

&ngx_http_chunked_filter_module,

&ngx_http_range_header_filter_module,

&ngx_http_gzip_filter_module,

&ngx_http_postpone_filter_module,

&ngx_http_ssi_filter_module,

&ngx_http_charset_filter_module,

&ngx_http_userid_filter_module,

&ngx_http_headers_filter_module,

&ngx_http_copy_filter_module,

&ngx_http_range_body_filter_module,

&ngx_http_not_modified_filter_module,

NULL

};

從 write_filter 到 not_modified_filter,模塊的執行順序是反向的。也就是說最早執行的

是 not_modified_filter,然後各個模塊依次執行。所有第三方的模塊隻能加入到 copy_filter 和

headers_filter 模塊之間執行。

Nginx 執行的時候是怎麼按照次序依次來執行各個過濾模塊呢?它采用了一種很隐晦的

方法,即通過局部的全局變量。比如,在每個 filter 模塊,很可能看到如下代碼:

static ngx_http_output_header_filter_pt

ngx_http_next_header_filter;

static ngx_http_output_body_filter_pt

ngx_http_next_body_filter;

...

ngx_http_next_header_filter = ngx_http_top_header_filter;

ngx_http_top_header_filter = ngx_http_example_header_filter;

ngx_http_next_body_filter = ngx_http_top_body_filter;

ngx_http_top_body_filter = ngx_http_example_body_filter;

ngx_http_top_header_filter 是一個全局變量。當編譯進一個 filter 模塊的時候,就被賦值

為當前 filter 模塊的處理函數。而 ngx_http_next_header_filter 是一個局部全局變量,它保存

了編譯前上一個 filter 模塊的處理函數。所以整體看來,就像用全局變量組成的一條單向鍊

表。

每個模塊想執行下一個過濾函數,隻要調用一下 ngx_http_next_header_filter 這個局部

變量。而整個過濾模塊鍊的入口,需要調用 ngx_http_top_header_filter 這個全局變量。

ngx_http_top_body_filter 的行為與 header fitler 類似。

響應頭和響應體過濾函數的執行順序如下所示:

nginxstream模塊詳解(Nginx源碼分析之r)3

這圖隻表示了 head_filter 和 body_filter 之間的執行順序,在 header_filter 和 body_filter 處理

函數之間,在 body_filter 處理函數之間,可能還有其他執行代碼。

3. 模塊編譯

Nginx 可以方便的加入第三方的過濾模塊。在過濾模塊的目錄裡,首先需要加入 config 文件,

文件的内容如下:

ngx_addon_name=ngx_http_example_filter_module

HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES

ngx_http_example_filter_module"

NGX_ADDON_SRCS="$NGX_ADDON_SRCS

$ngx_addon_dir/ngx_http_example_filter_module.c"

說 明 把 這 個 名 為 ngx_http_example_filter_module 的 過 濾 模 塊 加 入 ,

ngx_http_example_filter_module.c 是該模塊的源代碼。

注意 HTTP_AUX_FILTER_MODULES 這個變量與一般的内容處理模塊不同。

1.2 過濾模塊的分析

. 1. 相關結構體

ngx_chain_t 結構非常簡單,是一個單向鍊表:

typedef struct ngx_chain_s ngx_chain_t;

struct ngx_chain_s {

ngx_buf_t *buf;

ngx_chain_t *next;

};

在過濾模塊中,所有輸出的内容都是通過一條單向鍊表所組成。這種單向鍊表的設計,正好

應和了 Nginx 流式的輸出模式。每次 Nginx 都是讀到一部分的内容,就放到鍊表,然後輸出

出去。這種設計的好處是簡單,非阻塞,但是相應的問題就是跨鍊表的内容操作非常麻煩,

如果需要跨鍊表,很多時候都隻能緩存鍊表的内容。

單鍊表負載的就是 ngx_buf_t,這個結構體使用非常廣泛,先讓我們看下該結構體的代碼:

struct ngx_buf_s {

u_char *pos; /* 當前 buffer 真實内容的起始位置 */

u_char *last; /* 當前 buffer 真實内容的結束位置 */

off_t file_pos; /* 在文件中真實内容的起始位置 */

off_t file_last; /* 在文件中真實内容的結束位置 */

u_char *start; /* buffer 内存的開始分配的位置 */

u_char *end; /* buffer 内存的結束分配的位置 */

ngx_buf_tag_t tag; /* buffer 屬于哪個模塊的标志 */

ngx_file_t *file; /* buffer 所引用的文件 *//* 用來引用替換過後的 buffer,以便當所有 buffer 輸出以後,

* 這個影子 buffer 可以被釋放。

*/

ngx_buf_t *shadow;

/* the buf's content could be changed */

unsigned temporary:1;

/*

* the buf's content is in a memory cache or in a read

only memory

* and must not be changed

*/

unsigned memory:1;

/* the buf's content is mmap()ed and must not be changed

*/

unsigned mmap:1;

unsigned recycled:1; /* 内存可以被輸出并回收 */

unsigned in_file:1; /* buffer 的内容在文件中 */

/* 馬上全部輸出 buffer 的内容, gzip 模塊裡面用得比較多 */

unsigned flush:1;

/* 基本上是一段輸出鍊的最後一個 buffer 帶的标志,标示可以輸

出,

* 有些零長度的 buffer 也可以置該标志

*/

unsigned sync:1;

/* 所有請求裡面最後一塊 buffer,包含子請求 */

unsigned last_buf:1;

/* 當前請求輸出鍊的最後一塊 buffer */

unsigned last_in_chain:1;

/* shadow 鍊裡面的最後 buffer,可以釋放 buffer 了 */

unsigned last_shadow:1;

/* 是否是暫存文件 */

unsigned temp_file:1;

/* 統計用,表示使用次數 */

/* STUB */ int num;

};

一般 buffer 結構體可以表示一塊内存,内存的起始和結束地址分别用 start 和 end 表示,pos

和 last 表示實際的内容。如果内容已經處理過了,pos 的位置就可以往後移動。如果讀取到

新的内容,last 的位置就會往後移動。所以 buffer 可以在多次調用過程中使用。如果 last 等

于 end,就說明這塊内存已經用完了。如果 pos 等于 last,說明内存已經處理完了。下面是

一個簡單的示意圖,說明 buffer 中指針的用法:

nginxstream模塊詳解(Nginx源碼分析之r)4

2. 響應頭過濾函數

響應頭過濾函數主要的用處就是處理 HTTP 響應的頭,可以根據實際情況對于響應頭進行修

改或者添加删除。響應頭過濾函數先于響應體過濾函數,而且隻調用一次,所以一般可作過

濾模塊的初始化工作。

響應頭過濾函數的入口隻有一個:

ngx_int_t

ngx_http_send_header(ngx_http_request_t *r)

{

...

return ngx_http_top_header_filter(r);

}

該函數向客戶端發送回複的時候調用,然後按前一節所述的執行順序。該函數的返回值一般

是 NGX_OK,NGX_ERROR 和 NGX_AGAIN,分别表示處理成功,失敗和未完成。可以把 HTTP

響應頭的存儲方式想象成一個 hash 表,在 Nginx 内部可以很方便地查找和修改各個響應頭

部,ngx_http_header_filter_module 過濾模塊把所有的 HTTP 頭組合成一個完整的 buffer,最

終 ngx_http_write_filter_module 過濾模塊把 buffer 輸出。

按照前一節過濾模塊的順序,依次講解如下:

nginxstream模塊詳解(Nginx源碼分析之r)5

nginxstream模塊詳解(Nginx源碼分析之r)6

3. 響應體過濾函數

響應體過濾函數是過濾響應主體的函數。ngx_http_top_body_filter 這個函數每個請求可能會

被執行多次,它的入口函數是 ngx_http_output_filter,比如:

ngx_int_t

ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)

{

ngx_int_t rc;

ngx_connection_t *c;

c = r->connection;

rc = ngx_http_top_body_filter(r, in);

if (rc == NGX_ERROR) {

/* NGX_ERROR may be returned by any filter */

c->error = 1;

}

return rc;

}

ngx_http_output_filter 可以被一般的靜态處理模塊調用,也有可能是在 upstream 模塊裡面被

調用,對于整個請求的處理階段來說,他們處于的用處都是一樣的,就是把響應内容過濾,

然後發給客戶端。具體模塊的響應體過濾函數的格式類似這樣:

static int

ngx_http_example_body_filter(ngx_http_request_t *r, ngx_chain_t

*in)

{

...

return ngx_http_next_body_filter(r, in);

}

該函數的返回值一般是 NGX_OK,NGX_ERROR 和 NGX_AGAIN,分别表示處理成功,失敗和

未完成。

4. 主要功能介紹

響應的主體内容就存于單鍊表 in,鍊表一般不會太長,有時 in 參數可能為 NULL。in 中存有

buf 結構體中,對于靜态文件,這個 buf 大小默認是 32K;對于反向代理的應用,這個 buf 可

能是 4k 或者 8k。為了保持内存的低消耗,Nginx 一般不會分配過大的内存,處理的原則是

收到一定的數據,就發送出去。一個簡單的例子,可以看看 Nginx 的 chunked_filter 模塊,在

沒有 content-length 的情況下,chunk 模塊可以流式(stream)的加上長度,方便浏覽器接收

和顯示内容。

在響應體過濾模塊中,尤其要注意的是 buf 的标志位,完整描述可以在“相關結構體”這個節

中看到。如果 buf 中包含 last 标志,說明是最後一塊 buf,可以直接輸出并結束請求了。如

果有 flush 标志,說明這塊 buf 需要馬上輸出,不能緩存。如果整塊 buffer 經過處理完以後,

沒有數據了,你可以把 buffer 的 sync 标志置上,表示隻是同步的用處。

當所有的過濾模塊都處理完畢時,在最後的 write_fitler 模塊中,Nginx 會将 in 輸出鍊拷貝到

r->out 輸出鍊的末尾,然後調用 sendfile 或者 writev 接口輸出。由于 Nginx 是非阻塞的 socket

接口,寫操作并不一定會成功,可能會有部分數據還殘存在 r->out。在下次的調用中,Nginx

會繼續嘗試發送,直至成功。

5. 發出子請求

Nginx 過濾模塊一大特色就是可以發出子請求,也就是在過濾響應内容的時候,你可以發送

新的請求,Nginx 會根據你調用的先後順序,将多個回複的内容拼接成正常的響應主體。一

個簡單的例子可以參考 addition 模塊。

Nginx 是如何保證父請求和子請求的順序呢?當 Nginx 發出子請求時,就會調用

ngx_http_subrequest 函數,将子請求插入父請求的 r->postponed 鍊表中。子請求會在主請求

執行完畢時獲得依次調用。子請求同樣會有一個請求所有的生存期和處理過程,也會進入過

濾模塊流程。

關鍵點是在 postpone_filter 模塊中,它會拼接主請求和子請求的響應内容。r->postponed 按

次序保存有父請求和子請求,它是一個鍊表,如果前面一個請求未完成,那後一個請求内容

就不會輸出。當前一個請求完成時并輸出時,後一個請求才可輸出,當所有的子請求都完成

時,所有的響應内容也就輸出完畢了。

6. 一些優化措施

Nginx 過濾模塊涉及到的結構體,主要就是 chain 和 buf,非常簡單。在日常的過濾模塊中,

這兩類結構使用非常頻繁,Nginx 采用類似 freelist 重複利用的原則,将使用完畢的 chain 或

者 buf 結構體,放置到一個固定的空閑鍊表裡,以待下次使用。

比如,在通用内存池結構體中,pool->chain 變量裡面就保存着釋放的 chain。而一般的 buf

結構體,沒有模塊間公用的空閑鍊表池,都是保存在各模塊的緩存空閑鍊表池裡面。對于 buf

結構體,還有一種 busy 鍊表,表示該鍊表中的 buf 都處于輸出狀态,如果 buf 輸出完畢,這

些 buf 就可以釋放并重複利用了。

nginxstream模塊詳解(Nginx源碼分析之r)7

7. 過濾内容的緩存

由于 Nginx 設計流式的輸出結構,當我們需要對響應内容作全文過濾的時候,必須緩存部分

的 buf 内容。該類過濾模塊往往比較複雜,比如 sub,ssi,gzip 等模塊。這類模塊的設計非

常靈活,我簡單講一下設計原則:

1. 輸入鍊 in 需要拷貝操作,經過緩存的過濾模塊,輸入輸出鍊往往已經完全不一樣了,

所以需要拷貝,通過 ngx_chain_add_copy 函數完成。

2. 一般有自己的 free 和 busy 緩存鍊表池,可以提高 buf 分配效率。

3. 如果需要分配大塊内容,一般分配固定大小的内存卡,并設置 recycled 标志,表示可以

重複利用。

4. 原有的輸入 buf 被替換緩存時,必須将其 buf->pos 設為 buf->last,表明原有的 buf 已經

被輸出完畢。或者在新建立的 buf,将 buf->shadow 指向舊的 buf,以便輸出完畢時及時

釋放舊的 buf。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved