大型网站,负载均衡时永恒的话题。
硬件负载均衡如:F5、BIG-IP、Citrix NetScaler、Radware、A10等,性能好价格昂贵
软件负载均衡如:LVS、Nginx、HAProxy等
现在最火的当属nginx
nginx特点:稳定性高、功能强大、资源消耗低,而且能做web服务器。
负载均衡问题的产生
在nginx中,建立连接的时候,会涉及负载均衡问题。在多个子进程争抢处理一个新连接事件时,一定只有一个worker子进程最终会成功建立连接,
随后它会一直处理这个连接直到连接关闭。那么,就可能出现这样的情况:有的子进程建立并处理了大部分连接,而有的子进程只处理了少量连接。
这对多核cpu架构下的应用时很不利的。因为子进程之间应该是平等的,每个子进程应该尽量独占一个cpu核心。子进程负载不均衡,必定会影响整个
服务性能。
如何解决负载均衡问题
只有打开了accept_mutex锁,才能实现子进程间的负载均衡。同时post事件机制也是解决负载均衡问题的关键。
在ngx_event_accept方法建立新连接的过程中,初始化一个全局变量ngx_accept_disabled。它就是负载均衡机制实现的关键阈值。
其定义(/src/event/ngx_event.c)
ngx_int_t ngx_accept_disabled;
初始化(src/event/ngx_event_accept.c)是在函数ngx_event_accept中:
ngx_accept_disabled=ngx_cycle->connection_n/8 - ngx_cycle->free_connection_n;
这样,在nginx启动的时候其实是个负值: -7/8 * ngx_cycle->connection_n
依据这个值进行负载均衡的核心代码是在函数ngx_process_events_and_timers中(src/event/ngx_event.c)
//负载均衡处理
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
//调用ngx_trylock_accept_mutex方法,尝试获取accept锁
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
解释一下:
当ngx_accept_disabled为负数时,不会触发负载均衡操作,正常获取accept锁,试图处理新连接。
当ngx_accept_disabled为正数时,会触发负载均衡操作。nginx此时不再处理新连接事件,取而代之的仅仅是把变量ngx_accept_disabled减一,这表示既然经过一轮事件处理,那么负载肯定有所减小,所以要响应的调整这个值。
即当前使用的连接超过总连接数的7/8的时候才会被触发,值越大,表示负载越重。每次调用ngx_process_events_and_timers的时候只会将ngx_accept_disabled减一,直到ngx_accept_disabled降到0,即使用的连接数降到总连接输的7/8,这样就减少了该work子进程处理新连接的机会,这样其他较空闲的worker子进程就有机会去处理更多的新连接,以达到整个web服务器的均衡效果。
Nginx默认将accept_mutex配置项设置为accept_mutex on。
@上面所说的是客户端请求在多个nginx进程之间的负载均衡
@下面所说的是客户端请求在多个后端服务器之间的负载均衡(反向代理)
负载均衡模块简介
负载均衡模块Load-balance是辅助模块,主要为Upstream模块服务,目标明确且单一:如何从多台后端服务器中选择出一台合适的服务器来处理当前的请求。
Load-blance模块中4个关键回调函数:
回调指针:uscf->peer.init_upstream
函数功能:解析配置文件过程中调用,根据upstream里各个server配置项做初始准备工作,另外的核心工作是设置回调指针us->peer.init。配置文件解析完后不再被调用。
round_robin模块:ngx_http_upstream_init_round_robin 设置us->peer.init=ngx_http_upstream_init_round_robin_peer;
IP_hash模块: ngx_http_upstream_init_ip_hash 设置us->peer.init=ngx_http_upstream_init_ip_hash_peer;
回调指针:us->peer.init
函数功能:在每一次Nginx准备转发客户请求道后端服务器前都会调用该函数。该函数为本次转发选择合适的后端服务器做初始准备工作,另外的核心工作就是设置回调指针 r->upstream->peer.get和r->upstream->peer.free等
round_robin模块: ngx_http_upstream_init_round_robin_peer 设置 r->upstream->peer.get=ngx_http_upstream_get_round_robin_peer; r->upstream->peer.free=ngx_http_upstream_free_round_robin_peer
IP_hash模块: ngx_http_upstream_init_ip_hash_peer 设置 r->upstream->peer.get=ngx_http_upstream_get_ip_hash_peer; r->upstream->peer.free为空
回调指针: r->upstream->peer.get
函数功能:在每一次Nginx准备转发客户端请求到后端服务器前都会调用该函数,该函数实现具体的为本次转发选择合适的后端服务器的算法逻辑,即完成选择获取合适后端服务器的功能。
round_robin模块: ngx_http_upstream_get_round_robin_peer 加权选择当前权值最高的后端服务器。
IP_hash模块: ngx_http_upstream_get_ip_hash_peer 根据IP哈希值选择后端服务器
回调指针: r->upstream->peer.free
函数功能:在每一次Nginx完成于后端服务器之间的交互后都会调用该函数
round_robin模块:ngx_http_upstream_free_round_robin_peer更新相关数值,比如rrp->current
IP_hash模块:空
@负载均衡配置指令
nginx负载均衡模块ngx_http_upstream_module允许定义一组服务器,这组服务器可以被proxy_pass,fastcgi_pass和memcached_pass这些指令引用
proxy代理服务,
配置例子:
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server unix:/tmp/backend3;
server backup1.example.com:8080 backup;
server backup2.example.com:8080 backup;
}
server {
location / {
proxy_pass http://backend;
}
}
语法:upstream name {...}
所属指令:http
定义一组用于实现nginx负载均衡的服务器,它们可以侦听在不同的端口,另外,可以混合使用侦听TCP与UNIX-domain套接文件
例子:
upstream backend {
server backend1.example.com weight=5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
默认情况下,请求被分散在使用加权轮询的nginx负载 均衡服务器上。在上面的例子中,每七个请求按下面分配:五个请求发送给backend1.example.com,127.0.0.1:8080和unix:/tmp/backend3各自分配一个。如果在与服务器通信时发生了一个错误,这个请求就会被传递到下一个服务器,一次类推直到所有的服务器都尝试过,如果不能从所有的这些nginx负载均衡服务器上获得回应,客户端将会获得最后一个链接的服务器的处理结果。
语法:server地址[参数]
所属指令:upstream
设置一个nginx负载均衡服务器的地址和其他参数。一个地址可以被指定为域名或IP地址,和一个可选的端口,或者被定义为UNIX-domain套接字文件的路径,使用“unix:”作为前缀。如果端口没指定,则使用80端口。一个被解析到多个IP地址的域名本质指定了多少服务器
可以定义下面的参数:
weight=number
设置服务器的权重,默认是1
max_fails=number
设置在fail_timeout参数设置的时间内(注意内)最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器是停机了,停机时间是fail_timeout设置的时间。默认情况下,不成功连接数被设置为1。被设置为零则表示不进行链接数统计。那些连接被认为是不成功的可以通过proxy_next_upstream、fastcgi_next_upstream和memcached_next_upstream指令配置。http_404状态不会被认为是不成功的尝试。
fail_timeout=time
设置多长时间内失败次数达到最大失败次数会被认为服务器停机了,服务器会被认为停机的时间长度,默认情况下,超时时间被设置为10s。
backup
标记该服务器为备用服务器,当主服务器停止时,请求会被发送到这里。
down
标记服务器永久停机了;与指令ip_hash一起使用。
例子:
upstream backend {
server backend1.example.com weight=5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
server backup1.example.com:8080 backup;
}
语法:ip_hash
所属指令:upstream
指定nginx负载均衡器组使用基于客户端ip的负载均衡算法,ipv4的前3个八进制和所有的ipv6地址呗用作一个hash key。这个方法确保了相同客户端的请求一直发送到相同的服务器上除非这个服务器被认为是停机了,这种情况下,请求会被发送到其他主机上,然后可能会一直发送到这个主机上。
如果nginx负载均衡器组里面的一个服务器要临时移除,它应该用参数down标记,来防止之前的客户端ip还往这个服务器上发请求。
例子:
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}
注意!ip_hash不能使用权重weight
语法:keepalive连接数
所属模块:upstream
nginx负载均衡器的活动链接数缓存
连接数(keepalive的值)指定了每个工作进程中保留的持续连接到nginx负载均衡器缓存的最大值,如果超过这个设置值的闲置进程想链接到nginx负载均衡器组,最先连接的将被关闭
注意!keepalive指令不限制nginx工作进程可以连接到nginx负载均衡器可以开启的最大工作进程,如果有需要的话,新进程还是会被发起,连接数应该被设置最后低来满足nginx负载均衡器处理新进的连接。
带有持续连接的memcached upstream配置例子
upstream memcached_backend {
server 127.0.0.1:11211;
server 10.0.0.2:11211;
keepalive 32;
}
server {
...
location /memcached/ {
set $memcached_key $uri;
memcached_pass memcached_backend;
}
}
nginx负载均衡器内置变量
nginx负载均衡模块ngx_http_upstream_module支持下列内置变量:
$upstream_addr
保存一个服务器的ip地址和端口号或者unix-domain套接字文件的路径。如果在处理请求过程中使用了多个服务器,那么他们的地址将以逗号分割,例如:“192.168.1.1:80,192.168.1.2:80,unix:/tmp/sock”。如果一个内置的从一个服务器组到另一个服务器组的重定向使用X-Accel-Redirect or error_page,那么这些服务器组以冒号隔开,例如“192.168.1.1:80,192.168.1.2:80,unix:/tmp/sock:192.168.10.1:80,192.138.10.2:80”。
$upstream_response_time
保存nginx负载均衡服务器响应时间,以毫秒计。多个响应也以逗号和冒号隔开。
$upstream_status
保存nginx负载均衡服务器响应代码。多个响应代码也以逗号或冒号隔开。
$upstream_http_...
保存nginx负载均衡服务器响应头字段。注意!只有最后一个服务器响应头字段被保存
nginx负载均衡策略可分为