如果将Web服务器集群当作一个城池,那么负载均衡服务器则相当于城门,重要性不言而喻。如果“城门”关闭了,与外界的通道也就掐断了。如果只有一台Nginx负载均衡服务器,当该服务器发生故障时,则会导致整个网站无法访问。因此,我们需要两台以上的Nginx负载均衡服务器,实现故障转移与高可用。
双机高可用一般是通过虚拟IP(也称漂移IP)方式来实现的,基于Linux/Unix的IP别名技术。双机高可用方式目前可分为两种:第一种方式为一台主服务器加一台热备服务器,正常情况下主服务器绑定一个公网虚拟IP,提供负载均衡服务,热备服务器处于空闲状态,当主服务器发生故障时,热备服务器接管主服务器的虚拟IP,提供负载均衡服务;第二种方式为两台负载均衡服务器都处于活动状态,各自绑定一个公网虚拟IP,提供负载均衡服务,当其中一台服务器发生故障时,另一台服务器接管发生故障服务器的虚拟IP。第一种方式较为常见,但始终有一台服务器处于空闲状态,浪费了一台服务器的负载均衡处理能力。第二种方式需要多用一个公网IP,笔者已经在金山游戏官方网站——逍遥网(xoyo.com)线上环境成功使用,能够在正常情况下将两台服务器都用于实际的负载均衡处理。
第一种方式:
(1)www.yourdomain.com域名解析到虚拟ip:61.1.1.2上。
(2)正常情况下,主机61.1.1.4绑定虚拟ip61.1.1.2。
/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.2 dev eth0:1
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1
(3)用户访问www.yourdomain.com(虚拟IP 61.1.1.2)实际访问的是主机61.1.1.4,而备机61.1.1.5则处于空闲状态。
(4)如果主机61.1.1.4发生故障,备机61.1.1.5将在几秒钟内接管虚拟IP 61.1.1.2,与自己绑定,并发送ARPing包给IDC的公网网关刷新MAC地址。
/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.2 dev eth0:1
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1
(5)这时,用户访问www.yourdomain.com(虚拟IP 61.1.1.2)实际上访问的是备机61.1.1.5,从而实现故障转移与高可用,避免了单点故障。转移过程如图6-8所示。

图6-8 一台主机配一台备机的可用服务方式
另外,第一种方式还可以利用基于VRRP路由协议的Keepalived软件来实现。
第二种方式:
(1)www.yourdomain.com域名通过DNS轮询解析到虚拟IP 61.1.1.2和61.1.1.3上。
(2)正常情况下,服务器①61.1.1.4绑定虚拟IP 61.1.1.2,服务器②61.1.1.5绑定虚拟IP61.1.1.3。其联结与运行方式如图6-9所示。

图6-9 两台负载均衡服务器的高可用服务方式(正常状态)
在服务器①61.1.1.4上执行以下命令:
/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.2 dev eth0:1
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1
在服务器①61.1.1.5上执行以下命令:
/sbin/ifconfig eth0:1 61.1.1.3 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.3 dev eth0:1
/sbin/arping -I eth0 -c 3 -s 61.1.1.3 61.1.1.1
(3)用户访问www.yourdomain.com(虚拟IP 61.1.1.2和61.1.1.3)实际上是根据DNS轮询访问两台负载均衡服务器,两台服务器均处于活动状态。
(4)如果服务器①发生故障,服务器②将在几秒钟内接管服务器①的虚拟IP 61.1.1.2,与自己绑定,并发送ARPing包给IDC的公网网关刷新MAC地址。这时,服务器②同时绑定61.1.1.2和61.1.1.3两个虚拟IP,其联结与运行方式如图6-10所示。

图6-10 其中一台负载均衡服务器发生故障的运行方式
在服务器②61.1.1.5上执行以下命令:
/sbin/ifconfig eth0:1 61.1.1.2 broadcast 61.1.1.255 netmask 255.255.255.0 up
/sbin/route add -host 61.1.1.2 dev eth0:1
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1
我们可以写两个shell脚本,来实现第二种方式的自动故障转移。
以下代码6-7为脚本1(nginx_ha1.sh),部署在Nginx负载均衡服务器①:
代码6-7
#!/bin/sh
LANG=C
date=$(date -d "today" +"%Y-%m-%d %H:%M:%S")
function_bind_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.2 broadcast 219.232.254.255 netmask
255.255.255.192 up
/sbin/route add -host 61.1.1.2 dev eth0:ha1
}
function_bind_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.3 broadcast 219.232.254.255 netmask
255.255.255.192 up
/sbin/route add -host 61.1.1.3 dev eth0:ha2
}
function_restart_nginx()
{
kill -USR1 `cat /usr/local/webserver/nginx/nginx.pid`
}
function_remove_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.2 broadcast 219.232.254.255 netmask
255.255.255.192 down
}
function_remove_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.3 broadcast 219.232.254.255 netmask
255.255.255.192 down
}
function_vip_arping1()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1 > /dev/null 2>&1
}
function_vip_arping2()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.3 61.1.1.1 > /dev/null 2>&1
}
bind_time_vip1="N";
bind_time_vip2="N";
while true
do
httpcode_rip1=`/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.4`
httpcode_rip2=`/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.5`
if [ x$httpcode_rip1 == "x200" ];
then
if [ $bind_time_vip1 == "N" ];
then
function_bind_vip1
function_vip_arping1
function_restart_nginx
bind_time_vip1="Y"
fi
function_vip_arping1
else
if [ $bind_time_vip1 == "Y" ];
then
function_remove_vip1
bind_time_vip1="N"
fi
fi
if [ x$httpcode_rip2 == "x200" ];
then
if [ $bind_time_vip2 == "Y" ];
then
function_remove_vip2
bind_time_vip2="N"
fi
else
if [ $bind_time_vip2 == "N" ];
then
function_bind_vip2
function_vip_arping2
function_restart_nginx
bind_time_vip2="Y"
fi
function_vip_arping2
fi
sleep 5
done
在Nginx负载均衡服务器①将脚本驻留后台运行:
nohup /bin/sh ./nginx_ha1.sh 2>&1 > /dev/null &
以下代码6-8为脚本2(server2.sh),部署在Nginx负载均衡服务器②:
代码6-8
#!/bin/sh
LANG=C
date=$(date -d "today" +"%Y-%m-%d %H:%M:%S")
function_bind_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.3 broadcast 219.232.254.255 netmask
255.255.255.192 up
/sbin/route add -host 61.1.1.3 dev eth0:ha1
}
function_bind_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.2 broadcast 219.232.254.255 netmask
255.255.255.192 up
/sbin/route add -host 61.1.1.2 dev eth0:ha2
}
function_restart_nginx()
{
kill -USR1 `cat /usr/local/webserver/nginx/nginx.pid`
}
function_remove_vip1()
{
/sbin/ifconfig eth0:ha1 61.1.1.3 broadcast 219.232.254.255 netmask
255.255.255.192 down
}
function_remove_vip2()
{
/sbin/ifconfig eth0:ha2 61.1.1.2 broadcast 219.232.254.255 netmask
255.255.255.192 down
}
function_vip_arping1()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.3 61.1.1.1 > /dev/null 2>&1
}
function_vip_arping2()
{
/sbin/arping -I eth0 -c 3 -s 61.1.1.2 61.1.1.1 > /dev/null 2>&1
}
bind_time_vip1="N";
bind_time_vip2="N";
while true
do
httpcode_rip1=`/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.5`
httpcode_rip2=`/usr/bin/curl -o /dev/null -s -w %{http_code} http://61.1.1.4`
if [ x$httpcode_rip1 == "x200" ];
then
if [ $bind_time_vip1 == "N" ];
then
function_bind_vip1
function_vip_arping1
function_restart_nginx
bind_time_vip1="Y"
fi
function_vip_arping1
else
if [ $bind_time_vip1 == "Y" ];
then
function_remove_vip1
bind_time_vip1="N"
fi
fi
if [ x$httpcode_rip2 == "x200" ];
then
if [ $bind_time_vip2 == "Y" ];
then
function_remove_vip2
bind_time_vip2="N"
fi
else
if [ $bind_time_vip2 == "N" ];
then
function_bind_vip2
function_vip_arping2
function_restart_nginx
bind_time_vip2="Y"
fi
function_vip_arping2
fi
sleep 5
done
在Nginx负载均衡服务器②将脚本驻留后台运行:
nohup /bin/sh ./nginx_ha2.sh 2>&1 > /dev/null &