亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

首頁 慕課教程 Nginx 入門教程 Nginx 入門教程 21 Nginx的基礎架構解析(下)

Nginx的基礎架構解析(下)

1. Nginx模塊

1.1 Nginx中的模塊化設計

Nginx 的內部結構是由核心部分和一系列的功能模塊所組成。這樣劃分是為了使得每個模塊的功能相對簡單,便于開發,同時也便于對系統進行功能擴展。Nginx 將各功能模塊組織成一條鏈,當有請求到達的時候,請求依次經過這條鏈上的部分或者全部模塊,進行處理。例如前面講到的 http 請求,會有11個處理階段,而每個階段有對應著許多在此階段生效的模塊對該 http 請求進行處理。同時,Nginx 開放了第三方模塊編寫功能,用戶可以自定義模塊,控制 http 請求的處理與響應,這種高度可定制化催生了 Nginx 的大量第三方模塊,也使得 Nginx 定制化開發在各大互聯網公司十分流行。

1.2 Nginx中的模塊分類

關于 Nginx 模塊的分類有很多種方式,目前網上博客中寫的較多的是按照功能進行分類,有如下幾大類:

  • event 模塊: 搭建 獨立于操作系統的事件處理機制的框架,以及 提供各種具體事件的處理。代表性的模塊有:ngx_events_module, ngx_event_core_module, ngx_epoll_module;

  • handler 模塊: 主要負責處理客戶端請求并產生待響應的內容,比如 ngx_http_static_module 模塊,負責客戶端的靜態頁面請求處理并將對應的磁盤 文件準備為響應內容輸出;

  • filter 模塊: 主要 負責處理輸出的內容,包括修改輸出內容。代表性的模塊有: ngx_http_sub_module;

  • upstream 模塊: 該類模塊都是用于實現反向代理功能,將真正的請求轉發到后端服務器上,并從后端服務器上讀取響應,發回給客戶端。比如前面介紹到轉發 http、websocket、grpc、rtmp等協議的模塊都可以劃分為這一類;

  • 負載均衡模塊: 負載均衡的模塊,實現相應算法。這類模塊都是用于實現 Nginx 的負載均衡功能。

  • extend 模塊: 又稱第三方模塊,非 Nginx 官方提供,由各大企業的開發人員結合自身業務開發而成。Nginx 提供了非常好的模塊編寫機制,遵循相關的標準可以很快定制出符合我們業務場景的模塊,而且內部調用 Nginx 內部提供的方法進行處理,使得第三方模塊往往都具備很好的性能

對于官方提供的模塊,我們可以直接在官網文檔上學習,學習的方式和學習其他互聯網組件的方式一致,首先學習如何使用,在用至熟練后可以深入分析其源碼了解功能實現背后的原理。

我們以前面介紹到的 Nginx 的限速模塊(limit_req模塊)進行說明。首先是掌握該模塊的用法,在該模塊的官方地址中,有關于該模塊的詳細介紹,包括該模塊提供的所有指令以及所有變量說明。此外,還有比較豐富的指令用例。在多次使用該指令并自認為掌握了該模塊的用法之后,想了解限速背后的原理以及相關算法時,就可以深入到源碼學習了。

進入 Nginx 的源碼目錄,使用ls查看源碼文件,限速模塊是在 http 目錄中的。

[root@server nginx-1.17.6]# cd src/

[root@server src]# ls
core  event  http  mail  misc  os  stream

[root@server src]# ls http/modules/ngx_http_limit_*.c
http/modules/ngx_http_limit_conn_module.c
http/modules/ngx_http_limit_req_module.c

找到 Nginx 模塊對應的代碼文件后,我們就可以閱讀里面的代碼進行學習。往往源碼的閱讀是枯燥無味的,我們可以借助海量的網絡資源輔助我們學習。這里就有一篇文章,作者深入分析了 Nginx 的限流模塊的源碼以及相應限流算法,最后進行了相關的實驗測試。通過這樣一個個模塊深入學習,最后在使用每一個 Nginx 指令時,也會非常熟練,最后成為 Nginx 高手。

1.3 如何學習和使用第三方模塊

這里我們演示在 Nginx 中使用第三方模塊。 Openresty 社區提供了一款 Nginx 中的 Echo 模塊,即echo-nginx-module。在 Nginx 中添加了該模塊后,我們在配置文件中可以使用該模塊提供的 echo 指令返回用戶響應,簡單方便。該模塊的源碼在 github 上,并且有良好的文檔和使用示例,非常方便開發者使用。

圖片描述

現在我們在 Nginx 的源碼編譯階段加入該第三方模塊,具體操作如下:

[root@server shencong]# pwd
/root/shencong
[root@server shencong]# mkdir nginx-echo
# 下載 nginx 源碼包和第三方模塊的源碼包 
[root@server shencong]# wget http://nginx.org/download/nginx-1.17.6.tar.gz
[root@server shencong]# wget https://github.com/openresty/echo-nginx-module/archive/v0.62rc1.tar.gz

# 解壓
[root@server shencong]# tar -xzf nginx-1.17.6.tar.gz
[root@server shencong]# tar -xzf v0.62rc1.tar.gz

[root@server shencong]# ls
echo-nginx-module-0.62rc1  nginx-1.17.6  nginx-1.17.6.tar.gz  nginx-echo  v0.62rc1.tar.gz

[root@server shencong]# cd nginx-1.17.6
# 使用--add-module添加第三方模塊,參數為第三方模塊源碼
[root@server shencong]# ./configure --prefix=/root/shencong/nginx-echo  --add-module=/root/shencong/echo-nginx-module-0.62rc1

編譯完成后,我們就可以去nginx-echo目錄中的 nginx.conf文件中添加echo 指令 。準備如下的配置(可以參參考社區提供的示例):

...
http {
   server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
        
        # 新增測試 echo 指令配置
        location /timed_hello {
            default_type text/plain;
            echo_reset_timer;
            echo hello world;
            echo "'hello world' takes about $echo_timer_elapsed sec.";
            echo hiya igor;
            echo "'hiya igor' takes about $echo_timer_elapsed sec.";
        }

        location /echo_with_sleep {
            default_type text/plain;
            echo hello world;
            echo_flush;  # ensure the client can see previous output immediately
            echo_sleep   2.5;  # in sec
            echo "'hello' takes about $echo_timer_elapsed sec.";
        }

    }
}
...

啟動 Nginx 后,我們就可以在瀏覽器上請求者兩個 URI 地址,看到相應 echo 返回的信息了。第二個配置是使用了 echo_sleep 指令,會使得請求在休眠 2.5s 后才返回。
圖片描述

圖片描述

1.4 如何編寫自己的模塊

想要編寫 Nginx 模塊,首先需要對 Nginx 模塊中的源碼以及相關的數據結構有所了解,還要知曉 Nginx HTTP 模塊的調用流程。假設我要實現前面第三方模塊Echo的最簡單形式,即只輸出相應的字符串即可。假定模塊支持的指令名稱還是 echo, 這個 echo 指令需要跟一個參數,即輸出的字符串。我們需要做如下幾步:

  • 確定模塊名稱,以及模塊中的指令以及參數,還有運行的環境。這里涉及的結構是 ngx_command_t,它定義了模塊里的所有指令格式。下面的代碼表示該模塊中只有一個 echo 指令,它出現的上下文環境為 location,且有一個參數(NGX_CONF_TAKE1)。當某個配置塊中出現echo指令時,Nginx 將調用ngx_http_echo方法。然后在該方法中,會設置處理請求的 handler,這個 handler 就是處理請求的方法。
  static ngx_command_t  ngx_http_echo_commands[] = {  
         { ngx_string("echo"),      /* 指令名稱,利用ngx_string宏定義 */
          NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,  /* 用在 location 指令塊內,且有1個參數 */
          ngx_http_echo,            /* 處理回調函數 */
          NGX_HTTP_LOC_CONF_OFFSET,    
          offsetof(ngx_http_echo_loc_conf_t, ed), /* 指定參數讀取位置 */
          NULL },
          ngx_null_command
  };
  • 完成請求處理的 handler 函數,最重要的部分就在這里;
 static char *
 ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_http_core_loc_conf_t  *clcf;
     /* 找到指令所屬的配置塊,這里我們限定echo指令的上下文環境只有location */
     clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
     /* 指定處理的handler */
     clcf->handler = ngx_http_echo_handler;
     ...
     return NGX_CONF_OK;
 }
 
 static ngx_int_t
 ngx_http_echo_handler(ngx_http_request_t *r)
 {
     ...
     
     /* 向用戶發送相應包 */
     return ngx_http_output_filter(r, &out);
 }
  • 一些收尾工作,比如配置模塊介入 http 請求的哪些階段等。
  /* Http context of the module */
  static ngx_http_module_t  ngx_http_echo_module_ctx = {
      NULL,                                  /* preconfiguration */
      NULL,                                  /* postconfiguration */
      NULL,                                  /* create main configuration */
      NULL,                                  /* init main configuration */
      NULL,                                  /* create server configuration */
      NULL,                                  /* merge server configuration */
      ngx_http_echo_create_loc_conf,         /* create location configration */
      ngx_http_echo_merge_loc_conf           /* merge location configration */
  };
  /* Module */
  ngx_module_t  ngx_http_echo_module = {
      NGX_MODULE_V1,
      &ngx_http_echo_module_ctx,             /* module context */
      ngx_http_echo_commands,                /* module directives */
      NGX_HTTP_MODULE,                       /* module type */
      NULL,                                  /* init master */
      NULL,                                  /* init module */
      NULL,                                  /* init process */
      NULL,                                  /* init thread */
      NULL,                                  /* exit thread */
      NULL,                                  /* exit process */
      NULL,                                  /* exit master */
      NGX_MODULE_V1_PADDING
  };

完成以上幾步,一個簡易的自定義模塊就算大功告成了。接下來我們動手完成第一個自定義的模塊,將其編譯進Nginx 二進制文件中并進行測試。

2. 案例

我們來完成一個簡單的自定義 http 模塊,來實現前面Echo模塊的最簡單形式,即使用指令輸出 “hello, world” 字符串。首先新建一個目錄echo-nginx-module,然后在目錄下新建兩個文件configngx_http_echo_module.c

[root@server echo-nginx-module]# pwd
/root/shencong/echo-nginx-module

[root@server echo-nginx-module]# ls
config  ngx_http_echo_module.c

兩個文件內容分別如下:

[root@server echo-nginx-module]# cat config 
ngx_addon_name=ngx_http_echo_module
# 指定模塊名稱
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
# 指定模塊源碼路徑
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"

[root@server echo-nginx-module]# cat ngx_http_echo_module.c
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

/* Module config */
typedef struct {
    ngx_str_t  ed;
} ngx_http_echo_loc_conf_t;

static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);

/* 定義指令 */
static ngx_command_t  ngx_http_echo_commands[] = {
    { ngx_string("echo"),      /* 指令名稱,利用ngx_string宏定義 */
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,  /* 用在 location 指令塊內,且有1個參數 */
        ngx_http_echo,         /* 處理回調函數 */
        NGX_HTTP_LOC_CONF_OFFSET,    
        offsetof(ngx_http_echo_loc_conf_t, ed), /* 指定參數讀取位置 */
        NULL },
        ngx_null_command
};
/* Http context of the module */
static ngx_http_module_t  ngx_http_echo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    ngx_http_echo_create_loc_conf,         /* create location configration */
    ngx_http_echo_merge_loc_conf           /* merge location configration */
};
/* Module */
ngx_module_t  ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,             /* module context */
    ngx_http_echo_commands,                /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};
/* Handler function */
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_http_echo_loc_conf_t *elcf;
    /* 獲取指令的參數 */
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
    {
        /* 如果不是 HEAD/GET/PUT 請求,則返回405 Not Allowed錯誤 */
        return NGX_HTTP_NOT_ALLOWED;
    }
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = elcf->ed.len;
    if(r->method == NGX_HTTP_HEAD)
    {
        rc = ngx_http_send_header(r);
        if(rc != NGX_OK)
        {
            return rc;
        }
    }
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(b == NULL)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->pos = elcf->ed.data;
    b->last = elcf->ed.data + (elcf->ed.len);
    b->memory = 1;
    b->last_buf = 1;
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    /* 向用戶發送相應包 */
    return ngx_http_output_filter(r, &out);
}
static char *
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    /* 指定處理的handler */
    clcf->handler = ngx_http_echo_handler;
    ngx_conf_set_str_slot(cf,cmd,conf);
    return NGX_CONF_OK;
}
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_echo_loc_conf_t  *conf;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->ed.len = 0;
    conf->ed.data = NULL;
    return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_echo_loc_conf_t *prev = parent;
    ngx_http_echo_loc_conf_t *conf = child;
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
    return NGX_CONF_OK;
}

這樣一個第三方模塊包就完成了,接下來我們要向之前使用第三方模塊一樣,將它編譯進 Nginx,具體操作如下。

[root@server shencong]# cd nginx-1.17.6/
[root@server nginx-1.17.6]# ./configure --prefix=/root/shencong/nginx-echo --add-module=/root/shencong/echo-nginx-module
...
[root@server nginx-1.17.6] # make && make install
...
[root@server nginx-1.17.6]# cd ../nginx-echo/sbin/
[root@server sbin]# ./nginx -V
nginx version: nginx/1.17.6
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) 
configure arguments: --prefix=/root/shencong/nginx-echo --add-module=/root/shencong/echo-nginx-module

接下來,我們只要在 nginx.conf 中加入我們的指令,并給一個參數,就能看到我們自定義的輸出了。

...
http {
   ...
   server {
       listen       80;
       server_name  localhost;
       location / {
            root   html;
            index  index.html index.htm;
       }
       
       location /test {
            echo hello,world;
       }
       ...
   }
}
...

最后我們請求主機的80端口,URI=/test,瀏覽器輸出"hello, world",說明我們的自定義模塊成功了!

圖片描述

3. 小結

在 Nginx 基礎架構介紹的最后,主要是介紹了 Nginx 的模塊設計以及相應的模塊用法。最后,我們簡單給出了一個簡單編寫自己模塊的案例作為本章的實戰案例。希望這一節之后,大家能對 Nginx 的模塊化設計有所了解,并有興趣在模塊的源碼方向深入學習。