1 - HTTP负载均衡
注:内容翻译自Nginx官网文档 Using nginx as HTTP load balancer。
介绍
在多个应用实例间做负载均衡是一个被广泛使用的技术,用于优化资源效率,最大化吞吐量,减少延迟和容错。
nginx可以作为一个非常高效的HTTP负载均衡器来分发请求到多个应用服务器,并提高web应用的性能,可扩展性和可靠性。
负载均衡方法
nginx支持以下负载均衡机制(或者方法):
- round-robin/轮询: 到应用服务器的请求以round-robin/轮询的方式被分发
- least-connected/最少连接:下一个请求将被分派到活动连接数量最少的服务器
- ip-hash/IP散列: 使用hash算法来决定下一个请求要选择哪个服务器(基于客户端IP地址)
默认负载均衡配置(轮询)
nginx中最简单的负载均衡配置看上去大体如下:
http {
upstream myapp1 {
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
}
}
}
在上面的例子中, 同一个应用有3个实例分别运行在srv1-srv3。当没有特别指定负载均衡方法时, 默认为round-robin/轮询。所有请求被代理到服务器集群myapp1, 然后nginx实现HTTP负载均衡来分发请求。
在nginx中反向代理的实现包括HTTP, HTTPS, FastCGI, uwsgi, SCGI, 和 memcached的负载均衡。
要配置负载均衡用HTTPS替代HTTP,只要使用"https"作为协议即可。
为FastCGI, uwsgi, SCGI, 或 memcached 搭建负载均衡时, 只要使用相应的fastcgi_pass, uwsgi_pass, scgi_pass, 和 memcached_pass指令。
最少连接负载均衡
另一个负载均衡方式是least-connected/最少连接。当某些请求需要更长时间来完成时,最少连接可以更公平的控制应用实例上的负载。
使用最少连接负载均衡时,nginx试图尽量不给已经很忙的应用服务器增加过度的请求, 而是分配新请求到不是那么忙的服务器实例。
nginx中通过在服务器集群配置中使用least_conn指令来激活最少连接负载均衡方法:
upstream myapp1 {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
会话持久化(ip-hash)
请注意,在轮询和最少连接负载均衡方法中,每个客户端的后续请求被分派到不同的服务器。对于同一个客户端没有任何方式保证发送给同一个服务器。
如果需要将一个客户端绑定给某个特定的应用服务器——用另一句话说,将客户端会话"沾住"或者"持久化",以便总是能选择特定服务器——,那么可以使用ip-hash负载均衡机制。
使用ip-hash时,客户端IP地址作为hash key使用,用来决策选择服务器集群中的哪个服务器来处理这个客户端的请求。这个方法保证从同一个客户端发起的请求总是定向到同一台服务器,除非服务器不可用。
要配置使用ip-hash负载均衡,只要在服务器集群配置中使用ip_hash指令:
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
带权重的负载均衡
可以通过使用服务器权重来影响nginx的负载均衡算法。
在上面的例子中,服务器权重没有配置,这意味着所有列出的服务器被认为对于具体的负载均衡方法是完全平等的。
特别是轮询,分派给服务器的请求被认为是大体相当的——假设有足够的请求,并且这些请求被以同样的方式处理而且完成的足够快。
当服务器被指定weight/权重参数时,负载均衡决策会考虑权重。
upstream myapp1 {
server srv1.example.com weight=3;
server srv2.example.com;
server srv3.example.com;
}
With this configuration, every 5 new requests will be distributed across the application instances as the following: 3 requests will be directed to srv1, one request will go to srv2, and another one — to srv3.
在这个配置中,每5个新请求将会如下的在应用实例中分派: 3个请求分派去srv1,一个去srv2,另外一个去srv3.
在最近的nginx版本中,可以类似的在最少连接和IP哈希负载均衡中使用权重。
健康检查
nginx中的反向代理实现包含in-band/带内(或者说被动)的服务器健康检查。如果某台服务器响应失败,nginx将标记这台服务器为"失败",之后的一段时间将尽量避免选择这台服务器来处理后续请求。
max_fails 指令设置在fail_timeout时间内和服务器通讯连续不成功尝试的数量。默认,max_fails设置为0.如果设置为0, 则关闭这台服务器的健康检查。fail_timeout参数同样也定义了服务器被标记为"失败"的时间长度。
在服务器失败之后的fail_timeout间隔时间后, nginx会开始温和的用来自实际客户端的请求来探测服务器。如果探测成功, 服务器将被标记是存活。
更多
此外,在nginx中还有更多的指令和参数可以控制服务器负载均衡。例如:proxy_next_upstream, backup, down, 和 keepalive。请查阅我们的参考文档来获取更多信息。
注: 后面有翻译的 HTTP Upstream模块 文档。
最后, 应用负载均衡,应用健康检查, 活动监控和on-the-fly 服务器集群重配置在付费的nginx plus中提供。
2 - nginx如何处理请求
注:内容翻译自Nginx官网文档 How nginx processes a request。
基于名称的虚拟服务器
nginx首先要决定哪个服务器应该处理请求。让我们从一个简单的配置开始,三个虚拟服务器都监听在端口*:80:
server {
listen 80;
server_name example.org www.example.org;
...
}
server {
listen 80;
server_name example.net www.example.net;
...
}
server {
listen 80;
server_name example.com www.example.com;
...
}
在这个配置中,nginx仅仅检验请求header中的"Host"域来决定请求应该路由到哪个服务器。如果它的值不能匹配任何服务器,或者请求完全没有包含这个header域,那么nginx将把这个请求路由到这个端口的默认服务器。在上面的配置中,默认服务器是第一个 - 这是nginx标准的默认行为。也可以通过listen指令的default_server属性来显式的设置默认服务器:
server {
listen 80 default_server;
server_name example.net www.example.net;
...
}
default_server 参数从版本0.8.21开始可用,在更早的版本中要使用default参数。
注意默认服务器是监听端口的一个属性,而不是服务器名称。后面会有更多描述。
如何防止使用未定义的服务器名称来处理请求
如果容许请求没有"Host" header 域,放弃这些请求的服务器可以定义为:
server {
listen 80;
server_name "";
return 444;
}
这里,服务器名称被设置为空字符串,这样将匹配没有"Host"header域的请求, 并返回一个特殊的nginx的非标准码404,然后关闭连接。
从版本0.8.48开始,这是服务器名称的默认设置, 因此server_name ““可以不用写。在更早的版本中,机器的hostname被用作默认服务器名称。
基于名称和基于IP混合的虚拟服务器
让我们看一下更复杂的配置,有一些虚拟服务器监听在不同的地址:
server {
listen 192.168.1.1:80;
server_name example.org www.example.org;
...
}
server {
listen 192.168.1.1:80;
server_name example.net www.example.net;
...
}
server {
listen 192.168.1.2:80;
server_name example.com www.example.com;
...
}
在这个配置中,nginx首先通过server块的listen指令检验请求的IP地址和端口。然后在通过server块的server_name入口检验请求的"Host"header域。如果服务器名称没有找到,请求将被默认服务器处理。例如,在端口192.168.1.1:80接收到的去www.example.com的请求将被端口192.168.1.1:80的默认服务器处理。这里是第一个服务器,因为这个端口没有定义www.example.com。
前面已经提到,默认服务器是监听端口的属性,并且不同的端口可以定义不同的默认服务器:
server {
listen 192.168.1.1:80;
server_name example.org www.example.org;
...
}
server {
listen 192.168.1.1:80 default_server;
server_name example.net www.example.net;
...
}
server {
listen 192.168.1.2:80 default_server;
server_name example.com www.example.com;
...
}
一个简单的PHP站点配置
现在让我们看一下nginx如何选择location来为典型而简单的PHP站点处理请求:
server {
listen 80;
server_name example.org www.example.org;
root /data/www;
location / {
index index.html index.php;
}
location ~* \.(gif|jpg|png)$ {
expires 30d;
}
location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
include fastcgi_params;
}
}
nginx首先搜索由书面字符串给定的最为特别的前缀location,无视列表顺序。在上面的配置中仅有一个带前缀的location “/",并且因为它匹配任何请求,它将用于作为最后的对策。然后nginx检查通过正则表达式给定的location,基于在配置文件中列出的顺序。第一个匹配的表达式将停止搜索然后nginx将使用这个location。如果没有正则表达式匹配请求,则nginx将使用前面发现的最为特别的前缀location。
注意所有类型的location仅仅检验请求行(HTTP中的request line)中的URL部分,不带参数。这是因为请求字符串中的参数可以以多种方式给出,例如:
/index.php?user=john&page=1
/index.php?page=1&user=john
还有,任何人都可能使用这样的查询字符串来请求:
/index.php?page=1&something+else&user=john
现在让我们来看在上面的配置中请求将如何被处理:
- 请求“/logo.gif” 首先匹配前缀location “/” 然后匹配正则表达式“.(gif|jpg|png)$”, 因此, 它被后面的location处理。使用指令“root /data/www”,请求被映射到文件/data/www/logo.gif, 然后文件被发送到客户端。
- 请求 “/index.php” 同样首先匹配到前缀location “/” 然后匹配正则表达式“.(php)$”. 因此, 它被后面的location处理, 请求被分派到监听在localhost:9000的FastCGI服务器。 fastcgi_param 指令设置FastCGI 参数 SCRIPT_FILENAME 为 “/data/www/index.php”, 然后FastCGI 服务器执行这个文件. 变量 $document_root 等同于root指令的值,而变量$fastcgi_script_name 等同于请求 URI, 例如 “/index.php”.
- 请求 “/about.html” 仅仅匹配前缀location“/”, 因此, 它在这个location中被处理. 通过指令 “root /data/www” 请求被映射为文件/data/www/about.html, 然后这个文件被发送到客户端。
- 处理请求 “/”要更复杂一些. 它仅仅匹配前缀location“/”, 因此, 它在这个location中被处理. 然后index指令根据它的参数和“root /data/www”指令来检验index文件的存在。如果文件/data/www/index.html不存在,而文件 /data/www/index.php 存在, 则指令执行一个内部重定向到“/index.php”, 然后 nginx 重新搜索location就像这个请求是从客户端发过来一样。如我们前面所见,这个重定向请求最终将被FastCGI服务器处理。
3 - 服务器名称
注:内容翻译自Nginx官网文档 Server Name。
服务器名称通过使用server_name指令来定义并决定哪个服务器块(server block)将用于给定的请求。参考"How nginx processes a request"。
注: 中文翻译版本 nginx如何处理请求。
可以使用精确名称,通配符和正则表达式:
server {
listen 80;
server_name example.org www.example.org;
...
}
server {
listen 80;
server_name *.example.org;
...
}
server {
listen 80;
server_name mail.*;
...
}
server {
listen 80;
server_name ~^(?<user>.+)\.example\.net$;
...
}
当通过名称搜索虚拟服务器时, 如果名字和多个指定的变量匹配, 例如同时匹配通配符和正则表达式,在下面的优先级次序中,第一个匹配的变量将被选择:
- 精确名称
- 星号开头的最长的通配符名称, 例如 “*.example.org”
- 星号结束的最长的通配符名称, 例如 “mail.*”
- 第一个匹配的正则表达式(按照出现在配置文件中的顺序)
通配符名称
通配符名称可以在名称的开头和结尾包含星号,并且只能紧挨着点号(.)。名称"www..example.org"和"w.example.org"是不合法的。当然,这些名字可以用正则表达式来指定,例如:"~^www..+.example.org$" 和 “~^w..example.org$”. 星号可以匹配多个名称部位,名称 “.example.org” 不仅可以匹配 www.example.org 还可以匹配 www.sub.example.org.
“.example.org"这种特殊的通配符名称可以用于匹配精确名称"example.org"和通配符名称”*.example.org".
正则表达式名称
nginx所用的正则表达式兼容于Perl编程语言(PCRE)。为了使用正则表达式, 服务器名称必须以波浪号(~)开头:
server_name ~^www\d+\.example\.net$;
否则将被当成是精准名称,或者如果表达式中包含星号就被当成通配符名称(而且大都被认为时不合法).不要忘记设置"^“和”$“锚点。虽然语法上没要求,但是逻辑上需要他们。还要注意域名的点号要使用反斜杠做转义。包含字符”{“和”}“的正则表达式需要使用引号:
server_name "~^(?<name>\w\d{1,3}+)\.example\.net$";
否则nginx会启动失败并显示错误信息:
directive "server_name" is not terminated by ";" in ...
被命名的正则表达式捕获器可以随后作为变量使用:
server {
server_name ~^(www\.)?(?<domain>.+)$;
location / {
root /sites/$domain;
}
}
PCRE 类库使用下列语法来支持命名捕获器:
?<name> Perl 5.10 兼容语法, 从PCRE-7.0开始支持
?'name' Perl 5.10 兼容语法, 从PCRE-7.0开始支持
?P<name> Python 5.10 兼容语法, 从PCRE-4.0开始支持
五花八门的名称
有一些服务器名称需要特别对待。
如果一个非"default"的服务器块需要处理不带"Host” header的请求, 需要指定一个空的名称:
server {
listen 80;
server_name example.org www.example.org "";
...
}
服务器块中如果没有定义server_name,那么nginx将使用空名称作为服务器名。
直到0.8.48版本,nginx在这种情况下使用机器的hostname作为服务器名称。如果服务器名称被定义为"$hostname"(0.9.4), 使用机器的hostname。
如果某些请求使用IP地址替代服务器名称, 请求的"Host" header将包含IP地址, 使用IP地址作为服务器名称可以处理这些请求:
server {
listen 80;
server_name example.org
www.example.org
""
192.168.1.1
;
...
}
在这个匹配所有的服务器例子中, 可以看到一个奇怪的名称"_":
server {
listen 80 default_server;
server_name _;
return 444;
}
这个名字没有任何特别之处,它仅仅是无数从来不和实际名称相交的非法域名中的一个。其他非法名称类似"–" 和 “!@#"。
0.6.25版本之前的nginx支持特殊的名称”",被错误的理解为是一个匹配所有的名称,但是实际上从来没有工作过。相反,这个功能现在是通过server_name_in_redirect指令来提供的。特殊名称"“现在被废弃,应该使用server_name_in_redirect指令。注意使用server_name指令时是没有方法可以指定匹配所有的名称或者默认服务器的。这是listen指令的一个属性,而不是server_name指令。请参考How nginx processes a request。可以定义服务器监听于端口*:80和*:8080,并指示某个成为端口*:8080的默认服务器,而其他成为端口*:80的默认服务器:
server {
listen 80;
listen 8080 default_server;
server_name example.net;
...
}
server {
listen 80 default_server;
listen 8080;
server_name example.org;
...
}
优化
精确名称,以星号开头的通配符名称和以星号结束的通配符名称存储于绑定在监听端口上的三个hash表中。hash表的大小在配置阶段做了优化以便可以以最大的CPU缓存命中来查找服务器。构建hash table的细节在单独的文档中提供。
精确名称的hash表被第一个搜索。如果名称没有找到,继续搜索用星号开头的通配符名称的hash表。如果名字还没有找到,继续搜索用星号结束的通配符名称的hash表。
搜索通配符名称哈希表比搜索精确名称哈希表要慢,因为名称是通过域名部分来搜索的。注意特殊形式的通配符”.example.org"是存储在通配符名称哈希表而不是精确名称哈希表。
正则表达式是逐个检验的,因此是最慢的方法,而且不可扩展。
处于这些理由, 最好能尽可能的使用精确名称。例如, 如果一个服务器最频繁的请求名称是example.org和www.example.org, 显式定义他们将更有效率:
server {
listen 80;
server_name example.org www.example.org *.example.org;
...
}
than to use the simplified form:
server {
listen 80;
server_name .example.org;
...
}
如果定义有大量的服务器名称,或者定义有非正常的长服务器名称, 有必要在http级别调整 server_names_hash_max_size 和 server_names_hash_bucket_size 指令。server_names_hash_bucket_size指令的默认值可能是32,或者64,或者其他值,取决于CPU cache line的大小。如果默认值是32而服务器名被定义为"too.long.server.name.example.org", 那么nginx在启动时会失败并显示错误信息:
could not build the server_names_hash,
you should increase server_names_hash_bucket_size: 32
在这种情况下, 指令值应该增加到下一个级别:
http {
server_names_hash_bucket_size 64;
...
如果定义有大量的服务器名称,会出现另外一个错误:
could not build the server_names_hash,
you should increase either server_names_hash_max_size: 512
or server_names_hash_bucket_size: 32
在这种情况下,先尝试将server_names_hash_max_size设置为接近服务器名称的数量。只有在这种方式无效,或者nginx的启动时间长的不可接收时,尝试增加server_names_hash_bucket_size。
如果某台服务器是该监听接口唯一的服务器,则nginx将完全不检验服务器名字(也不会为监听端口构建哈希表)。但是,有一个例外。如果服务器名称是带有捕获器的正则表达式, 那么nginx将不得不执行表达式以便获取捕获的内容。
兼容性
- 特殊服务器名称"$hostname"从0.9.4版本开始支持
- 在0.8.48版本之后,默认服务器名称是空名称""
- 被命名的正则表达式服务器名称捕获器从0.8.25版本开始支持
- 正则表达式服务器名称捕获器从0.7.40版本开始支持
- 空服务器名称"“从0.7.12版本开始支持
- 从0.6.25版本开始支持使用通配符服务器名称或者正则表达式名称作为第一个服务器名称
- 从0.6.7版本开始支持使用正则表达式名称
- 从0.6.0版本开始支持"example.*“形式的通配符
- 从0.3.18版本开始支持特别格式.example.org
- 从0.1.13版本开始支持通配符形式*.example.org