Tengine 如何查詢 server 塊
概述
本文的目標讀者是Tengine/Nginx 研發或者運維同學,如果自己對這塊邏輯非常清楚,那可以略過,如果在配置或者開發 Tengine/Nginx 過程中,有如下疑問的同學,本文或許能解答你多年的疑惑:
- 請求到達匹配的是哪個 server 塊?
- 為啥明明配置了 server 塊,還是沒有生效?
- 沒有這個域名的 server 塊,請求到底使用了哪個 server 塊?
- 要自己去匹配 server 塊的話,該從哪裡入手?
……
等等此類 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 塊。下面來驗證一下:
可以看出:
-
127.0.0.1:80 和 127.0.0.1:8080 都訪問到了第一個 server 塊
- 這是因為第一個 server 監聽了 :80 和 :8080 埠,其他 server 塊沒有監聽 127.0.0.1 相應的埠,127.0.0.1 的訪問只能匹配第一個 server 塊。
-
10.101.192.91:80 的訪問,域名和 server 塊匹配時使用了相應的 server 塊,不匹配時使用了第一個預設 server 塊
- IP:Port 匹配的情況下,再匹配到域名所在的 server 塊,域名跟 server_name 不匹配則匹配預設 server 塊。
-
10.101.192.91:8080 的訪問,域名先精確匹配到了
www.bb.com
的 server 塊,然後再匹配到了泛域名 *.bb.com 的 server 塊,不匹配時使用了第三個隱式預設 server 塊- 這裡涉及到泛域名和隱式預設 server 塊,泛域名的匹配是在精確域名之後,這個也比較好理解,隱式預設 server 塊是沒有在 listen 後面指定 default_server 引數的 server 塊, Tengine/Nginx 在解析配置時,每個 IP:Port 都有一個預設 server 塊,如果 listen 後面顯式指定了 default_server 引數則該 listen 所在的 server 就是這個 IP:Port 的預設 server 塊,如果沒有顯式指定 default_server 引數則該 IP:Port 的第一個 server 塊就是隱式預設 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 塊非常多的系統 debug 非常有用,需要注意的是這個配置需要放到一個配置檔案和用 server_auto_include 載入,然後 tengine 會自動在所有 server 塊生效(nginx 沒有類似的配置命令)。
資料結構
我們再來看看 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 塊)
從請求到 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 塊配置了。
️本文作者:金九
本文為雲棲社群原創內容,未經允許不得轉載。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69947441/viewspace-2656619/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- sql-server高階查詢SQLServer
- Sql Server 的引數化查詢SQLServer
- SQL Server 跨資料庫查詢SQLServer資料庫
- sql-server不相關子查詢SQLServer
- sql-server相關子查詢SQLServer
- SQL SERVER死鎖查詢,死鎖分析,解鎖,查詢佔用SQLServer
- dbForge Studio for SQL Server入門教程:如何建立和編輯查詢SQLServer
- SQL Server 查詢超時問題排查SQLServer
- SQL Server 語句日期格式查詢方法SQLServer
- SQL Server-簡單查詢語句SQLServer
- SQL Server查詢慢的解決方案SQLServer
- DbForge Studio for SQL Server入門教程:在查詢生成器中建立查詢SQLServer
- SQL Server 查詢表註釋和欄位SQLServer
- 如何在tengine/nginx層做ABtestNginx
- 查詢SQL Server的歷史執行記錄SQLServer
- 使用SSMS連線和查詢 SQL Server 例項SSMSQLServer
- SQL server根據表名查詢表主鍵SQLServer
- 如何查詢上標
- SQL Server資料庫————模糊查詢和聚合函式SQLServer資料庫函式
- SQL server資料庫表碎片比例查詢語句SQLServer資料庫
- QL Server 百萬級資料提高查詢速度的方法Server
- KCSQL SERVER實現連線與合併查詢dinSQLServer
- 概括SQL Server實時查詢Oracle資料庫WSSQLServerOracle資料庫
- SQL Server解惑——查詢條件IN中能否使用變數SQLServer變數
- SQL Server 查詢歷史執行的SQL語句SQLServer
- 分塊查詢【大規模資料查詢演算法優化】【索引順序查詢】演算法 PHP 版演算法優化索引PHP
- 資料結構之查詢(順序、折半、分塊查詢,B樹、B+樹)資料結構
- [轉帖]SQL Server簡潔查詢正在執行的程序SQLServer
- SQL Server實戰四:查詢資料庫的資料SQLServer資料庫
- Ms Sql Server查詢儲存過程中的內容SQLServer儲存過程
- 在 SQL Server 中查詢活動連線和死鎖SQLServer
- 如何查詢網站 ip 地址網站
- MongoDB 如何支援類 SQL 查詢MongoDBSQL
- 如何精準查詢日誌
- CAD如何進行文字查詢
- mysql多表查詢如何實現MySql
- cmd命令如何查詢ip地址
- 如何找東西?查詢演算法之順序查詢和二分查詢詳解演算法