了解 Nginx 服务器和位置块选择算法

介绍

Nginx 是世界上最受欢迎的 Web 服务器之一,它可以成功处理许多同时连接的客户端的高负载,并可作为 Web 服务器、邮件服务器或反向代理服务器运作。

在本指南中,我们将讨论一些决定 Nginx 如何处理客户端请求的幕后细节,了解这些想法可以帮助设计服务器和位置块的猜测,并使请求处理看起来不那么不可预测。

Nginx 区块配置

Nginx 逻辑地将旨在服务不同内容的配置分为区块,这些区块生活在一个层次结构中. 每次提出客户端请求时, Nginx 都会开始确定应该使用哪些配置区块来处理请求的过程。

我们将讨论的主要块是 **服务器 ** 区块和 ** 位置 ** 区块。

服务器块是 Nginx 配置的子集,它定义了用于处理定义类型的请求的虚拟服务器,管理员通常会配置多个服务器块,并决定哪个区块应根据所请求的域名、端口和 IP 地址来处理哪个连接。

位置块生活在服务器块内,用来定义 Nginx 应该如何处理对主服务器的不同资源和 URI 的请求。

Nginx如何决定哪个服务器块将处理请求

由于 Nginx 允许管理员定义作为独立的虚拟 Web 服务器实例的多个服务器块,因此它需要一个程序来确定哪些服务器块将被用来满足请求。

Nginx在这个过程中关注的主要服务器封锁指令是倾听指令和服务器名称指令。

通过倾听指令查找可能的比赛

首先, Nginx 查看请求的 IP 地址和端口,并与每个服务器的倾听指令相匹配,以构建可能解决请求的服务器块列表。

默认情况下,任何不包含倾听指令的服务器块都被赋予了0.0.0.0.0:80的倾听参数(或0.0.0.0:80如果 Nginx 由正常的非root用户运行)。

倾听指令可以设置为:

  • IP 地址/端口组合
  • 一个单独的 IP 地址,然后在默认端口 80 上聆听
  • 一个单独的端口,将聆听该端口上的每个接口
  • 通往 Unix 接口的路径

最后一个选项通常只会在不同服务器之间传输请求时产生影响。

在试图确定哪个服务器块要发送请求时,Nginx首先会试图根据倾听指令的具体性,使用以下规则来决定:

*Nginx翻译出所有"不完整"的"倾听"指令,将缺失的值取而代之,取而代之的是默认值,这样每个块都可以被其IP地址和端口评价. 这些译名的一些例子有:

  • 一个没有倾听'指令的块使用0.0.0.0:80'的值.(_) ) - 设置到IP地址111.11.11.111'的块会变成111.11.111:80'
  • 设置到"8888"的块会变成`0.0.0.0:8888'
  • Nginx随后试图收集一个最具体地基于IP地址和端口匹配请求的服务器块列表. 这意味着任何功能上使用"0.0.0.0"作为其IP地址的块(以匹配任何接口),如果有匹配的块列出特定的IP地址,则将不会被选中. 无论如何,港口必须精确匹配。 () ( )* 如果只有一个最具体的匹配,该服务器块将被用于服务请求. 如果有多个服务器块具有相同程度的特异性匹配,Nginx随后开始评价每个服务器块的"server_name"指令. () (英语)

重要的是要理解, Nginx 只会在需要区分符合倾听指令相同级别的服务器块时对server_name指令进行评估,例如,如果 example.com 被托管在 192.168.1.10 端口 80 上,则在本示例中第一个区块将始终为 example.com 提供请求,尽管第二个区块中的 server_name 指令。

 1server {
 2    listen 192.168.1.10;
 3
 4    . . .
 5
 6}
 7
 8server {
 9    listen 80;
10    server_name example.com;
11
12    . . .
13
14}

如果多个服务器块与相同的特定性相匹配,下一步是检查server_name指令。

解析server_name指令以选择匹配

接下来,为了进一步评估具有同样特定的倾听指令的请求, Nginx 会检查请求的主机标题,该值包含客户端实际试图访问的域或 IP 地址。

Nginx 试图通过在每个仍然是选择候选人的服务器块中查看server_name指令来找到其发现的值的最佳匹配。

  • Nginx 将首先尝试找到一个带有 server_name 的服务器块, 与请求的 host 标题中的值相匹配_ 准确_ 。 如果找到此选项, 将使用关联块为请求服务 。 如果找到多个精确匹配,则使用 ** 第一位 ** 。
  • 如果找不到精确的匹配, Nginx 将尝试找到一个使用主通配符匹配的 服务器_ name 服务器块( 在配置中的名称开头用 Q_ 表示) 。 如果找到一个,该块将被用来满足请求。 如果找到多个匹配,将使用最长的匹配来服务请求。 (_) ( )* 如果没有使用主通配符找到匹配,Nginx则会寻找一个使用后导通配符匹配的 server_name的服务器块(在配置中以 Q 结尾的服务器名表示)。 如果找到,则使用该块满足请求。 如果找到多个匹配,将使用最长的匹配来服务请求。 () ( )* 如果没有使用后导通配符找到匹配,Nginx则使用正则表达式(在名称前用QQ表示)来评价定义"server_name"的服务器块. ** 第一位** 有正则表达式的`server_name',与"Host"标题相匹配,将被用于服务请求. () ( )* 如果没有找到正则表达式匹配, Nginx 然后为该 IP 地址和端口选择默认服务器块 。 (_) (英语)

每个 IP 地址/端口组合都具有默认服务器块,在无法通过上述方法确定操作过程时使用。对于 IP 地址/端口组合,这要么是配置中的第一个块,要么是包含默认_服务器选项作为倾听指令的一部分的块(这将超越第一个发现的算法)。

例子

如果定义的server_nameHost 标题值完全匹配,则该服务器块被选中以处理请求。

在本示例中,如果请求的主机标题被设置为host1.example.com,则将选择第二个服务器:

 1server {
 2    listen 80;
 3    server_name *.example.com;
 4
 5    . . .
 6
 7}
 8
 9server {
10    listen 80;
11    server_name host1.example.com;
12
13    . . .
14
15}

如果没有找到确切的匹配, Nginx 会检查是否有server_name 与一个开始的 wildcard 匹配。

在本示例中,如果请求有一个主机标题,即www.example.org,则将选择第二个服务器块:

 1server {
 2    listen 80;
 3    server_name www.example.*;
 4
 5    . . .
 6
 7}
 8
 9server {
10    listen 80;
11    server_name *.example.org;
12
13    . . .
14
15}
16
17server {
18    listen 80;
19    server_name *.org;
20
21    . . .
22
23}

如果没有找到与一张开始的野牌的匹配,Nginx会看到在表达的末尾是否存在使用野牌的匹配,在此时,将选择以野牌结束的最长匹配来满足请求。

例如,如果请求有一个主机标题设置为www.example.com,将选择第三个服务器块:

 1server {
 2    listen 80;
 3    server_name host1.example.com;
 4
 5    . . .
 6
 7}
 8
 9server {
10    listen 80;
11    server_name example.com;
12
13    . . .
14
15}
16
17server {
18    listen 80;
19    server_name www.example.*;
20
21    . . .
22
23}

如果找不到野卡匹配,那么 Nginx 将继续尝试匹配使用正规表达式的 server_name 指令。

例如,如果请求的主机标题设置为www.example.com,则将选择第二个服务器块来满足请求:

 1server {
 2    listen 80;
 3    server_name example.com;
 4
 5    . . .
 6
 7}
 8
 9server {
10    listen 80;
11    server_name ~^(www|host1).*\.example\.com$;
12
13    . . .
14
15}
16
17server {
18    listen 80;
19    server_name ~^(subdomain|set|www|host1).*\.example\.com$;
20
21    . . .
22
23}

如果上述任何步骤都无法满足请求,那么请求将传送到 default 服务器以获取匹配的 IP 地址和端口。

匹配位置块

类似于 Nginx 用来选择将请求处理的服务器块的过程, Nginx 也有一个已建立的算法来决定在服务器中使用哪个位置块来处理请求。

位置区块合成

在我们讨论 Nginx 如何决定使用哪个位置块来处理请求之前,让我们来看看您在位置块定义中可能看到的一些语法:位置块生活在服务器块(或其他位置块)内,并用于决定如何处理请求 URI(域名或 IP 地址/端口之后的请求部分)。

位置块一般采取如下形式:

1location optional_modifier location_match {
2
3    . . .
4
5}

上面的location_match定义了 Nginx 应该对请求 URI 进行检查的内容。上面的示例中修改器的存在或不存在会影响 Nginx 试图匹配位置块的方式。

  • (没有):如果没有修改者存在,则该位置被解释为 prefix match. 这意味着所给出的位置将与请求 URI 的开始相匹配以确定匹配.
  • =:如果使用平等符号,则该块将被视为匹配,如果请求 URI 准确匹配给定的位置
  • ``:如果有调节器存在,则该位置将被解释为随案敏感的正规表达式匹配
  • ```:如果有卡拉特和调节器存在,并且如果此块被选择为最佳非调节表达式匹配,则该位置块将被解释为随案敏感的正规表达式匹配( _

示范位置区块语法的示例

作为前缀匹配的例子,可以选择下列位置块来响应看起来像/site,/site/page1/index.html/site/index.html的请求 URI:

1location /site {
2
3    . . .
4
5}

为了演示准确的请求 URI 匹配,这块块将始终用于响应一个看起来像 /page1 的请求 URI. 它将 **不**用于响应一个 /page1/index.html’ 请求 URI. 请记住,如果这个块被选中并使用索引页面完成请求,则将进行内部重定向到另一个位置,这将是请求的实际处理器:

1location = /page1 {
2
3    . . .
4
5}

作为一个应该被解释为案例敏感的常规表达式的位置的例子,这个区块可以用来处理对 /tortoise.jpg 的请求,但对于 /FLOWER.PNG 的请求没有:

1location ~ \.(jpe?g|png|gif|ico)$ {
2
3    . . .
4
5}

一个允许类似于上面的案例不敏感匹配的区块如下所示:在这里,既 /tortoise.jpg and /FLOWER.PNG 都可以由这个区块处理:

1location ~* \.(jpe?g|png|gif|ico)$ {
2
3    . . .
4
5}

最后,如果确定是最佳的非规则表达式匹配,这个块将防止正常表达式匹配发生,它可以处理对 `/costumes/ninja.html 的请求:

1location ^~ /costumes {
2
3    . . .
4
5}

正如你所看到的,修改器表明应该如何解释位置块,但是,这不会告诉我们 Nginx 使用的算法来决定哪个位置块发送请求。

Nginx如何选择使用哪个位置处理请求

Nginx选择将用来服务请求的位置,类似于它选择服务器块的方式。它通过一个过程来确定给定请求的最佳位置。

考虑到我们上面描述的位置声明类型,Nginx通过将请求URI与每个位置进行比较来评估可能的位置背景。

  • Nginx开始检查所有基于前缀的位置匹配(所有位置类型不涉及正则表达式). 它对照完整的请求URI检查每个地点.(_ ( )* 首先,Nginx寻找精确的匹配。 如果发现使用 QQ 修改符的位置块与请求 URI 完全匹配,则立即选择此位置块为请求服务. (_) ( )* 如果没有找到精确(带有 QQ 修改符) 位置块匹配, Nginx 则继续评估非精确的前缀 。 它发现了给定请求URI最长的匹配前缀位置,然后评价如下:
  • 如果最长的匹配前缀位置有QQ修改器,那么Nginx将立即结束搜索并选择此位置为请求服务. (
  • 如果最长的匹配前缀位置_ does not_使用 QQ 修改符,则匹配符由 Nginx 存储, 暂时可以转移搜索焦点. () ( )* 在最长的匹配前缀位置被确定并存储后,Nginx继续去评价正则表达式位置(既有大小写敏感也有不敏感). 如果有任何正则表达式位置 在_ 最长的匹配前缀位置之内, Nginx 将把这些位置移动到 regex 位置列表的首位以检查 。 然后Nginx试图与正则表达式位置相依相匹配. ** 第一个** 符合请求的正则表达式位置被立即选中,以服务请求。 (_) ( )* 如果找不到符合请求URI的正则表达式位置,则选择先前存储的前缀位置服务请求. (单位:千美元) (英语)

重要的是要理解,在默认情况下, Nginx 将提供常规表达式匹配,而不是前缀匹配,然而,它首先评估前缀位置,允许管理员通过使用 =^~ 修改器来排除这种趋势。

重要的是要注意的是,虽然前缀位置通常根据最长、最具体的匹配进行选择,但当找到第一个匹配位置时,定期表达式评估会停止,这意味着配置中的定位对常规表达式位置具有巨大的影响。

最后,重要的是要理解,当 Nginx 评估 regex 位置时,最长前缀匹配的常规表达式匹配将跳过线路。这些将被评估,顺序,在考虑其他常规表达式匹配之前。

什么时候位置封锁评估跳到其他位置?

一般来说,当一个位置块被选中以服务请求时,请求从这一点开始完全在该背景下处理,只有选择的位置和继承的指令决定了请求的处理方式,而不会受到兄弟位置块的干扰。

虽然这是一个一般规则,允许您以可预测的方式设计您的位置块,但重要的是要意识到有时,在所选位置内某些指令会触发新的位置搜索。

一些可能导致这种类型的内部重定向的指令是:

    • 指数 **
  • 文件 **
  • 重写 **
  • 错误_页面 **

让我们简短地谈谈这些。

索引指令总是导致内部重定向,如果它被用来处理请求。精确的位置匹配通常被用来通过立即结束算法的执行来加速选择过程。

在本示例中,第一个位置与/exact的请求URI匹配,但为了处理请求,由区块继承的索引指令启动了对第二个区块的内部重定向:

 1index index.html;
 2
 3location = /exact {
 4
 5    . . .
 6
 7}
 8
 9location / {
10
11    . . .
12
13}

在上述情況下,如果您真的需要執行才能留在第一個區塊中,您將不得不想出一個不同的方法來滿足對目錄的要求,例如,您可以為該區塊設定一個無效的「索引」,然後啟用「自動索引」:

 1location = /exact {
 2    index nothing_will_match;
 3    autoindex on;
 4}
 5
 6location  / {
 7
 8    . . .
 9
10}

这是防止一个索引切换语境的一种方法,但对于大多数配置来说可能并不有用. 大多数情况下,对目录的准确匹配对于重写请求(这还会导致新的位置搜索)有帮助。

另一个可以重新评估处理位置的例子是try_files指令,该指令要求 Nginx 检查一个命名的文件或目录集的存在,最后一个参数可能是 Nginx 将内部重定向的 URI。

考虑下面的配置:

1root /var/www/main;
2location / {
3    try_files $uri $uri.html $uri/ /fallback/index.html;
4}
5
6location /fallback {
7    root /var/www/another;
8}

在上面的示例中,如果对 /blahblah.html 提出请求,第一个位置会得到请求,它会尝试在 /var/www/main' 目录中找到一个名为 blahblah.html 的文件,如果无法找到一个,它会搜索一个名为 blahblah.html' 的文件,然后会尝试在 /var/www/main' 目录中找到一个名为 blahblah/的目录,如果所有这些尝试都失败,它会重定向到/fallback/index.html。 这将引发另一个位置搜索,这将被第二个位置块捕获。这将为 `/var/www/anotherfall/back/index.html’ 文件提供服务。

当使用最后参数与重写指令时,或当不使用任何参数时,Nginx将根据重写的结果搜索新的匹配位置。

例如,如果我们修改最后一个示例以包括重写,我们可以看到,请求有时会被直接传送到第二个位置,而不依赖‘try_files’指令:

1root /var/www/main;
2location / {
3    rewrite ^/rewriteme/(.*)$ /$1 last;
4    try_files $uri $uri.html $uri/ /fallback/index.html;
5}
6
7location /fallback {
8    root /var/www/another;
9}

在上面的例子中,对/rewriteme/hello的请求将最初由第一个位置块处理,它将被重写为/hello并搜索一个位置,在这种情况下,它将再次匹配第一个位置,并像往常一样被try_files处理,如果没有找到任何东西,可能将返回/fallback/index.html (使用我们上面讨论的try_files内部重定向)。

但是,如果对/rewriteme/fallback/hello进行请求,那么第一个块将再次匹配。

在发送301302状态代码时,会发生返回指令的相关情况。在这种情况下,不同之处在于它以外部可见的重定向形式产生了一个全新的请求。在使用重定向永久旗帜时,也可以发生重定向指令的相同情况。

error_page 指令可能导致内部重定向,类似于由 try_files 创建的指令。 该指令用于定义当遇到某些状态代码时应该发生什么。

考虑这个例子:

1root /var/www/main;
2
3location / {
4    error_page 404 /another/whoops.html;
5}
6
7location /another {
8    root /var/www;
9}

每个请求(除了那些从/another开始的请求)都将由第一个块处理,该块将服务于/var/www/main中的文件,但是,如果没有找到一个文件(404状态),将发生内部重定向到/another/whoops.html,导致一个新的位置搜索,最终会降落在第二个块上。

正如您所看到的,了解 Nginx 触发新位置搜索的情况可以帮助预测您在提出请求时会看到的行为。

结论

了解 Nginx 处理客户端请求的方式可以使您作为管理员的工作变得更容易。您将能够知道 Nginx 根据每个客户端请求选择哪个服务器块。您还将能够根据请求 URI 选择位置块的方式。

Published At
Categories with 技术
Tagged with
comments powered by Disqus