负载均衡是一种优化资源利用率、提升最大吞吐量、减少延迟、提高系统容错率的常用技术。
要使用Nginx对一组服务器的HTTP流量进行负载均衡,首先需要使用upstream定义一组后端服务器(配置于http字段中),然后使用server对upstream组中的服务进行配置(同样配置于http字段中,注意与定义虚服务的server字段区分开)。
例如,如下配置定义了一组由3台服务器组成的名为backend的资源组:
http { upstream backend { server backend1.example.com weight=5; server backend2.example.com; server 192.168.0.1 backup; } }
接下来可以使用proxy_pass指令(或者fastcgi_pass、memcached_pass、scgi_pass、uwsgi_pass,取决于所使用的协议)将http请求传递给后端服务组backend,配置如下:
http { upstream backend { server backend1.example.com weight=5; server backend2.example.com; server 192.168.0.1 backup; } server { location / { proxy_pass http://backend; } } }
此配置即为一标准的反向代理,用户向nginx发送的http请求将被代理到backend中的三台服务器上,其中192.168.0.1为备用服务器。因为upstream块中未指定负载均衡算法,所以将采用默认算法:轮询调度法。
Nginx开源版本支持4种算法,PLUS版又额外新增了两种算法。
请求按照权重平均分发给各个服务器,这种算法为默认算法,无需额外定义:
upstream backend { server backend1.example.com; server backend2.example.com; }
请求将被分发到当前活跃连接数最小的服务器,同时将权重考虑在内:
upstream backend { least_conn; server backend1.example.com; server backend2.example.com; }
根据IP地址来决定将请求分发到哪台服务器。此算法将根据IPv4的前三个8位或者整个IPv6地址计算哈希值,可确保来自同一IP的请求被分发到同一服务器上(除非该服务器不可用),可用于session保持。
upstream backend { ip_hash; server backend1.example.com; server backend2.example.com; }
如果其中一台服务器需要临时从负载均衡循环中删除,可以使用down参数对其进行标记以保留客户端IP地址的当前哈希值。该服务器的处理的请求将自动切换到组中下一台服务器:
upstream backend { server backend1.example.com; server backend2.example.com; server backend3.example.com down; }
处理请求的服务器由自定义key决定,该key可以是文本字符串,也可以是变量或者两者的组合,例如成对的IP地址和端口,或者是如下示例中的URI:
upstream backend { hash $request_uri consistent; server backend1.example.com; server backend2.example.com; }
使用consistent参数将启用ketama一致性负载均衡。根据用户定义的哈希键值,将请求平均分配到所有服务器上。如果在upstream中新增或者删减服务器,则只会有少数键值会被重新映射,从而在负载均衡缓存服务器或其他状态累计应用程序中最大程度地减少缓存未命中。
对于每个请求,Nginx将选择拥有最小平均延迟和最少活跃连接的服务器进行处理。最低平均延迟将根据如下参数进行计算:
header——从服务器接收到第一个字节的时间
last_byte——从服务器接收到完整响应的时间
last_byte inflight——从服务器接收到完整响应的时间,将不完整请求考虑在内
upstream backend { least_time header; server backend1.example.com; server backend2.example.com; }
每个请求将被随机传递给各个服务器。two参数表示Nginx将在考虑权重的情况下随机先择两台服务器,然后根据two后面指定的算法从两台服务器中选定一台服务器,支持的算法如下:
least_conn
least_time=header
least_time=last_byte
上述三种算法的含义已在上文解释,不再赘述。
upstream backend { random two least_time=last_byte; server backend1.example.com; server backend2.example.com; server backend3.example.com; server backend4.example.com; }
该算法适用于多个负载均衡器共用一组后端服务器的分布式环境中。对于负载均衡器拥有全量请求视图的一般情景,请使用其他5种负载均衡算法。
注意:upstream块中指定负载时要放在server之前。
Nginx给每台服务器分配的默认权重为1,即在轮询算法调度下,请求将被平均分配给各个服务器。而在下面的配置中:
upstream backend { server backend1.example.com weight=5; server backend2.example.com; server 192.0.0.1 backup; }
backend1.example.com的权重被设置为5,这意味着,对于每6个请求,5个将被发送给backend1.example.com,1个将被发送给backend2.example.com。而192.168.0.1因为被指定为backup,将不参与调度,除非backend1.example.com和backend2.example.com均不可用。
“慢启动(slow_start)”参数可以给从故障中恢复的节点加入调度队列一个缓冲时间,防止刚恢复的节点因访问超时而再次被标记为故障。此特性适用于Nginx PLUS版。
在Nginx PLUS版中,慢启动可以让upstream中的节点权重在其恢复后逐渐从0恢复到正常权重,此功能通过为server添加slow_start参数实现:
upstream backend { server backend1.example.com slow_start=30s; server backend2.example.com; server 192.168.0.1 backup; }
参数30s表示backend1.example.com在恢复可用的30秒后,接收的连接数达到正常值。
注意:如果upstream中只有一台服务器,那么max_fails、fail_timeout、slow_start这些参数都会被忽略,即Nginx将这台唯一的服务器视为可用的。
此功能仅适用于Nginx PLUS。会话保持意味着Nginx可以识别用户会话,并将该会话的所有请求路由到一台指定服务器上。
Nginx PLUS支持3种会话保持方法,可通过sticky参数进行指定。(如果使用的是开源版Nginx,可以使用IP哈希或一般哈希法实现会话保持)
Nginx PLUS为来自upstream组的第一个响应添加一个会话cookie,并标记发送该响应的服务器。携带此cookie值的下一客户端响应将被Nginx PLUS路由到这个被标记的服务器。
upstream backend { server backend1.example.com; server backend2.example.com; sticky cookie srv_id expires=1h domain=.example.com path=/; }
上例中,srv_id设置了cookie的名称,expires设置cookie了过期时间(即经过多久后浏览器就不再将此cookie发送给服务器),domain设置了cookie的作用域,path设置了cookie的作用路径。sticky cookie是最简单的会话保持方法。
Nginx PLUS在收到第一个请求时为此客户端分配一个“路由”,所有后续的请求将与server指令的route参数进行比较,来决定将请求代理到哪台服务器。路由信息取自cookie或者请求URI。
upstream backend { server backend1.example.com route=a; server backend2.example.com route=b; sticky route $route_cookie $route_uri; }
Nginx PLUS首先通过分析server的响应来找到会话标识符,然后学习哪个server对应哪个会话标识符。通常,这些标识符通过HTTP cookie进行传递。如果请求包含的会话标识符已经被学习了,那么Nginx就会将请求转发给相应的服务器:
upstream backend { server backend1.example.com; server backend2.example.com; sticky learn create=$upstream_cookie_examplecookie lookup=$cookie_examplecookie zone=client_sessions:1m timeout=1h; }
上例中,upstream的一个节点通过在响应中设置“EXAMPLECOOKIE” cookie创建一个会话,带有此cookie的后续请求将被转发给该节点,如果该节点无法处理请求,则视为客户端尚未进行绑定,Nginx将重新选择一个节点来处理请求。
create参数(不可缺省)定义一个变量来指定如何创建新会话。在上例中,新会话是根据upstream中server发送的“EXAMPLECOOKIE” cookie创建。
lookup参数(不可缺省)定义如何搜索已经存在的会话。在上例中,已经存在的会话是根据客户端发送的“EXAMPLECOOKIE”cookie进行搜索的。
zone参数(不可缺省)定义一块保存所有会话信息的内存区域(在Nginx侧)。在上例中,此内存区域名为client_sessions,大小为1MB(在64位系统中,1MB大约可以存储4000个会话信息)。
timeout参数定义多长时间内未被访问的会话将从zone中删除,该值默认为10分钟。上例中,此时间为1小时。
这种会话保持方法相比前两种更为复杂,它不在客户端侧存储任何cookie,所有的信息都被保存在服务侧的共享内存区域中。
如果集群中有多个Nginx实例使用了sticky learn方法,可以将其共享内存区域的内容进行同步,条件是:
1、zone名称相同
2、每个实例都配置了zone_sync功能
3、sticky learn中指定了sync参数
upstream backend { server backend1.example.com; server backend2.example.com; sticky learn create=$upstream_cookie_examplecookie lookup=$cookie_examplecookie zone=client_sessions:1m timeout=1h; sync; }
Nginx PLUS中,可以使用max_conns参数来限制server的连接数量。如果server的连接数量达到了限制值,则请求将被置于队列中以等待进一步处理。
upstream backend { server backend1.example.com max_conn=3; server backend2.example.com; queue 100 timeout=70; }
如果队列也满了(上例中队列可存放100个请求),或者timeout指定的时间内Nginx仍未能选择到可用的server,那么客户端就会收到报错。
如果upstream块中未包含zone指令,则每个worker process都会保存自己的服务器组配置副本,并维护自己的一组计数器。计数器包括与upstream组中每个服务器的当前连接数以及请求传递给服务器的失败尝试次数。那么,服务器组的配置将无法动态更新。
如果upstream块中定义了zone,那么服务器组的配置将被保存在多个worker process共享的一个内存区域中。此方案是可动态配置的,因为每个worker process访问的是同一个组配置副本,并且使用的是相同的计数器。
zone参数对于主动健康检查和upstream组的动态配置更新是必须的,而且upstream组的其他功能也可从中受益。
例如,如果一个组的配置不是共享的,每个worker process都维护自己的尝试请求失败计数器(通过max_fails参数设置)。在这种情况下,每个请求仅达到一个worker process,当被选择用来处理此请求的worker process无法将请求给后端的某个服务器时,其他的worker process对此一无所知。这就造成尽管部分worker process认为一个server不可用,其他worker process仍然会向该server发送请求。server确定不可用的条件是在fail_timeout定义的时间内请求失败的次数必须达到max_fails*worker process的数量。如果我们配置了zone,server确定不可用的条件才会变成在fail_timeout定义的时间内请求失败次数达到max_fails,这才是我们想要的效果。
相似的,如果不设置zone,至少在低负载情况下,基于最小连接的负载均衡方法将不按预期工作。此种负载均衡将请求发送给当前活跃连接数最小的服务器,如果组配置不是共享的,每个worker process使用的都是自己的连接数计数器,那个很可能会把请求发送给另一个worker process刚刚发过请求的服务器上。在高负载情况下,请求在work process见均匀分配,最小连接负载均衡法就可以按预期工作。
由于使用模式差异巨大,因此不可能推荐一个通用的理想值。所需的内存大小取决于启用了那些功能(例如会话保持、健康检查、DNS重解析)以及upstream的定义方式。
例如,在一个使用了sticky_route会话保持且启用了健康检查的案例中,一个256KB的zone可以维护如下数量的server信息:
128个节点(每个以IP:port形式定义)
或者88个节点(每个以hostname:port形式定义,且每个域名解析到单个IP地址)
或者12个节点(每个以hostname:port形式定义,且每个域名解析到多个IP地址)
server组的配置可以使用DNS在运行时进行修改。
对于upstream中使用域名标识的server,Nginx PLUS可以监控DNS解析记录的变化,并自动将变化应用到负载均衡调度组中,而无需重启。实现方式是在http块中引入resolver指令,并在server后面添加resolve参数。
http { resolver 10.0.1 valid=300s ipv6=off; resolver_timeout 10s; server { location / { proxy_pass http://backend; } upstream backend { zone backend 32k; least_conn; # ... server backend1.example.com resolve; server backend2.example.com resolve; } }
上例中,server后的resolve参数指示Nginx PLUS周期性地重解析backend1.example.com和backend2.example.com为IP地址。
resolver指令定义了DNS服务器地址(10.0.0.1),Nginx PLUS默认以TTL(生存时间值)指定的频率重解析DNS记录,我们可以使用valid参数指定TTL值,上例中该值为300s。
ipv6=off表示负载均衡仅使用IPv4地址。Nginx默认是既支持IPv4也支持IPv6的。
如果一个域名被解析为多个IP地址,地址将会被保存到upstream配置中并进行负载均衡。如果IP列表发生了变更,则Nginx PLUS会立即基于新的地址列表进行负载均衡。
在Nginx PLUS R7或更高版本中,Nginx PLUS还可以代理Microsoft Exchange Server的流量,并进行负载均衡。
配置步骤如下:
1、location块中配置proxy_pass,代理到Microsoft Exchange Server服务组:
location / { proxy_pass https://exchange; # ... }
2、location块中配置proxy_http_version为1.1,配置proxy_set_header为Connection “”:
location / { # ... proxy_http_version 1.1; proxy_set_header Connection ""; # ... }
3、在upstream块中添加ntml指令,以使组中服务器接受带有NTML验证的请求:
http { # ... upstream exchange { zone exchange 64k; ntml; # ... } }
4、添加server组:
http { #... upstream exchange { zone exchange 64k; ntml; server exchange1.example.com; server exchange2.example.com; # ... } }
完整配置如下:
http { # ... upstream exchange { zone exchange 64k; ntml; server exchange1.example.com; server exchange1.example.com; } server { listen 443 ssl; ssl_certificate /etc/nginx/ssl/company.com.crt; ssl_certificate_key /etc/nginx/ssl/company.com.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass https://exchange; proxy_http_version 1.1; proxy_set_header Connection ""; } } }
本文出处:代码的荣耀
本文链接:http://www.icoder.top/blog
作者/译者:zhangrj