了解Nginx的HTTP代理功能,负载均衡,Buffering和 Caching

Nginx的是一个高性能的反向代理服务器和Web服务器。在本指南中,我们将探讨的Nginx的HTTP代理和负载平衡功能。我们将介绍nginx怎么可以使用缓冲区和缓存来提高客户端的代理经验。

介绍

在本指南中,我们将讨论Nginx的http代理功能,它允许Nginx将请求传递到后端http服务器进行进一步处理。 Nginx通常被设置为反向代理解决方案,以帮助扩展基础设施或将请求传递到其他未设计为处理大客户端负载的服务器。 一路上,我们将讨论如何使用Nginx的内置负载平衡功能向外扩展。我们还将探讨缓冲和缓存,以提高客户端代理操作的性能。

一般代理信息

如果您过去仅使用过简单的单服务器配置的Web服务器,您可能会想知道为什么需要代理请求。 代理到Nginx的其他服务器的一个原因是能够扩展您的基础设施。 Nginx被构建为同时处理许多并发连接。这使其成为客户的联系点的理想选择。服务器可以将请求传递给任意数量的后端服务器来处理大量的工作,这将负载分散在整个基础架构中。此设计还为您提供了灵活性,可轻松添加后端服务器或根据需要进行维护。 另一个http代理可能有用的实例是使用应用程序服务器,这些应用程序服务器可能不是用于直接处理生产环境中客户端的请求。许多框架包括Web服务器,但是大多数框架不如针对高性能(如Nginx)设计的服务器那么强大。将Nginx放在这些服务器的前面可以为用户带来更好的体验,并提高安全性。 在Nginx中的代理是通过操纵针对Nginx服务器的请求并将其传递到其他服务器用于实际处理来实现的。请求的结果传递回Nginx,然后Nginx将信息中继到客户端。这个实例中的其他服务器可以是远程机器,本地服务器,或者甚至Nginx中定义的其他虚拟服务器。这Nginx的请求代理的服务器被称为上游服务器 。 Nginx可以代理请求到使用http(s),FastCGI,SCGI和uwsgi或memcached协议通信的服务器,通过为每种类型的代理的单独的指令集。在本指南中,我们将关注http协议。 Nginx实例负责传递请求并将任何消息组件按摩到上游服务器可以理解的格式。

解构基本HTTP代理通行证

最直接的代理类型涉及将请求切换到可以使用http进行通信的单个服务器。 这种类型的代理被称为通用的“代理通行证”,并通过适当命名的处理proxy_pass指令。 该proxy_pass指令主要存在于位置上下文。 它也是有效的if一个位置上下文内,并且在块limit_except上下文。 当一个请求具有一个位置匹配proxy_pass内部指令,该请求被转发到由指令给出的URL。 让我们来看一个例子:
# server context

location /match/here {
    proxy_pass http://example.com;
}

. . .

在上述结构片断,没有URI被在所述的服务器端给出proxy_pass定义。对于适合此模式的定义,客户端请求的URI将按原样传递到上游服务器。 例如,当一个请求/match/here/please是由该块的处理,请求URI将被发送到example.com服务器http://example.com/match/here/please 。 让我们来看看另一种情况:
# server context

location /match/here {
    proxy_pass http://example.com/new/prefix;
}

. . .
在上面的例子中,代理服务器与在末端URI段(定义/new/prefix )。 当一个URI中给出proxy_pass定义,该位置定义相匹配的请求的部分被此URI通期间更换。 例如,对于请求/match/here/please Nginx的服务器将被传递到上游服务器http://example.com/new/prefix/please 。 在/match/here被替换/new/prefix 。这是一个重要的要点。 有时,这种替换是不可能的。在这些情况下,该URI在的端proxy_pass定义将被忽略,或者从客户端原始URI或URI作为由其他指令修改将被传递到上游服务器。 例如,当使用正则表达式匹配位置时,Nginx不能确定URI的哪个部分与表达式匹配,因此它发送原始客户机请求URI。另一个例子是当在同一位置内使用重写指令时,导致客户端URI被重写,但仍然在同一个块中处理。在这种情况下,重写的URI将被传递。

了解Nginx如何处理标头

有一件事可能不太清楚,如果你期望上游服务器正确地处理请求,那么传递不仅仅是URI是非常重要的。来自Nginx代表客户端的请求将看起来不同于直接来自客户端的请求。这很大一部分是与请求一起的标头。 当Nginx代理请求时,它会自动对从客户端接收的请求头进行一些调整:
  • Nginx去掉任何空标头。没有将空值传递给另一个服务器的点;它只会膨胀请求。
  • Nginx默认情况下会将包含下划线的任何标题视为无效。它将从代理请求中删除这些。如果你想有Nginx的解释这些是有效的,你可以设置underscores_in_headers指令“上”,否则你的头永远不会使它的后端服务器。
  • “主机”头重新写入由定义的值$proxy_host变量。 这将是在上游的IP地址或名称和端口号,直接作为由定义的proxy_pass指令。
  • “连接”标题更改为“关闭”。该报头用于发送关于在双方之间建立的特定连接的信息。在这种情况下,Nginx将此设置为“关闭”,以向上游服务器指示,一旦原始请求被响应,该连接将被关闭。上游不应该期望此连接是持久的。
我们可以从上述推断的第一点是,该希望通过任何头应该被设置为一个空串。具有空值的标头将从传递的请求中完全删除。 第二点,从上述信息搜集的是,如果你的后台应用程序将被加工非标头,你必须确保他们没有下划线。 如果你需要使用下划线头,您可以设置underscores_in_headers指令“关于”进一步在你的配置(有效无论是在HTTP上下文或默认服务器声明的IP地址/端口组合的情况下)。如果你不这样做,Nginx会将这些头标记为无效,并默默丢弃它们,然后传递给你的上游。 “主机”头在大多数代理场景中特别重要。如上所述,在默认情况下,这将被设置为的值$proxy_host ,将包含的域名或IP地址和端口直接从所取的变量proxy_pass定义。这是默认选择,因为它是唯一的地址Nginx可以确保上游服务器响应(因为它是直接从连接信息拉)。 “主机”标题的最常见的值如下:
  • $proxy_host :这台“主机”头的域名或IP地址和端口组合取自proxy_pass定义。这是Nginx的默认和“安全”,但通常不是被代理服务器正确处理请求所需要的。
  • $http_host :将“主机”头来自客户端的请求“主机”标题。 由客户端发送的头始终在Nginx中可用作变量。 该变量将与启动$http_前缀,其次是小写头的名称,用下划线代替任何破折号。 虽然$http_host变量作品大部分时间,当客户端请求没有一个有效的“主机”头,这可能会导致传球失败。
  • $host :这个变量被设置,优先于顺序:从请求线本身的主机名,来自客户端的请求“主机”头,或服务器名称相匹配的请求。
在大多数情况下,你将要设置的“主机”头到$host变量。它是最灵活的,并且通常为代理服务器提供尽可能准确地填充的“主机”报头。

设置或重置标题

要调整或代理连接设置头,我们可以使用proxy_set_header指令。例如,要更改我们已经讨论的“主机”头,并添加一些额外的头与代理请求通用,我们可以使用这样:
# server context

location /match/here {
    proxy_set_header HOST $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_pass http://example.com/new/prefix;
}

. . .
上述要求设置“主机”头到$host变量,它应包含有关被请求原始主机的信息。 该X-Forwarded-Proto标题提供有关原始客户机请求的模式被代理服务器信息(无论是HTTP或HTTPS请求)。 所述X-Real-IP设置为客户端的IP地址,以便在代理可以正确地做出决定或日志基于该信息。 所述X-Forwarded-For头是含有每客户机已通过代理到这一点服务器的IP地址的列表。 在上面的例子中,我们将其设置为所述$proxy_add_x_forwarded_for变量。 此变量将原始的值X-Forwarded-For从客户端检索的报头,并将该Nginx的服务器的IP地址到最后。 当然,我们可以移动至proxy_set_header指示出到服务器或HTTP上下文,允许它在一个以上的位置被引用:
# server context

proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_Header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

location /match/here {
    proxy_pass http://example.com/new/prefix;
}

location /different/match {
    proxy_pass http://example.com;
}

为负载平衡定义代理连接的上游上下文

在前面的示例中,我们演示了如何对单个后端服务器执行简单的http代理。 Nginx允许我们通过指定我们可以传递请求的整个后端服务器池来轻松扩展这个配置。 我们可以通过这样做upstream指令来定义服务器池。 此配置假定任何一个列出的服务器都能够处理客户端的请求。 这使我们几乎不用努力扩展我们的基础设施。 在upstream指令必须在Nginx的配置的HTTP上下文进行设置。 让我们看一个简单的例子:
# http context

upstream backend_hosts {
    server host1.example.com;
    server host2.example.com;
    server host3.example.com;
}

server {
    listen 80;
    server_name example.com;

    location /proxy-me {
        proxy_pass http://backend_hosts;
    }
}
在上面的例子中,我们已经成立了一个名为上游方面backend_hosts 。 一旦定义,此名称将可用于代理传递中,就像它是常规域名一样。 正如你所看到的,我们的服务器模块中,我们通过提出任何请求example.com/proxy-me/...我们上面定义的池。在该池中,通过应用可配置算法来选择主机。默认情况下,这只是一个简单的循环选择过程(每个请求将依次路由到不同的主机)。

改变上游平衡算法

您可以通过在上游上下文中包括指令或标志来修改上游池使用的平衡算法:
  • (循环赛):如果没有其他平衡指令都存在,所使用的默认负载平衡算法。在上游上下文中定义的每个服务器依次顺序地传递请求。
  • least_conn :指定新的连接应始终考虑到有活动的连接数最少的后端。这在与后端的连接可能持续一段时间的情况下尤其有用。
  • ip_hash :该均衡算法分配请求基于所述客户端的IP地址不同的服务器。前三个八位字节用作决定服务器处理请求的键。结果是,客户端往往每次由相同的服务器提供服务,这可以帮助会话一致性。
  • hash :该平衡算法主要用于与memcached的代理。基于任意提供的散列键的值来划分服务器。这可以是文本,变量或组合。这是唯一需要用户提供数据的平衡方法,这是应该用于哈希的键。
当更改平衡算法时,块可能看起来像这样:
# http context

upstream backend_hosts {

    least_conn;

    server host1.example.com;
    server host2.example.com;
    server host3.example.com;
}

. . .

在上面的示例中,将基于哪个具有最少的连接来选择服务器。该ip_hash指令可以以同样的方式来获得一定量的会话“粘性”的设定。 对于hash方法,则必须提供密钥散列反对。这可以是任何你想要的:
# http context

upstream backend_hosts {

    hash $remote_addr$remote_port consistent;

    server host1.example.com;
    server host2.example.com;
    server host3.example.com;
}

. . .

以上示例将根据客户端ip地址和端口的值分发请求。我们还增加了可选的参数consistent ,它实现了ketama一致性哈希算法。基本上,这意味着如果您的上游服务器更改,将对您的缓存影响最小。

设置平衡的服务器权重

在后端服务器的声明中,默认情况下,每个服务器同样“加权”。这假设每个服务器可以并且应该处理相同的负载量(考虑到平衡算法的效果)。但是,您也可以在声明期间为服务器设置替代权重:
# http context

upstream backend_hosts {
    server host1.example.com weight=3;
    server host2.example.com;
    server host3.example.com;
}

. . .

在上面的例子中, host1.example.com将接收三次流量与其他两台服务器。默认情况下,每台服务器的权重为1。

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

涉及许多用户的代理的一个问题是向该过程添加附加服务器对性能的影响。在大多数情况下,这可以通过利用Nginx的缓冲和缓存功能大大减轻。 当代理到另一个服务器时,两个不同连接的速度将影响客户端的体验:
  • 从客户端到Nginx代理的连接。
  • 从Nginx代理到后端服务器的连接。
Nginx能够根据您想要优化的连接中的哪一个来调整其行为。 没有缓冲区,数据从代理服务器发送,并立即开始传输到客户端。如果假设客户端是快速的,则可以关闭缓冲以便尽可能快地将数据获取到客户端。使用缓冲区,Nginx代理将临时存储后端的响应,然后将此数据提供给客户端。如果客户端运行缓慢,这允许Nginx服务器更快地关闭到后端的连接。然后它可以处理以任何速度将数据分发给客户端。 Nginx默认为缓冲设计,因为客户端往往具有非常不同的连接速度。我们可以使用以下指令调整缓冲行为。这些可以在http,服务器或位置上下文中进行设置。重要的是要记住,浆纱指令每个请求的配置是非常重要的,因此,当有许多客户机请求增加它们超出了你的需要会影响你的表现:
  • proxy_buffering :这个指令控制缓冲此上下文和语境孩子是否启用。默认情况下,这是“开”。
  • proxy_buffers :这个指令控制缓冲区为代理响应数(第一个参数)和大小(第二个参数)。 默认是配置的大小等于一个内存页(或者8个缓冲4k8k )。增加缓冲区的数量可以允许缓冲更多信息。
  • proxy_buffer_size :从后端服务器,其中包含报头的响应的初始部分,分别从所述响应的其余部分的缓冲。 此指令为响应的此部分设置缓冲区的大小。 默认情况下,这将是大小相同proxy_buffers ,但因为这是用于标题信息,这通常可以设置为一个较低的值。
  • proxy_busy_buffers_size :这个指令设置,可以标注“客户就绪”,从而忙缓冲区的最大尺寸。虽然客户端一次只能从一个缓冲区读取数据,但缓冲区被放置在队列中以便以串的形式发送到客户端。此指令控制允许处于此状态的缓冲区空间的大小。
  • proxy_max_temp_file_size :这是最大的尺寸,每个请求,用于在磁盘上的临时文件。当上游响应太大而无法容纳到缓冲区中时,将创建这些值。
  • proxy_temp_file_write_size :这是数据的Nginx将写入临时文件在同一时间,当代理服务器的响应与配置的缓冲区太大的量。
  • proxy_temp_path :这是路径上盘里的Nginx应存储的任何临时文件时,从上游服务器的响应无法融入配置缓冲区的区域。
如你所见,Nginx提供了很多不同的指令来调整缓冲行为。大多数时候,你不必担心大多数这些,但它可以是有用的,调整这些值中的一些。也许最有用的调整是proxy_buffersproxy_buffer_size指令。 一个示例,增加每个上游请求的可用代理缓冲区的数量,同时减少可能存储标头的缓冲区将如下所示:
# server context

proxy_buffering on;
proxy_buffer_size 1k;
proxy_buffers 24 4k;
proxy_busy_buffers_size 8k;
proxy_max_temp_file_size 2048m;
proxy_temp_file_write_size 32k;

location / {
    proxy_pass http://example.com;
}
相反,如果你有快速客户端,你想立即提供数据,你可以完全关闭缓冲。 Nginx实际上仍然使用缓冲区,如果上游比客户端快,但它会立即尝试刷新数据到客户端,而不是等待缓冲区池。如果客户端运行缓慢,这可能导致上游连接保持打开,直到客户端可以赶上。当缓冲是“关”只能由定义的缓冲proxy_buffer_size将用于指令:
# server context

proxy_buffering off;
proxy_buffer_size 4k;

location / {
    proxy_pass http://example.com;
}

高可用性(可选)

Nginx代理可以通过添加一组冗余的负载均衡器来创建高可用性基础架构,从而使其更加健壮。 高可用性 (HA)的设置是没有单一故障点的基础设施,你的负载平衡器此配置的一部分。通过拥有多个负载均衡器,您可以防止潜在的停机时间,如果您的负载均衡器不可用或者您需要将其关闭进行维护。 以下是基本高可用性设置的图表: HA设置 在此示例中,您有多个负载平衡器(一个活动和一个或多个被动)在静态IP地址后面,可以从一个服务器重新映射到另一个。客户端请求从静态IP路由到活动负载均衡器,然后路由到您的后端服务器。要了解更多信息,请参阅如何使用浮动IP地址本节

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

虽然缓冲可以帮助释放后端服务器来处理更多的请求,Nginx还提供了一种缓存来自后端服务器的内容的方法,消除了对于许多请求连接到上游的需要。

配置代理缓存

要设置缓存来使用代理内容,我们可以使用proxy_cache_path指令。 这将创建一个可以保存从代理服务器返回的数据的区域。 该proxy_cache_path指令必须在HTTP上下文中进行设置。 在下面的例子中,我们将配置这个和一些相关的指令来设置我们的缓存系统。
# http context

proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=backcache:8m max_size=50m;
proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
随着proxy_cache_path指令,我们已经定义上,我们希望我们的存储缓存中的文件系统的目录。 在这个例子中,我们选择了/var/lib/nginx/cache目录。如果此目录不存在,您可以通过键入以下内容以正确的权限和所有权创建:
sudo mkdir -p /var/lib/nginx/cache
sudo chown www-data /var/lib/nginx/cache
sudo chmod 700 /var/lib/nginx/cache
levels=参数指定缓存将如何组织。 Nginx将通过散列键的值(下面配置)来创建缓存键。我们上面选择的级别表示将创建一个单字符目录(这将是散列值的最后一个字符)和一个两个字符的子目录(从散列值结尾的接下来的两个字符)。你通常不必关心这个的细节,但它有助于Nginx快速找到相关的值。 该keys_zone=参数定义这个缓存区,这是我们叫的名字backcache 。 这也是我们定义要存储多少元数据的地方。 在这种情况下,我们存储8 MB的密钥。 对于每个兆字节,Nginx可以存储大约8000个条目。 该max_size参数设置的实际缓存数据的最大尺寸。 我们上面所使用的另一个指令是proxy_cache_key 。这用于设置将用于存储缓存值的键。该相同的密钥用于检查是否可以从高速缓存提供请求。我们将此设置为方案(http或https),HTTP请求方法以及请求的主机和URI的组合。 该proxy_cache_valid指令可以指定多次。它允许我们配置根据状态代码存储值的时间。在我们的示例中,我们存储成功和重定向10分钟,并在每分钟404个响应过期缓存。 现在,我们已经配置了缓存区域,但我们仍然需要告诉Nginx何时使用缓存。 在我们代理到后端的位置,我们可以配置使用此缓存:
# server context

location /proxy-me {
    proxy_cache backcache;
    proxy_cache_bypass $http_cache_control;
    add_header X-Proxy-Cache $upstream_cache_status;

    proxy_pass http://backend;
}

. . .

使用proxy_cache指令,就可以指定该backcache缓存区应该用于这方面。 Nginx将在传递到后端之前检查这里是否有有效的条目。 该proxy_cache_bypass指令被设置为$http_cache_control变量。这将包含关于客户端是否明确请求资源的新的非缓存版本的指示符。设置此指令允许Nginx正确处理这些类型的客户端请求。无需进一步配置。 我们还增加称作一个额外的头部X-Proxy-Cache 。 我们这个头设置为值$upstream_cache_status变量。基本上,这设置了一个头,允许我们查看请求是否导致缓存命中,缓存未命中,或者如果缓存被明确绕过。这对于调试特别有用,但对于客户端也是有用的信息。

关于缓存结果的注意事项

缓存可以极大地提高代理的性能。但是,配置缓存时一定要牢记一些注意事项。 首先,任何用户相关的数据不应被高速缓存。这可能导致一个用户的数据被呈现给另一个用户。如果您的网站完全静态,这可能不是问题。 如果您的网站有一些动态元素,您必须在后端服务器中解决此问题。如何处理这取决于什么应用程序或服务器正在处理后端处理。对于私人内容,你应该设置Cache-Control标题为“无缓存”,“无店铺”,或“专用”具体取决于数据的性质:
  • 无缓存 :表示响应不应该再担当,而不首先检查数据没有在后台修改。如果数据是动态的和重要的,这可以使用。在每个请求上检查ETag哈希元数据头,并且如果后端返回相同的哈希值,则可以提供先前的值。
  • 无店铺 :表示在任何时候,应该接收的数据不断被缓存。这是私人数据的最安全的选择,因为它意味着必须每次从服务器检索数据。
  • 私人 :这表明没有共享的缓存空间应该缓存此数据。这对于指示用户的浏览器可以缓存数据很有用,但代理服务器不应将此数据视为对后续请求有效。
  • 公共 :这表明反应是可以在该连接的任何点被高速缓存的公共数据。
一个相关的报头,可以控制这种行为是max-age头,其中表示,任何资源应该被缓存的秒数。 根据内容的敏感度正确设置这些标头,有助于您利用缓存,同时保持您的私人数据安全,动态数据更新。 如果你的后端也使用Nginx的,你可以通过设置一些这expires指令,将设置max-ageCache-Control
location / {
    expires 60m;
}

location /check-me {
    expires -1;
}
在上述示例中,第一块允许内容被高速缓存一个小时。第二块设置Cache-Control标题为“无缓存”。 要设置其他值,可以使用add_header指令,就像这样:
location /private {
    expires -1;
    add_header Cache-Control "no-store";
}

结论

Nginx首先是一个逆向代理,这也恰好有能力作为一个Web服务器工作。由于这个设计决定,代理对其他服务器的请求是相当简单的。 Nginx非常灵活,允许对代理配置更复杂的控制,如果需要。