nginx的负载均衡配置,包括http、tcp和udp负载均衡,以及Round robin
、Least connections
、Least time
(Nginx Plus专属)、Generic hash
、Random
、IP hash
(HTTP模块专属)的原理分析。
我们先来看一小段配置文件
upstream backend {
server 10.0.0.1:80 weight=1;
server nginx.example.com:80 weight=2;
}
server {
location / {
proxy_pass http://backend;
}
}
这是一个简单的使用upstream模块对http服务进行指定权重的负载均衡的配置文件,一般存放在nginx目录下的conf.d
文件夹中。
server可以使用Unix socket、IP、DNS、FQDN等来进行服务器的指定,这里的Unix socket指的是POSIX操作系统中的组件,即用于进程间通信的那个Unix socket。也就是说如果做负载均衡的时候本机也作为server之一,使用scoket确实是可以有效提高速度的(对比DNS和IP等),因为都在同一个系统上,走进程间的通信比走网络通信要少了很多验证步骤和协议,通信的速度会更快。但是在实际业务中比较少使用这样的方式,一般都会直接使用IP方便定位主机和运维分析等。IP相比DNS和FQDN要少了一步域名解析的过程,理论上速度会快一些,但是DNS其实也可以做负载均衡,同时DNS和FQDN给了网络路由更多的控制权,实际怎么使用还要看具体的业务需求。
upstream
中server
指令语法如下:
server address [parameters]
关键字server
必选
address
也必选,可以是主机名、域名、ip或unix socket,也可以指定端口号
parameters
是可选参数,可以是如下参数:
down
:表示当前server已停用backup
:表示当前server是备用服务器,只有其它非backup后端服务器都挂掉了或者很忙才会分配到请求weight
:表示当前server负载权重,权重越大被请求几率越大,默认是1max_fails
和fail_timeout
一般会关联使用,如果某台server在fail_timeout
时间内出现了max_fails
次连接失败,那么Nginx会认为其已经挂掉了,从而在fail_timeout
时间内不再去请求它,fail_timeout
默认是10s
max_fails
默认是1,即默认情况是只要发生错误就认为服务器挂掉了
如果将max_fails
设置为0,则表示取消这项检查
我们来看一个stream模块的配置:
stream {
upstream mysql_read {
server mysqlread1.example.com:3306 weight=5;
server mysqlread2.example.com:3306;
server 10.0.0.1:3306 backup;
}
server {
listen 3306;
proxy_pass mysql_read;
}
}
在这个配置中我们实现了一个MySQL的负载均衡和备份。我们先看整个stream模块包含了upstream
模块和server
模块,在upstream中指定了三个server,其中第二个server在没有指定权重weight
的情况下,weight
默认为1,而第三个server后面指定了其状态为backup
,也就是备用服务器。一般来说nginx会同时监听运行服务器和备用服务器,以便在active
服务器出现故障的时候能迅速切换到备用服务器。
需要注意的是,stream模块的配置文件不建议放到nginx下的conf.d目录下(该目录一般用于放置http模块相关的配置文件),我们可以新建一个stream.conf.d
目录用于存放stream模块的配置文件,同时需要在nginx目录下的nginx.conf
文件中写入该目录,如:
stream {
include /etc/nginx/stream.conf.d/*.conf;
}
然后我们在对应新建的stream.conf.d
目录下面新建配置文件的时候,就不需要再添加stream{}
了,这和之前的http模块对应的conf.d
目录下的配置相似,同样的,我们其实也可以直接将整个stream模块配置全部都放到nginx目录下的nginx.conf
文件中,只不过这样不方便整理,尤其是当需要配置的项目变多了的时候。
实际上我们会发现tcp负载均衡使用的stream模块和http模块十分相似,这是因为nginx一开始是作为web服务器和七层负载均衡服务器,tcp和udp的负载均衡是属于四层负载均衡,这项功能是在1.9版本加入的,因此在一些配置和原理上都参考了http模块。
udp负载均衡和上面的两个负载均衡比较类似,在实现的原理上也参考了tcp的负载均衡。
我们日常使用的服务中比较常见的使用UDP协议的有NTP、DNS等。
stream {
upstream ntp {
server ntp1.example.com:123 weight=2;
server ntp2.example.com:123;
}
server {
listen 123 udp;
proxy_pass ntp;
}
}
udp负载均衡的配置和tcp基本一致,需要注意的就是要在监听的端口后面加上udp
参数指定协议为udp协议即可。
除了默认的轮询负载均衡算法,nginx还内置了其他的一些负载均衡策略,实际上对于HTPP、TCP和UDP三类负载均衡使用的策略默认有Round robin
、Least connections
、Least time
(Nginx Plus专属)、Generic hash
、Random
、IP hash
(仅HTTP可用)这六种。
网上提到较多的url_hash和fair这两种策略属于第三方模块实现的策略。
Round robin
轮询算法是默认的负载均衡算法,根据设定的权重值来进行访问,权重值越高被访问的概率就越高,不设置权重值的话则会默认设置为1,最后的被访问比例从概率统计的角度上看等于设定的权重值比例。
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com weight=1;
server backend3.example.com backup;
server backend4.example.com down;
}
具体使用到的是名为smooth weighted round-robin balancing
的负载均衡算法,具体原理和测试有兴趣的可以看之前的文章,这里直接摘录之前的原理叙述部分。
weight
:配置文件中设置的权重值,是定值,在整个选择过程中是不会改变的,对应到这里就是3、5、7。current_weight
:后端服务器的当前权重值,初始值等于0,在每轮选择中,该值最大的服务器就会被选中effective_weight
:变化权重值,初始值等于weight
,用于动态调整服务器被选择的概率,即当被选中的服务器出现了failure的时候,该服务器对应的effective_weight
就会减小,具体操作我们下面再解释。total_weight
:总的权重值,即所有服务器的权重值相加,在这里为3+5+7=15。接下来我们开始逐步解析算法执行过程:
weight
赋值为配置文件中的weight
,current_weight
赋值为0,effective_weight
赋值为weight
,total_weight
为所有weight
之和;current_weight
,加上该服务器对应的weight
;current_weight
值最大的服务器来接受这次访问,然后该服务器对应的current_weight
需要减去total_weight
(因此current_weight
是可以出现负值的)total_weight
时,所有服务器的current_weight
刚好为0,此时结束一轮负载均衡。Least connections
**在配置文件中使用least_conn
来指定该策略。**前面的轮询算法是使每台服务器的连接数大致相同或者符合设定的权重比例来实现负载均衡,前提是每个访问请求所需要的处理时间都大致相同,如果每次访问需要的处理时间不一样,使用轮询算法的效果就比较一般。这时候就可以考虑使用最少连接数算法。
顾名思义,nginx会将访问负载到访问数最少的服务器上,同时也会将设定的权重值weight
纳入考虑因素。具体来说就是nginx会记录分配给后端服务器的连接数,当有访问过来的时候优先分配给连接数最少的服务器,而如果最少连接数的服务器出现了多台,则根据上面的轮询算法来进行选择。
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
}
Least time
(Nginx Plus专属)号称在这几种算法中最复杂的算法,在最少连接数算法的基础上增加了响应时间这一维度,因此在使用的时候需要加上header
或者last_byte
。
upstream backend {
least_time header;
server backend1.example.com;
server backend2.example.com;
}
header
参数,nginx会使用接收到响应报文的报头的时间来作为响应时间last_byte
参数,nginx会使用接收整个完整报文的时间来作为响应时间Generic hash
以用户自定义资源(比如URL、特定的文本、请求的变量或者多个的组合等)的方式计算hash值完成分配。当我们需要更好地控制请求的发送到哪个服务器上或者确定服务器最有可能有缓存数据时,此方法很有用。**注意此时的server
语句中不能写入weight
等其他的参数,hash_method
是使用的hash
算法。**当有服务器加入或者移除后端的服务器列表的时候,哈希请求会被重新分配,想要最小化该影响,可以添加关键字consistent
。这个关键词会使用一种新的一致性哈希算法 ketama, 该算法会让管理员添加或删除某个服务实例的时候,只有一小部分的请求会被转发到与之前不同的服务实例上去,其他请求仍然会被转发到原有的服务实例上去。
upstream resinserver {
hash $request_uri consistent;
server backend1.example.com;
server backend2.example.com;
hash_method crc32;
}
Random
随机算法就是随机从后端服务器中挑选一个来接受访问,不过它还有一个附加参数two [parameters]
,可以随机挑选两个服务器,然后根据指定的均衡算法从服务器中挑选一台接受访问。如果不指定two
后面的parameters
则默认使用Least time
算法进行选择。
upstream backend {
random two ip_hash;
server backend1.example.com;
server backend2.example.com;
}
IP hash
(HTTP模块专属)IP哈希算法使用ipv4地址的前三段(比如说192.168.1.1就使用192.168.1这三段)或者是整个ipv6地址来进行哈希算法计算,从源码中我们可以看到实际使用的哈希算法比较简单,在nginx源码的\src\http\modules\ngx_http_upstream_ip_hash_module.c
中大概181行的位置,具体如下:
hash = (hash * 113 + iphp->addr[i]) % 6271
这种算法的好处是可以保持服务器的session的一致性,因为同一个IP根据哈希算法的结果一般都是访问到同一台服务器(除非中途服务器崩了),需要注意的是该算法也可以使用轮询算法的参数。
upstream backend {
ip_hash;
server backend1.example.com weight=5;
server backend2.example.com weight=1;
}