本文的目標讀者是Tengine/Nginx 研發或者運維同窗,若是本身對這塊邏輯很是清楚,那能夠略過,若是在配置或者開發 Tengine/Nginx 過程當中,有以下疑問的同窗,本文或許能解答你多年的疑惑:nginx
等等此類 server 塊有關的問題,在使用 Tengine 時可能常常有遇到,在配置的 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 參數,這個代表了這個是默認 server 塊的意思(準確地說是這個 listen 的 IP:Port 進來的請求默認 server 塊),監聽了兩個端口80和8080,匹配域名爲 www.aa.com
,第二個是監聽了 10.101.192.91:80 和匹配域名爲www.bb.com
的 server 塊,第三個是監聽了 10.101.192.91:8080 和匹配泛域名 *.bb.com
的 server 塊,第四個是監聽了 10.101.192.91:8080 和匹配精確域名 www.bb.com
的 server 塊。下面來驗證一下: 運維
能夠看出:socket
127.0.0.1:80 和 127.0.0.1:8080 都訪問到了第一個 server 塊函數
10.101.192.91:80 的訪問,域名和 server 塊匹配時使用了相應的 server 塊,不匹配時使用了第一個默認 server 塊優化
10.101.192.91:8080 的訪問,域名先精確匹配到了 www.bb.com
的 server 塊,而後再匹配到了泛域名 *.bb.com 的 server 塊,不匹配時使用了第三個隱式默認 server 塊ui
上面這些配置能夠衍生出一些 debug 技巧:this
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 塊很是多的系統 debug 很是有用,須要注意的是這個配置須要放到一個配置文件和用 server_auto_include 加載,而後 tengine 會自動在全部 server 塊生效(nginx 沒有相似的配置命令)。spa
咱們再來看看 http 核心模塊 server 塊的配置在數據結構上怎麼關聯的,其數據結構是:
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 怎麼與其餘數據結構關聯,從上面的配置能夠知道 server 是與 IP:Port 有關聯的,在 tengine/nginx 裏的關係以下:
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 的核心數據結構 ngx_http_conf_addr_t 裏面有默認 server 塊 default_server,以及該 IP:Port 關聯的全部 server 塊數組 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 在建立監聽 socket 時的地址是 0.0.0.0 ,若是還有其餘配置 listen 了精確 ip 和端口,好比 listen 10.101.192.91:80;
,那在內核是無法建立這個 socket 的,第2節配置裏面的幾個 listen 在內核是這樣監聽的:
雖然 listen 了 80 和 10.101.192.91:80,但在內核都是 0.0.0.0:80,因此 tengine 須要用 ngx_http_conf_port_t
來記錄該端口的全部精確地址。但這個結構只是使用在配置階段,在監聽 socket 時轉換成告終構 ngx_http_port_t
和 ngx_http_in_addr_t
(這是由於 ip:port 和 server 塊是多對多的關係,須要從新組織和優化):
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
記錄了該端口的全部精確地址和對應的 server 塊。而 ngx_http_port_t
放到了監聽的 socket 核心結構 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 的大致關聯關係以下:
(能夠經過這個圖來理解一下 tengine 如何查找 server 塊)
上面講了 ip:port 和 server 的一些關係和核心數據結構,這一節來說講 tengine 從處理請求到匹配 server 的邏輯。ngx_http_init_connection
是初始化鏈接的函數,在這個函數裏面咱們看到有這樣的邏輯:
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 指針中,這個就是上面講到的數據結構 ngx_http_addr_conf_t
指針,這裏面存了該 ip:port 關聯的全部 server 塊核心配置,在以後收到 HTTP 請求頭處理請求行或者處理 Host 頭時,再根據域名去 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); //建立 r 時,r->srv_conf 和 r->loc_conf 是 hc->conf_ctx 的默認配置 //查不到匹配的 server 塊則不須要設置 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; // 省略…… }
函數 ngx_http_find_virtual_server
是查找域名對應的 server 塊接口(這個函數還有另外一個地方調用是在處理 SSL 握手遇到 SNI 時,這是由於在握手時也須要找到匹配的 server 塊裏面配置的證書)。
至此,server 塊配置的查找邏輯結束,後續其餘模塊處理時能夠從 r->srv_conf 和 r->loc_conf 查到本身模塊的 server/location 塊配置了。
本文做者:金九
本文爲雲棲社區原創內容,未經容許不得轉載。