溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Tengine怎么查找server塊

發(fā)布時間:2021-07-30 15:39:07 來源:億速云 閱讀:97 作者:chen 欄目:云計算

這篇文章主要講解了“Tengine怎么查找server塊”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Tengine怎么查找server塊”吧!

概述

本文的目標(biāo)讀者是Tengine/Nginx 研發(fā)或者運維同學(xué),如果自己對這塊邏輯非常清楚,那可以略過,如果在配置或者開發(fā) Tengine/Nginx 過程中,有如下疑問的同學(xué),本文或許能解答你多年的疑惑:

  1. 請求到達匹配的是哪個 server 塊?

  2. 為啥明明配置了 server 塊,還是沒有生效?

  3. 沒有這個域名的 server 塊,請求到底使用了哪個 server 塊?

  4. 要自己去匹配 server 塊的話,該從哪里入手? 
    ……

等等此類 server 塊有關(guān)的問題,在使用 Tengine 時可能經(jīng)常有遇到,在配置的 server 塊較少時,比較容易識別出,但在 CDN 或者云平臺接入層這種場景下,配置的 server 塊一般都非常多,少的有幾十上百個,多的成千上萬個都有可能,所以了解 Tengine 如何查找 server 塊非常有利于日常問題排查。

配置

先來看看幾個配置:

server {    listen       10.101.192.91:80 default_server;    listen       80 default_server;    listen       8080 default_server;    server_name  www.aa.com;    default_type  text/plain;    location / {        return 200 "default-server: $server_name, host: $host";
    }
}server {    listen       10.101.192.91:80;    server_name  www.bb.com;    default_type  text/plain;    location / {        return 200 "80server: $server_name, host: $host";
    }
}server {    listen       10.101.192.91:8080;    server_name  *.bb.com;    default_type  text/plain;    location / {        return 200 "8080server: $server_name, host: $host";
    }
}server {    listen       10.101.192.91:8080;    server_name  www.bb.com;    default_type  text/plain;    location / {        return 200 "8080server: $server_name, host: $host";
    }
}

上面配置了四個 server 塊,配置也非常簡單,第一個 server 塊配置了 default_server 參數(shù),這個表明了這個是默認(rèn) server 塊的意思(準(zhǔn)確地說是這個 listen 的 IP:Port 進來的請求默認(rèn) server 塊),監(jiān)聽了兩個端口80和8080,匹配域名為  www.aa.com,第二個是監(jiān)聽了 10.101.192.91:80 和匹配域名為 www.bb.com 的 server 塊,第三個是監(jiān)聽了 10.101.192.91:8080 和匹配泛域名 *.bb.com 的 server 塊,第四個是監(jiān)聽了 10.101.192.91:8080 和匹配精確域名  www.bb.com 的 server 塊。下面來驗證一下:  
Tengine怎么查找server塊
可以看出:

  1. 127.0.0.1:80 和 127.0.0.1:8080 都訪問到了第一個 server 塊

    • 這是因為第一個 server 監(jiān)聽了  :80 和 :8080 端口,其他 server 塊沒有監(jiān)聽 127.0.0.1 相應(yīng)的端口,127.0.0.1 的訪問只能匹配第一個 server 塊。

  2. 10.101.192.91:80 的訪問,域名和 server 塊匹配時使用了相應(yīng)的 server 塊,不匹配時使用了第一個默認(rèn) server 塊

    • IP:Port 匹配的情況下,再匹配到域名所在的 server 塊,域名跟 server_name 不匹配則匹配默認(rèn) server 塊。

  3. 10.101.192.91:8080 的訪問,域名先精確匹配到了  www.bb.com 的 server 塊,然后再匹配到了泛域名 *.bb.com 的 server 塊,不匹配時使用了第三個隱式默認(rèn) server 塊

    • 這里涉及到泛域名和隱式默認(rèn) server 塊,泛域名的匹配是在精確域名之后,這個也比較好理解,隱式默認(rèn) server 塊是沒有在 listen 后面指定 default_server 參數(shù)的 server 塊, Tengine/Nginx 在解析配置時,每個 IP:Port 都有一個默認(rèn) server 塊,如果 listen 后面顯式指定了 default_server 參數(shù)則該 listen 所在的 server 就是這個 IP:Port 的默認(rèn) server 塊,如果沒有顯式指定 default_server 參數(shù)則該 IP:Port 的第一個 server 塊就是隱式默認(rèn) server 塊。

上面這些配置可以衍生出一些 debug 技巧:

if ($http_x_alicdn_debug_get_server = "on") {    return 200 "$server_addr:$server_port, server_name: $server_name";
}

只要帶上請求頭  X-Alicdn-Debug-Get-Server: on 即可知道請求命中的是哪個 server 塊,這個配置對 server 塊非常多的系統(tǒng) debug 非常有用,需要注意的是這個配置需要放到一個配置文件和用 server_auto_include 加載,然后 tengine 會自動在所有 server 塊生效(nginx 沒有類似的配置命令)。

數(shù)據(jù)結(jié)構(gòu)

我們再來看看 http 核心模塊 server 塊的配置在數(shù)據(jù)結(jié)構(gòu)上怎么關(guān)聯(lián)的,其數(shù)據(jù)結(jié)構(gòu)是:

typedef struct {    /* array of the ngx_http_server_name_t, "server_name" directive */
    ngx_array_t                 server_names;    /* server ctx */
    ngx_http_conf_ctx_t        *ctx;
    u_char                     *file_name;    ngx_uint_t                  line;    ngx_str_t                   server_name;#if (T_NGX_SERVER_INFO)
    ngx_str_t                   server_admin;#endif
    size_t                      connection_pool_size;    size_t                      request_pool_size;    size_t                      client_header_buffer_size;    ngx_bufs_t                  large_client_header_buffers;    ngx_msec_t                  client_header_timeout;    ngx_flag_t                  ignore_invalid_headers;    ngx_flag_t                  merge_slashes;    ngx_flag_t                  underscores_in_headers;    unsigned                    listen:1;#if (NGX_PCRE)
    unsigned                    captures:1;#endif
    ngx_http_core_loc_conf_t  **named_locations;
} ngx_http_core_srv_conf_t;

這里不細說這些字段是干嘛用的,主要看 ngx_http_core_srv_conf_t 怎么與其他數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián),從上面的配置可以知道 server 是與 IP:Port 有關(guān)聯(lián)的,在 tengine/nginx 里的關(guān)系如下:

typedef struct {    ngx_http_listen_opt_t      opt;    ngx_hash_t                 hash;    ngx_hash_wildcard_t       *wc_head;    ngx_hash_wildcard_t       *wc_tail;#if (NGX_PCRE)
    ngx_uint_t                 nregex;    ngx_http_server_name_t    *regex;#endif
    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */} ngx_http_conf_addr_t;

可以看出,IP:Port 的核心數(shù)據(jù)結(jié)構(gòu) ngx_http_conf_addr_t 里面有默認(rèn) server 塊 default_server,以及該 IP:Port 關(guān)聯(lián)的所有 server 塊數(shù)組 servers,其他幾個字段不細展開了。tengine 把所有的 IP:Port 按 Port 拆分后將  ngx_http_conf_addr_t 放到了 ngx_http_conf_port_t 里面了:

typedef struct {    ngx_int_t                  family;    in_port_t                  port;    ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */} ngx_http_conf_port_t;

為什么將 IP:Port 拆分呢,這是因為 listen 的 Port 如果沒有指定 IP,比如  listen 80; ,那 tengine/nginx 在創(chuàng)建監(jiān)聽 socket 時的地址是 0.0.0.0 ,如果還有其他配置 listen 了精確 ip 和端口,比如  listen 10.101.192.91:80; ,那在內(nèi)核是沒法創(chuàng)建這個 socket 的,第2節(jié)配置里面的幾個 listen 在內(nèi)核是這樣監(jiān)聽的: 
image.png
雖然 listen 了 80 和 10.101.192.91:80,但在內(nèi)核都是 0.0.0.0:80,所以 tengine 需要用  ngx_http_conf_port_t 來記錄該端口的所有精確地址。但這個結(jié)構(gòu)只是使用在配置階段,在監(jiān)聽 socket 時轉(zhuǎn)換成了結(jié)構(gòu)  ngx_http_port_t 和  ngx_http_in_addr_t(這是因為 ip:port 和 server 塊是多對多的關(guān)系,需要重新組織和優(yōu)化):

typedef struct {    /* ngx_http_in_addr_t or ngx_http_in6_addr_t */
    void                      *addrs;    ngx_uint_t                 naddrs;
} ngx_http_port_t;typedef struct {    in_addr_t                  addr;    ngx_http_addr_conf_t       conf;
} ngx_http_in_addr_t;
typdef  ngx_http_addr_conf_s ngx_http_addr_conf_t;struct ngx_http_addr_conf_s {    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;    ngx_http_virtual_names_t  *virtual_names;    unsigned                   ssl:1;    unsigned                   http2:1;    unsigned                   proxy_protocol:1;
};

其中, ngx_http_port_t 記錄了該端口的所有精確地址和對應(yīng)的 server 塊。而  ngx_http_port_t 放到了監(jiān)聽的 socket 核心結(jié)構(gòu) ngx_listening_t 中:

typedef struct ngx_listening_s  ngx_listening_t;struct ngx_listening_s {    ngx_socket_t        fd;    struct sockaddr    *sockaddr;    socklen_t           socklen;    /* size of sockaddr */
    size_t              addr_text_max_len;    ngx_str_t           addr_text;    // 省略……
    /* handler of accepted connection */
    ngx_connection_handler_pt   handler;    void               *servers;  /* array of ngx_http_in_addr_t, for example */
    // 省略……};struct ngx_connection_s {    // 省略……
    ngx_listening_t    *listening;    // 省略……};

所以一個連接可以從 c->listening->servers 來查找匹配的 server 塊。

tengine 中 ip:port 和 server 的大體關(guān)聯(lián)關(guān)系如下:  
Tengine怎么查找server塊
(可以通過這個圖來理解一下 tengine 如何查找 server 塊)

從請求到 server 塊

上面講了 ip:port 和 server 的一些關(guān)系和核心數(shù)據(jù)結(jié)構(gòu),這一節(jié)來講講 tengine 從處理請求到匹配 server 的邏輯。ngx_http_init_connection 是初始化連接的函數(shù),在這個函數(shù)里面我們看到有這樣的邏輯:

void
ngx_http_init_connection(ngx_connection_t *c)
{    // 省略……
    ngx_http_port_t        *port;
    ngx_http_in_addr_t     *addr;
    ngx_http_connection_t  *hc;    // 省略……
    /* find the server configuration for the address:port */
    port = c->listening->servers;    if (port->naddrs > 1) {            // 省略……
            sin = (struct sockaddr_in *) c->local_sockaddr;
            addr = port->addrs;            /* the last address is "*" */
            for (i = 0; i < port->naddrs - 1; i++) {                if (addr[i].addr == sin->sin_addr.s_addr) {                    break;
                }
            }
            hc->addr_conf = &addr[i].conf;            // 省略……
    } else {            // 省略……
            addr = port->addrs;
            hc->addr_conf = &addr[0].conf;            // 省略……
    }    /* the default server configuration for the address:port */
    hc->conf_ctx = hc->addr_conf->default_server->ctx;    // 省略……}

可以看出,初始化時,拿到了 socket 的 ip:port 后去匹配了最合適的配置,存到了 hc->addr_conf 指針中,這個就是上面講到的數(shù)據(jù)結(jié)構(gòu)  ngx_http_addr_conf_t 指針,這里面存了該 ip:port 關(guān)聯(lián)的所有 server 塊核心配置,在之后收到 HTTP 請求頭處理請求行或者處理 Host 頭時,再根據(jù)域名去 hc->addr_conf 里面匹配出真實的 server 塊:

static ngx_int_t
ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
{    // 省略……
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;    // 省略……
    hc = r->http_connection;    // 省略……
    rc = ngx_http_find_virtual_server(r->connection,
                                      hc->addr_conf->virtual_names,
                                      host, r, &cscf);    //創(chuàng)建 r 時,r->srv_conf 和 r->loc_conf 是 hc->conf_ctx 的默認(rèn)配置
    //查不到匹配的 server 塊則不需要設(shè)置 r->srv_conf 和 r->loc_conf
    if (rc == NGX_DECLINED) {        return NGX_OK;
    }    // 查到匹配的 server,使用真實 server 塊的配置
    r->srv_conf = cscf->ctx->srv_conf;
    r->loc_conf = cscf->ctx->loc_conf;    // 省略……}

函數(shù)  ngx_http_find_virtual_server 是查找域名對應(yīng)的 server 塊接口(這個函數(shù)還有另一個地方調(diào)用是在處理 SSL 握手遇到 SNI 時,這是因為在握手時也需要找到匹配的 server 塊里面配置的證書)。 
至此,server 塊配置的查找邏輯結(jié)束,后續(xù)其他模塊處理時可以從 r->srv_conf 和 r->loc_conf 查到自己模塊的 server/location 塊配置了。

感謝各位的閱讀,以上就是“Tengine怎么查找server塊”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Tengine怎么查找server塊這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI