了解 Nginx HTTP 代理、负载平衡、缓冲和缓存

介绍

在本指南中,我们将讨论 Nginx 的 http 代理功能,这些功能允许 Nginx 将请求传送到后端 http 服务器进行进一步处理。

沿途,我们将讨论如何利用 Nginx 内置的负载平衡功能扩展,我们还将探索缓冲和缓存,以提高客户端代理操作的性能。

通用接近信息

如果你过去只使用Web服务器进行简单的单一服务器配置,你可能会想知道为什么你需要代理请求。

Nginx 向其他服务器提供代理的理由之一是能够扩展您的基础设施。 Nginx 可同时处理多个同时连接,这使其成为客户端的接触点。服务器可以向任何数量的后端服务器传递请求,以便处理大部分工作,从而在您的基础设施中分散负载。

另一个HTTP代理服务器可能有用的例子是使用应用程序服务器时,这些服务器可能无法直接处理生产环境中的客户端请求。 许多框架包括Web服务器,但大多数服务器并不像Nginx这样的高性能服务器那样强大。

在 Nginx 中,代理操作是通过操纵针对 Nginx 服务器的请求,并将其传送到其他服务器进行实际处理,请求的结果被传回 Nginx,然后将信息传递给客户端,在这种情况下,其他服务器可能是远程机器,本地服务器,甚至是 Nginx 内定义的其他虚拟服务器。

Nginx 可以向使用 http(s)、FastCGI、SCGI 和 uwsgi 进行通信的服务器进行代理请求,或通过每个类型的代理程序的单独指令集通过 memcached 协议进行通信。

解构基本的 HTTP Proxy Pass

最简单的代理类型是将请求发送给一个可以使用HTTP进行通信的服务器,这种类型的代理被称为通用代理通道,并由适当的名为proxy_pass的指令处理。

proxy_pass指令主要在位置环境中找到。它也适用于位置环境中的if块和limit_except环境中。当请求与内部的proxy_pass指令相匹配时,请求将转发到该指令给出的URL。

让我们来看看一个例子:

1# server context
2
3location /match/here {
4    proxy_pass http://example.com;
5}
6
7. . .

在上面的配置片段中,在proxy_pass定义中,在服务器的末端没有给出 URI。

例如,当这个块处理/match/here/please请求时,请求URI将以http://example.com/match/here/please的名称发送到example.com服务器。

让我们来看看替代方案:

1# server context
2
3location /match/here {
4    proxy_pass http://example.com/new/prefix;
5}
6
7. . .

在上面的示例中,代理服务器被定义为有一个 URI 细节(/new/prefix)。当一个 URI 被指定为 proxy_pass 定义时,与 location 定义相匹配的请求部分被这个 URI 取代。

例如,在 Nginx 服务器上对 /match/here/please 的请求将作为 http://example.com/new/prefix/please` 传递给上游服务器。 /match/here 将被 /new/prefix 取代。

在这些情况下,在proxy_pass定义的末尾的URI被忽略,并将来自客户端的原始URI或由其他指令修改的URI传送到上游服务器。

例如,当使用常规表达式匹配位置时, Nginx 无法确定 URI 的哪个部分匹配了表达式,因此它会发送原始客户端请求 URI。

了解 Nginx 如何处理头条

一个可能不立即清楚的事情是,如果您希望上游服务器正确处理请求,那么通过不仅仅是 URI 是很重要的。 代表客户端的 Nginx 发出的请求将与直接来自客户端的请求不同。

当 Nginx 代理请求时,它会自动对从客户端接收的请求标题进行一些调整:

  • Nginx 将任何空标题删除,没有必要将空标题传递到另一个服务器;它只会用来吹爆请求
  • 默认情况下, Nginx 将任何包含 underscores 的标题视为无效。 它将这些标题从代理请求中删除。 如果您希望 Nginx 将这些值解释为有效的,您可以将underscores_in_headers指令设置为on,否则您的标题将永远不会将其转移到后端服务器 *Host标题将重写到Header的定义值。

我们可以从上面提到的第一个点是,您不希望传递的任何标题都应该设置为空串,并且带有空值的标题将从传递请求中完全删除。

下一个要从上述信息中获取的点是,如果您的后端应用程序将处理非标准标题,您必须确保它们有标题. 如果您需要使用标题的标题,您可以在配置中进一步设置undercores_in_headers指令为On(在HTTP背景中或在IP地址/端口组合的默认服务器声明中有效)。

主机标题在大多数代理场景中尤为重要,如上所述,默认情况下,这将设置为$proxy_host,一个变量将包含域名或IP地址和直接从proxy_pass定义中取出的端口。

对于主机标题最常见的值如下:

  • $proxy_host:这将主机标题设置为域名或 IP 地址和从proxy_pass定义中采用的端口组合。 这是从 Nginx 的角度看的默认和安全,但通常不是代理服务器需要正确处理请求的标题
  • $http_host: 将主机标题设置为客户端请求的主机标题。 客户端发送的标题始终在 Nginx 中作为变量可用。 变量将从$http_``前缀开始,然后是下面的标题名称,随附下面的任何插件被替换。 尽管$http_host``变量通常工作,当客户端请求没有有效的主机标题时

在大多数情况下,您将希望将Host标题设置为$host变量,它是最灵活的,通常会为代理服务器提供尽可能准确地填写的Host标题。

设置或重置头部

要调整或设置代理连接的标题,我们可以使用proxy_set_header指令. 例如,如我们所讨论的那样,要更改主机标题,并添加一些与代理请求共同的额外标题,我们可以使用这样的东西:

 1# server context
 2
 3location /match/here {
 4    proxy_set_header HOST $host;
 5    proxy_set_header X-Forwarded-Proto $scheme;
 6    proxy_set_header X-Real-IP $remote_addr;
 7    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 8
 9    proxy_pass http://example.com/new/prefix;
10}
11
12. . .

上面的请求将Host标题设置为$host变量,该变量应该包含有关被请求的原始主机的信息。

X-Real-IP 设置为客户端的 IP 地址,以便代理人可以根据此信息正确地做出决定或登录。 X-Forwarded-For 标题是包含每个客户端到此时为止通过的服务器的 IP 地址的列表。在上面的示例中,我们将此设置为 $proxy_add_x_forwarded_for 变量。

当然,我们可以将proxy_set_header指令移动到服务器或HTTP环境中,允许它在多个位置进行引用:

 1# server context
 2
 3proxy_set_header HOST $host;
 4proxy_set_header X-Forwarded-Proto $scheme;
 5proxy_set_header X-Real-IP $remote_addr;
 6proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 7
 8location /match/here {
 9    proxy_pass http://example.com/new/prefix;
10}
11
12location /different/match {
13    proxy_pass http://example.com;
14}

定义负载平衡代理连接的上游背景

在之前的示例中,我们展示了如何对单个后端服务器进行简单的 http 代理。 Nginx 允许我们通过指定可以传递请求的整个后端服务器池来轻松扩展此配置。

我们可以通过使用上游指令来定义一群服务器来做到这一点,这种配置假定任何一个列出的服务器都能处理客户端的请求,这使我们能够几乎毫不费力地扩展我们的基础设施,而上游指令必须设置在您的 Nginx 配置的 http 环境中。

让我们来看看一个简单的例子:

 1# http context
 2
 3upstream backend_hosts {
 4    server host1.example.com;
 5    server host2.example.com;
 6    server host3.example.com;
 7}
 8
 9server {
10    listen 80;
11    server_name example.com;
12
13    location /proxy-me {
14        proxy_pass http://backend_hosts;
15    }
16}

在上面的例子中,我们设置了一个名为backend_hosts的上游背景。一旦定义,这个名称将可用于代理通道中,就好像它是一个常规域名一样。正如你所看到的,在我们的服务器块中,我们将任何向example.com/proxy-me/...提出的请求传递到我们上面定义的池中。在该池中,一个主机通过应用可配置算法进行选择。

上游平衡算法的改变

您可以通过在上游背景中包含指令或旗帜来修改上游池使用的平衡算法:

*(圆形Robin): 如果没有其他平衡指令,则使用的默认负载平衡算法。 上游上下文定义的每个服务器依次传递请求

  • 时间轴: 指定新连接应始终给予活动连接最少的后端。 在与后端的联系可能持续一段时间的情况下,这一点可能特别有用。 () ( )* **`: 这种平衡算法根据客户端的IP地址向不同的服务器分配请求. 前三个octets被用作决定服务器处理请求的密钥. 结果是客户端每次往往由同一个服务器服务,这可以帮助会议的一致性. () ( )* 说得好, 这种平衡算法主要与memcacked代理使用. 服务器按任意提供的散列键的值划分。 这可以是文字,变量,也可以是组合. 这是要求用户提供数据的唯一平衡方法,这是散列应当使用的密钥. ( (英语)

在更改平衡算法时,区块可能看起来像这样:

 1# http context
 2
 3upstream backend_hosts {
 4
 5    least_conn;
 6
 7    server host1.example.com;
 8    server host2.example.com;
 9    server host3.example.com;
10}
11
12. . .

在上面的示例中,服务器将根据哪个连接最少进行选择,可以以同样的方式设置ip_hash指令以获得一定数量的会话粘合性

至于哈希方法,您必须提供对哈希的密钥,这可以是您想要的任何东西:

 1# http context
 2
 3upstream backend_hosts {
 4
 5    hash $remote_addr$remote_port consistent;
 6
 7    server host1.example.com;
 8    server host2.example.com;
 9    server host3.example.com;
10}
11
12. . .

上面的示例将根据客户端 ip 地址和端口的值分配请求,我们还添加了可选的参数一致,该参数实现了 ketama 一致的哈希算法。

设置服务器重量以平衡

在后端服务器的声明中,默认情况下,每个服务器均为重量。这假定每个服务器可以并且应该处理相同的负载量(考虑到平衡算法的影响)。

1# http context
2
3upstream backend_hosts {
4    server host1.example.com weight=3;
5    server host2.example.com;
6    server host3.example.com;
7}
8
9. . .

在上面的示例中,host1.example.com 将比其他两个服务器获得三倍的流量. 默认情况下,每个服务器被分配一个重量。

使用缓冲器释放后端服务器

对于许多用户来说,代理服务的一个问题是将一个额外的服务器添加到进程的性能影响,在大多数情况下,通过利用 Nginx 的缓存和缓存功能,这在很大程度上可以减轻。

当向另一个服务器代理时,两个不同的连接的速度会影响客户端的体验:

  • 客户端连接到 Nginx 代理程序
  • Nginx 代理程序连接到后端服务器

Nginx有能力根据您想要优化的任何连接来调整其行为。

没有缓冲,数据从代理服务器发送,并立即开始传输给客户端。如果客户端被认为是快速的,缓冲可以被关闭,以便尽快将数据传递给客户端。使用缓冲,NGINX代理会暂时存储后端的响应,然后将这些数据传输给客户端。如果客户端是慢的,这允许NGINX服务器更快关闭连接到后端。

Nginx 默认使用缓冲设计,因为客户端的连接速度往往大不相同。我们可以通过以下指令来调整缓冲行为。这些指令可以在 http、服务器或位置环境中设置。重要的是要记住,大小指令配置为 per request,因此增加它们超出您的需求可能会影响客户端的多个请求:

  • @ proxy_buffering @ : 该指令控制是否启用了针对此背景和儿童背景的缓冲. 默认情况下,这是"On"(). ( )* : 此指令控制了缓冲器的数( 第1个参数)和大小( 第2个参数), 用于代理响应. 默认是配置大小等于一个内存页("4k"或"8k")的8个缓冲. 增加缓冲器的数量可以允许您缓冲更多信息. () ) * * +xy_缓冲器_大小 **: 后端服务器的初始响应部分包含信头,与其余响应部分分开缓冲. 此指令为这部分响应设定了缓冲大小. 默认情况下, 这将与 proxy_ buffers大小相同, 但是由于它被用于标题信息, 通常可以设定为更低的值 。 (_) ) * * +oxy_busy_buffers_大小: 此指令设定了可被标记为"客户准备"并因此繁忙的缓冲的最大大小. 虽然一个客户端只能一次从一个缓冲器读取数据,但缓冲器被放入队列中,以成群发送给客户端. 此指令控制允许在此状态的缓冲空间大小 。 (_) ) * ++oxy_max_temp_file_大小 `: 这是磁盘上一个临时文件的最大尺寸。 它们是在上游反应太大,无法装入缓冲器时产生的。 () ( )* ================================================================================================================================================================ 这是Nginx在代理服务器对所配置的缓冲器的响应太大时会写入临时文件的数据量. () ( )* ========================================================= ================================================================================================================================ 当上游服务器的响应无法匹配到所配置的缓冲器中时, Nginx 应该存储任何临时文件的磁盘上的区域路径 。 (_) (英语)

正如你所看到的, Nginx 提供了一些不同的指令来调整缓冲行为. 大多数时候,你不必担心这些指令,但调整其中一些值可能是有用的。

一个增加每个上游请求可用的代理缓冲器数量的示例,同时削减可能存储标题的缓冲器将看起来像这样:

 1# server context
 2
 3proxy_buffering on;
 4proxy_buffer_size 1k;
 5proxy_buffers 24 4k;
 6proxy_busy_buffers_size 8k;
 7proxy_max_temp_file_size 2048m;
 8proxy_temp_file_write_size 32k;
 9
10location / {
11    proxy_pass http://example.com;
12}

相比之下,如果您有快速客户端,您可以立即服务数据,您可以完全关闭缓冲。 Nginx 实际上仍然会使用缓冲器,如果上游速度比客户端快,但它会立即尝试将数据流到客户端,而不是等待缓冲器聚集。

1# server context
2
3proxy_buffering off;
4proxy_buffer_size 4k;
5
6location / {
7    proxy_pass http://example.com;
8}

高可用性(可选)

Nginx 代理可以通过添加多余的负载平衡器来提高可用性,从而创建高可用性基础设施。

一个高可用性(HA)设置是一个没有单个故障点的基础设施,而您的负载平衡器是该配置的一部分。

以下是基本高可用性设置的图表:

HA Setup

在本示例中,您有一个静态 IP 地址背后有多个负载平衡器(一个活跃和一个或多个被动)可以从一个服务器转换到另一个服务器。

配置代理缓存以减少响应时间

虽然缓存可以帮助释放后端服务器来处理更多的请求,但 Nginx 还提供了从后端服务器中缓存内容的方法,从而消除了许多请求的上游连接。

设置 proxy 缓存

要设置用于代理内容的缓存,我们可以使用proxy_cache_path指令,从而创建一个可以保留从代理服务器返回的数据的区域。

在下面的示例中,我们将配置此和一些相关指令来设置我们的缓存系统。

1# http context
2
3proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=backcache:8m max_size=50m;
4proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
5proxy_cache_valid 200 302 10m;
6proxy_cache_valid 404 1m;

使用proxy_cache_path指令,我们已经在文件系统中定义了一个目录,我们希望存储我们的缓存。在本示例中,我们选择了/var/lib/nginx/cache目录。

1sudo mkdir -p /var/lib/nginx/cache
2sudo chown www-data /var/lib/nginx/cache
3sudo chmod 700 /var/lib/nginx/cache

水平=参数规定了缓存将如何组织。 Nginx将通过缓存密钥的值来创建缓存密钥(下方配置)。我们上面选择的层次决定了一个单个字符目录(这将是 hashed 值的最后一个字符)与一个两个字符子子目录(从 hashed 值末尾接下来的两个字符)将被创建。

keys_zone=参数定义了这个缓存区域的名称,我们称之为backcache。这也是我们定义要存储多少元数据的地方。在这种情况下,我们正在存储8 MB的密钥。

我们上面使用的另一个指令是 proxy_cache_key. 这被用来设置将用于存储缓存值的密钥. 同样的密钥被用来检查是否可以从缓存中服务请求。

proxy_cache_valid指令可以多次指定,它允许我们根据状态代码配置存储值的时间。在我们的示例中,我们存储成功和重定向 10 分钟,并且每分钟都会对 404 个响应的缓存到期。

现在,我们已经配置了缓存区域,但我们仍然需要告诉 Nginx 何时使用缓存。

在我们向后端提供代理的位置,我们可以配置此缓存的使用:

 1# server context
 2
 3location /proxy-me {
 4    proxy_cache backcache;
 5    proxy_cache_bypass $http_cache_control;
 6    add_header X-Proxy-Cache $upstream_cache_status;
 7
 8    proxy_pass http://backend;
 9}
10
11. . .

使用proxy_cache指令,我们可以指定backcache缓存区域应用于此背景。

proxy_cache_bypass指令被设置为$http_cache_control变量,这将包含一个指标,表明客户端是否明确要求资源的新版本,而不是缓存。

我们还添加了一种名为X-Proxy-Cache的额外标题。我们将此标题设置为$upstream_cache_status变量值。基本上,这设置了一个标题,允许我们看到请求是否导致缓存,缓存错误,或缓存是否被明确绕过。

关于缓存结果的注意事项

缓存可以极大地提高代理的性能,但是,在配置缓存时一定要考虑一些问题。

首先,任何与用户相关的数据都应该被缓存,这可能会导致一个用户的数据被呈现给另一个用户,如果您的网站完全静态,这可能不是问题。

如果您的网站有某些动态元素,则必须在后端服务器中考虑这一点。您如何处理取决于哪个应用程序或服务器正在处理后端处理。对于私人内容,您应该将Cache-Control标题设置为No-cache,No-storePrivate,取决于数据的性质:

  • no-cache: 表示不应再次响应,而不先检查数据是否在后端没有发生变化. 如果数据是动态和重要的,可以使用此功能。 ETag hashed metadata header 在每个请求上被检查,如果后端返回相同的 hash 值,则可以使用以前的值
  • no-store: 表示在任何时候都不能将收到的数据缓存。 这是私人数据的最安全选择,因为这意味着数据必须每次从服务器中获取
  • private: 这表明没有共享缓存空间应该缓存这些数据。 这可能有助于表明用户的浏览器可以缓存数据,但代理服务器不应该认为这些数据对随后的请求有效。

可以控制这种行为的相关标题是max-age标题,该标题表示任何资源应该缓存的秒数。

正确设置这些标题,取决于内容的敏感性,将有助于您利用缓存,同时保持您的私人数据安全和动态数据新鲜。

如果您的后端也使用 Nginx,您可以使用到期指令来设置其中一些,该指令将为缓存控制设置max-age:

1location / {
2    expires 60m;
3}
4
5location /check-me {
6    expires -1;
7}

在上面的示例中,第一个区块允许内容在一个小时内缓存。第二个区块将Cache-Control标题设置为no-cache

1location /private {
2    expires -1;
3    add_header Cache-Control "no-store";
4}

结论

Nginx 首先是反向代理,它也可能具有作为 Web 服务器工作的能力. 由于这个设计决定,向其他服务器发送代理请求是相当直接的。

Published At
Categories with 技术
comments powered by Disqus