概述

什么是负载均衡(Load balancing)

所谓负载均衡,就是说如果一组计算机节点(或者一组进程)提供相同的(同质的)服务,那么对服务的请求就应该均匀的分摊到这些节点上。负载均衡的前提一定是“provide a single Internet service from multiple servers”, 这些提供服务的节点被称之为server farm、server pool或者backend servers。

这里的服务是广义的,可以是简单的计算,也可能是数据的读取或者存储。负载均衡也不是新事物,这种思想在多核CPU时代就有了,只不过在分布式系统中,负载均衡更是无处不在,这是分布式系统的天然特性决定的,分布式就是利用大量计算机节点完成单个计算机无法完成的计算、存储服务,既然有大量计算机节点,那么均衡的调度就非常重要。

负载均衡的意义在于,让所有节点以最小的代价、最好的状态对外提供服务,这样系统吞吐量最大,性能更高,对于用户而言请求的时间也更小。而且,负载均衡增强了系统的可靠性,最大化降低了单个节点过载、甚至crash的概率。不难想象,如果一个系统绝大部分请求都落在同一个节点上,那么这些请求响应时间都很慢,而且万一节点降级或者崩溃,那么所有请求又会转移到下一个节点,造成雪崩。

负载均衡分类

现在我们知道,负载均衡就是一种计算机网络技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁碟驱动器或其他资源中分配负载,以达到最佳化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。那么,这种计算机技术的实现方式有多种。大致可以分为以下几种,其中最常用的是四层和七层负载均衡:

  • 二层负载均衡
  • 负载均衡服务器对外依然提供一个VIP(虚IP),集群中不同的机器采用相同IP地址,但是机器的MAC地址不一样。当负载均衡服务器接受到请求之后,通过改写报文的目标MAC地址的方式将请求转发到目标机器实现负载均衡。
  • 三层负载均衡
  • 和二层负载均衡类似,负载均衡服务器对外依然提供一个VIP(虚IP),但是集群中不同的机器采用不同的IP地址。当负载均衡服务器接受到请求之后,根据不同的负载均衡算法,通过IP将请求转发至不同的真实服务器。
  • 四层负载均衡
  • 四层负载均衡工作在OSI模型的传输层,由于在传输层,只有TCP/UDP协议,这两种协议中除了包含源IP、目标IP以外,还包含源端口号及目的端口号。四层负载均衡服务器在接受到客户端请求后,以后通过修改数据包的地址信息(IP+端口号)将流量转发到应用服务器。
  • 七层负载均衡
  • 七层负载均衡工作在OSI模型的应用层,应用层协议较多,常用http、radius、dns等。七层负载就可以基于这些协议来负载。这些应用层协议中会包含很多有意义的内容。比如同一个Web服务器的负载均衡,除了根据IP加端口进行负载外,还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡。

对于一般的应用来说,有了Nginx就够了。Nginx可以用于七层负载均衡。但是对于一些大的网站,一般会采用DNS + 四层负载 + 七层负载的方式进行多层次负载均衡。

常用负载均衡工具

Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件。

LVS

LVS(Linux Virtual Server),也就是Linux虚拟服务器, 是一个由章文嵩博士发起的自由软件项目。使用LVS技术要达到的目标是:通过LVS提供的负载均衡技术和Linux操作系统实现一个高性能、高可用的服务器群集,它具有良好可靠性、可扩展性和可操作性。从而以低廉的成本实现最优的服务性能。

LVS主要用来做四层负载均衡。

Nginx

Nginx(发音同engine x)是一个网页服务器,它能反向代理HTTP, HTTPS, SMTP, POP3, IMAP的协议链接,以及一个负载均衡器和一个HTTP缓存。

Nginx主要用来做七层负载均衡。

HAProxy

HAProxy是一个使用C语言编写的自由及开放源代码软件,其提供高可用性、负载均衡,以及基于TCP和HTTP的应用程序代理。

Haproxy主要用来做七层负载均衡。

常见负载均衡算法

负载均衡服务器在决定将请求转发到具体哪台真实服务器的时候,是通过负载均衡算法来实现的。负载均衡算法可以分为两类:静态负载均衡算法和动态负载均衡算法。

静态负载均衡算法

  • 轮询(Round Robin):顺序循环将请求一次顺序循环地连接每个服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从顺序循环队列中拿出,不参加下一次的轮询,直到其恢复正常。
  • 比率(Ratio):给每个服务器分配一个加权值为比例,根椐这个比例,把用户的请求分配到每个服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从服务器队列中拿出,不参加下一次的用户请求的分配, 直到其恢复正常。
  • 优先权(Priority):给所有服务器分组,给每个组定义优先权,BIG-IP 用户的请求,分配给优先级最高的服务器组(在同一组内,采用轮询或比率算法,分配用户的请求);当最高优先级中所有服务器出现故障,BIG-IP 才将请求送给次优先级的服务器组。这种方式,实际为用户提供一种热备份的方式。

动态负载均衡算法

  • 最少的连接方式(Least Connection):传递新的连接给那些进行最少连接处理的服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从服务器队列中拿出,不参加下一次的用户请求的分配, 直到其恢复正常。
  • 最快模式(Fastest):传递连接给那些响应最快的服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP 就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
  • 观察模式(Observed):连接数目和响应时间以这两项的最佳平衡为依据为新的请求选择服务器。当其中某个服务器发生第二到第7 层的故障,BIG-IP就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
  • 预测模式(Predictive):BIG-IP利用收集到的服务器当前的性能指标,进行预测分析,选择一台服务器在下一个时间片内,其性能将达到最佳的服务器相应用户的请求。(被BIG-IP 进行检测)
  • 动态性能分配(Dynamic Ratio-APM):BIG-IP 收集到的应用程序和应用服务器的各项性能参数,动态调整流量分配。 动态服务器补充(Dynamic Server Act):当主服务器群中因故障导致数量减少时,动态地将备份服务器补充至主服务器群。
  • 服务质量(QoS):按不同的优先级对数据流进行分配。
  • 服务类型(ToS):按不同的服务类型(在Type of Field中标识)负载均衡对数据流进行分配。
  • 规则模式:针对不同的数据流设置导向规则,用户可自行定义。

Nginx负载均衡(七层)
   

当使用Nginx进行七层负载均衡时,我们一般关心以下几个方面:

  • 上游服务器配置:使用upstream server配置上游服务器。
  • 负载均衡算法:配置多个上游服务器时的负载均衡机制。
  • 失败重试机制:配置当超时或上游服务器不存活时,是否需要重试其他上游服务器。
  • 服务器心跳检查:上游服务器的健康检查/心跳检查。

Nginx上游服务器配置

通过在http指令下配置upstream即可。

http {
... ...
upstream backend {
server 192.168.0.100:8080 ;
server 192.168.0.101:8080 ;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
... ...
}

如上所示,可以通过server命令通过指定IP地址和端口来访问后端服务器。同时,也支持域名的方式,Nginx会进行域名解析(TODO)。

同时,可以设置后端服务器的状态:

down:表示当前server暂时不参与负载均衡。当测试或者某个后端服务器出现故障时,可以暂时通过该配置临时摘掉机器。 ②backup:表示是预留的备份机,当其他所有非backup机器出现故障或者繁忙的时候,才会请求backup机器,这台机器的访问压力最轻。如通过缩容上游服务器进行压测时,要摘掉一些上游服务器进行压测,但为了保险起见会配置一些备上游服务器,当压测的上游服务器都挂掉时,流量可以转发到备上游服务器,从而不影响用户请求处理。


 

upstream backend {
server 192.168.0.100:8080 ;
server 192.168.0.101:8080 ;
server 192.168.0.101:8080 down;
server 192.168.0.101:8080 backup;
}

还有max_fails和fail_timeout等参数,后面的失败重试部分再进行说明。

Nginx负载均衡算法

Nginx中支持的负载均衡算法(用户请求到来时选择哪个后端服务器)主要有以下几种:

轮询(round-robin),默认的负载均衡算法

以轮询的方式将请求转发到上游服务器,通过配合weight 配置可以实现基于权重的轮询。

最少连接(least_conn

将请求负载均衡到最少活跃连接的上游服务器。如果配置的服务器较少,则将转而使用基于权重的轮询算法。

最小响应时间(least_time

Nginx商业版支持该算法,基于最小平均响应时间进行负载均衡。

公平(fair,第三方)

根据后端服务器的响应时间来分配请求,响应时间短的优先分配。如果想要使用此调度算法,需要Nginx的upstream_fair模块。

IP哈希(ip_hash

根据客户IP进行负载均衡,即相同的IP将负载均衡到同一个Upstream Server。  

URL哈希(url_hash,第三方)

按访问URL的哈希结果来分配请求,使每个URL定向到同一台后端服务器,可以进一步提高后端缓存服务器的效率。如果想要使用此调度算法,需要Nginx的hash软件包。

哈希(hash key [consistent]

对某一个key进行哈希或者使用一致性哈希算法进行负载均衡。使用Hash算法存在的问题是,当添加/删除一台服务器时,将导致很多key被重新负载均衡到不同的服务器(从而导致后端可能出现问题);因此,建议考虑使用一致性哈希算法,这样当添加/删除一台服务器时,只有少数key将被重新负载均衡到不同的服务器。

TODO:各种算法的配置示例

Nginx失败重试

主要有两部分配置:upstream server和proxy_pass。

http {
... ...
upstream backend {
  server 192.168.61.1:9080 max_fails=2 fail_timeout=10s weight=1;
  server 192.168.61.1:9090 max_fails=2 fail_timeout=10s weight=1;
}
server {
listen 80;
location /test {
  proxy_connect_timeout 5s;
  proxy_read_timeout 5s;
  proxy_send_timeout 5s;
  proxy_next_upstreamerror timeout;
  proxy_next_upstream_timeout 10s;
  proxy_next_upstream_tries 2;
  proxy_pass http://backend;
  add_header upstream_addr $upstream_addr;
   }
  }
... ...
}

上游服务器中的max_failsfail_timeout:指定每个上游服务器,当fail_timeout时间内失败了max_fails次请求,则认为该上游服务器不可用/不存活,然后将摘掉该上游服务器,fail_timeout时间后会再次将该服务器加入到存活上游服务器列表进行重试。

Nginx健康检查

Nginx对上游服务器的健康检查默认采用的是惰性策略(Nginx商业版提供了进行主动健康检查)。当然也可以集成nginx_upstream_check_module模块来进行主动健康检查。

nginx_upstream_check_module模块支持以下两种检查模式。

1、TCP心跳检查

upstream backend {
  server 192.168.61.1:9080 weight=1;
  server 192.168.61.1:9090 weight=2;
  check interval=3000 rise=1 fall=3 timeout=2000 type=tcp;
}

参数说明:

  interval:检测间隔时间,此处配置了每隔3s检测一次。

  fall:检测失败多少次后,上游服务器被标识为不存活。

  rise:检测成功多少次后,上游服务器被标识为存活,并可以处理请求。

  timeout:检测请求超时时间配置。

2、HTTP心跳检查


 

upstream backend {
  server 192.168.61.1:9080 weight=1;
  server 192.168.61.1:9090 weight=2;
  check interval=3000 rise=1 fall=3 timeout=2000 type=http;
  check_http_send "HEAD /status HTTP/1.0rnrn";
  check_http_expect_alive http_2xx http_3xx;
}

参数说明:(未说明参数与TCP心跳检查部分一致)

  check_http_send:即检查时发的HTTP请求内容。

  check_http_expect_alive:当上游服务器返回匹配的响应状态码时,则认为上游服务器存活。

此处需要注意,检查间隔时间不能太短,否则可能因为心跳检查包太多造成上游服务器挂掉,同时要设置合理的超时时间。

Nginx与上游服务器的长连接

通过如下配置:

http {
... ...
upstream backend {
  server 192.168.61.1:9080 weight=1;
  server 192.168.61.1:9090 weight=2 backup;
  keepalive 100;
}
server {
listen 80;
location / {
  #http1.1支持keep-alive
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_pass http://backend;
   }
}
... ...
}

配置说明:

keepalive:配置长连接数量,这里是指每个Worker进程与上游服务器可缓存的空闲连接的最大数量(不是总连接数,keepalive指令不限制Worker进程与上游服务器的总连接数量)。当超出这个数量时,最近最少使用的连接将被关闭。

①如果是http/1.0,则需要配置发送“Connection: Keep-Alive”请求头。

②上游服务器不要忘记开启长连接支持。

Nginx反向代理配置

Nginx在七层的作用就是反向代理,它除了实现了负载均衡之外,还提供如缓存来减少上游服务器的压力。

全局配置缓存

http {
... ...
proxy_buffering on;
  proxy_buffer_size 4k;
  proxy_buffers 512 4k;
  proxy_busy_buffers_size 64k;
  proxy_temp_file_write_size 256k;
  proxy_cache_lock on;
  proxy_cache_lock_timeout 200ms;
  proxy_temp_path /tmpfs/proxy_temp;
  proxy_cache_path /tmpfs/proxy_cache levels=1:2keys_zone =cache:512m inactive=5m max_size=8g;
  proxy_connect_timeout 3s;
  proxy_read_timeout 5s;
  proxy_send_timeout 5s;
gzip on;
  gzip_min_length 1k;
  gzip_buffers 16 16k;
  gzip_http_version 1.0;
  gzip_proxied any;
  gzip_comp_level 2;
  gzip_types text/plainapplication/x-java text/css application/xml;
  gzip_vary on;
server{
... ...
}
... ...
}

配置说明:

①开启proxy buffer,缓存内容将存放在tmpfs(内存文件系统)以提升性能,设置超时时间。

②开启gzip支持,减少网络传输的数据包大小。对于内容型响应建议开启gzip压缩,gzip_comp_level压缩级别要根据实际压测来决定(带宽和吞吐量之间的抉择)。

location配置缓存

location ~ ^/backend/(.*)$ {
  #请求上游服务器使用GET方法(不管请求是什么方法)
  proxy_method GET;
  #不给上游服务器传递请求体
  proxy_pass_request_body off;
  #不给上游服务器传递请求头
  proxy_pass_request_headers off;
  #设置上游服务器的哪些响应头不发送给客户端
  proxy_hide_header Vary;
  #支持keep-alive
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  #给上游服务器传递Referer、Cookie和Host(按需传递)
  proxy_set_header Referer $http_referer;
  proxy_set_header Cookie $http_cookie;
  proxy_set_header Host web.c.3.local;
  proxy_pass http://backend /$1$is_args$args;
}

上述配置中开启了proxy_pass_request_bodyproxy_pass_request_headers,禁止向上游服务器传递请求头和内容体,从而使得上游服务器不受请求头攻击,也不需要解析;如果需要传递,则使用proxy_set_header按需传递即可。

 

Nginx负载均衡(四层)

Nginx 从1.9.0版本起支持四层负载均衡,从而使得Nginx变得更加强大。目前,四层软件负载均衡器用得比较多的是HaProxy;而Nginx也支持四层负载均衡,一般场景我们使用Nginx一站式解决方案就够了。

静态负载均衡

在默认情况下,ngx_stream_core_module 是没有启用的,需要在安装Nginx时,添加--with-stream配置参数启用。

./configure --prefix=/usr/servers --with-stream

1.stream指令

我们配置HTTP负载均衡时,都是配置在http指令下,而四层负载均衡则是配置在stream指令下。

stream {
upstream backend {
... ...
}
server {
... ...
}
}

2.upstream配置

类似于http upstream配置,配置如下:

upstream backend {
server 192.168.0.10:3306 max_fails=2 fail_timeout=10s weight=1;
server 192.168.0.11:3306 max_fails=2 fail_timeout=10s weight=1;
least_conn;
}

失败重试、惰性健康检查、负载均衡算法的相关配置与HTTP负载均衡配置类似。此处我们配置实现了两个数据库服务器的TCP负载均衡。

3.server配置


server {

 
#监听端口
listen 3308;
#失败重试
proxy_next_upstream on;
proxy_next_upstream_timeout 0;
proxy_next_upstream_tries 0;
#超时配置
proxy_connect_timeout 1s;
proxy_timeout 1m;
#限速配置
proxy_upload_rate 0;
proxy_download_rate 0;
#上游服务器
proxy_pass backend;
}

配置说明:

listen指令指定监听的端口,默认TCP协议,如果需要UDP ,则可以配置listen 3308 udp;

proxy_next_upstream*与HTTP负载均衡类似。

proxy_connect_timeout配置与上游服务器连接超时时间,默认60s。

proxy_timeout配置与客户端或上游服务器连接的两次成功读/写操作的超时时间,如果超时,将自动断开连接,即连接存活时间,通过它可以释放那些不活跃的连接,默认10分钟。

proxy_upload_rateproxy_download_rate分别配置从客户端读数据和从上游服务器读数据的速率,单位为每秒字节数,默认为0,不限速。

接下来就可以连接Nginx的3308端口,访问我们的数据库服务器了。

目前的配置都是静态配置,像数据库连接一般都是使用长连接,如果重启Nginx服务器,则会看到如下Worker进程一直不退出。


 

Nobody 10268 ……nginx: worker process is shutting down

这是因为Worker维持的长连接一直在使用,所以无法退出,解决办法只能是杀掉该进程。

当然,一般情况下是因为需要动态添加/删除上游服务器,才需要重启Nginx,像HTTP动态负载均衡那样。如果能做到动态负载均衡,则一大部分问题就解决了。