LVS文章荟萃
LVS集群技术
作者:
最后更新:2005年5月18日
目录
集群技术主要分为三大类:
- 高可用性(High Available Cluster),例:
- 负载均衡(Load balancing Cluster),例:、
- 高性能计算(High Performance Computing),例:
我们这里使用RedHat AS 3.x,LVS,Linux-HA,Ldirectord,构造一个高可用的负载均衡集群系统。如图:
各层的作用:
- Load Balancer(负载均衡器):
Load Balancer是整个集群系统的前端,负责把客户请求转发到Real Server上。
Backup是备份Load Balancer,当Load Balancer不可用时接替它,成为实际的Load Balancer。
Load Balancer通过Ldirectord监测各Real Server的健康状况。在Real Server不可用时 把它从群中剔除,恢复时重新加入。
- Server Array(服务器群):
Server Array是一组运行实际应用服务的机器,比如WEB, Mail, FTP, DNS, Media等等。
在实际应用中,Load Balancer和Backup也可以兼任Real Server的角色。
- Shared Storage(共享存储):
Shared Storage为所有Real Server提供共享存储空间和一致的数据内容。 |
各服务器IP分配:
Virtual IP: |
192.168.136.10 |
Load Balancer: |
192.168.136.11 |
Backup: |
192.168.136.12 |
Real Server 1: |
192.168.136.101 |
Real Server 2: |
192.168.136.102 |
Real Server 3: |
192.168.136.103 |
IPVS是LVS集群系统的核心软件,它的主要作用是:
- 安装在Load Balancer上,把发往Virtual IP的请求转发到Real Server上。
|
IPVS的负载均衡机制有三种,这里使用IP Tunneling机制:
- Virtual Server via NAT
- Virtual Server via IP Tunneling
- Virtual Server via Direct Routing
|
IPVS的负载调度算法有十种:
- 轮叫(Round Robin)
- 加权轮叫(Weighted Round Robin)
- 最少链接(Least Connections)
- 加权最少链接(Weighted Least Connections)
- 基于局部性的最少链接(Locality-Based Least Connections)
- 带复制的基于局部性最少链接(Locality-Based Least Connections with Replication)
- 目标地址散列(Destination Hashing )
- 源地址散列(Source Hashing)
- 最短期望延迟(Shortest Expected Delay)
- 无须队列等待(Never Queue)
IPVS安装主要包括三方面:
- 在Load Banlancer上安装IPVS内核补丁
- 在Load Banlancer上安装IPVS管理软件
- 在Real Server上安装ARP hidden内核补丁
|
关于如何编译内核请参考其他文档,这里使用从UltraMonkey下载的已编译好的内核。
在Load Banlancer、Backup和Real Server上使用同一内核,IPVS和ARP hidden都已编译在这个内核里:
wget
wget
wget
rpm -Fhv mkinitrd-3.5.13-1.um.1.i386.rpm
rpm -Fhv kernel-2.4.21-27.0.2.EL.um.1.i686.rpm
在Load Banlancer和Backup上安装IPVS管理软件:
wget
tar zxf ipvs-1.0.10.tar.gz
cd ipvs-1.0.10/ipvs/ipvsadm
make install
chkconfig --del ipvsadm |
配置IPVS(/etc/sysconfig/ipvsadm),添加Real Server:
-A -t 192.168.136.10:80 -s rr
-a -t 192.168.136.10:80 -r 192.168.136.11:80 -i
-a -t 192.168.136.10:80 -r 192.168.136.12:80 -i
-a -t 192.168.136.10:80 -r 192.168.136.101:80 -i
-a -t 192.168.136.10:80 -r 192.168.136.102:80 -i
-a -t 192.168.136.10:80 -r 192.168.136.103:80 -i |
相关链接:
Kernel:
IPVS和IPVSadm:
ARP hidden: |
注意事项:
1. Kernel,IPVS,IPVSadm,ARP hidden之间的版本必须对应。
2. 自己编译内核时,从下载标准内核源文件,不要使用发行版的内核源文件。
3. Kernel 2.4.28和2.6.10及以上版本已内置IPVS,有些Linux发行版也在其内核里编译了IPVS。
4. ARP hidden可以用arp_ignore/arp_announce或者arptables代替 |
HeartBeat是Linux-HA的高可用性集群软件,它的主要作用是:
- 安装在Load Balancer和Backup上,运行于active/standby模式。
当Load Balancer失效时,Backup自动激活,成为实际的Load Balancer。
- 切换到active模式时,按顺序启动Virtual IP、IPVS和Ldirectord。
切换到standby模式时,按顺序关闭Ldirectord、IPVS和Virtual IP。 |
HeartBeat串口线连接测试方法:
在Load Balancer上:cat < /dev/ttyS0
在Backup上:echo hello > /dev/ttyS0
|
修改主机名(/etc/hosts):
127.0.0.1 localhost.localdomain localhost
192.168.136.11 loadbalancer
192.168.136.12 backup
|
安装:
groupadd -g 694 haclient
useradd -u 694 -g haclient hacluster
rpm -ivh /mnt/cdrom/RedHat/RPMS/glib2-devel-*
wget
tar zxf libnet.tar.gz
cd libnet
./configure
make
make install
wget
tar zxf heartbeat-1.99.4.tar.gz
cd heartbeat-1.99.4
./ConfigureMe configure --disable-swig --disable-snmp-subagent
make
make install
cp doc/ha.cf doc/haresources doc/authkeys /etc/ha.d/
cp ldirectord/ldirectord.cf /etc/ha.d/
chkconfig --add heartbeat
chkconfig --del ldirectord |
主配置文件(/etc/ha.d/ha.cf):
#debugfile /var/log/ha-debug
logfile /var/log/ha-log
logfacility local0
keepalive 2
deadtime 30
warntime 10
initdead 120
udpport 694
baud 19200
serial /dev/ttyS0
mcast eth0 225.0.0.1 694 1 0
# 当主节点恢复后,是否自动切回
auto_failback on
# stonith用来保证共享存储环境中的数据完整性
#stonith baytech /etc/ha.d/conf/stonith.baytech
# watchdog能让系统在出现故障1分钟后重启该机器。这个功能可以帮助服务器在确实停止心跳后能够重新恢复心跳。
# 如果使用该特性,则在内核中装入"softdog"内核模块,用来生成实际的设备文件,输入"insmod softdog"加载模块。
# 输入"grep misc /proc/devices"(应为10),输入"cat /proc/misc | grep watchdog"(应为130)。
# 生成设备文件:"mknod /dev/watchdog c 10 130" 。
#watchdog /dev/watchdog
node loadbalancer
node backup
# 默认heartbeat并不检测除本身之外的其他任何服务,也不检测网络状况。
# 所以当网络中断时,并不会进行Load Balancer和Backup之间的切换。
# 可以通过ipfail插件,设置'ping nodes'来解决这一问题。详细说明参考hearbeat文档。
#ping 192.168.136.1 172.16.0.1
ping_group group1 192.168.136.1 192.168.136.2
respawn root /usr/lib/heartbeat/ipfail
apiauth ipfail gid=root uid=root
# 其他一些插件可以在/usr/lib/heartbeat下找到
#apiauth ipfail uid=hacluster
#apiauth ccm uid=hacluster
#apiauth cms uid=hacluster
#apiauth ping gid=haclient uid=alanr,root
#apiauth default gid=haclient |
资源文件(/etc/ha.d/haresources):
loadbalancer lvs IPaddr::192.168.136.10/24/eth0 ipvsadm ldirectord |
认证文件(/etc/ha.d/authkeys),选取一种认证方式,这个文件的权限必须是600:
auth 1
1 crc
#2 sha1 sha1_any_password
#3 md5 md5_any_password |
相关链接:
安装HeartBeat过程中,已经自动安装了Ldirectord,它的作用是:
- 监测Real Server,当Real Server失效时,把它从Load Balancer列表中删除,恢复时重新添加。
|
配置(/etc/ha.d/ldirectord.cf):
# Global Directives
checktimeout=3
checkinterval=1
fallback=127.0.0.1:80
autoreload=yes
logfile="/var/log/ldirectord.log"
quiescent=yes
# A sample virual with a fallback that will override the gobal setting
virtual=192.168.136.10:80
real=192.168.136.11:80 ipip
real=192.168.136.12:80 ipip
real=192.168.136.101:80 ipip
real=192.168.136.102:80 ipip
real=192.168.136.103:80 ipip
fallback=127.0.0.1:80 gate
service=http
request="test.html"
receive="Test Page"
virtualhost=
scheduler=rr
#persistent=600
#netmask=255.255.255.255
protocol=tcp |
在每个Real Server的中添加监控页:
echo "Test Page" >> /var/www/html/test.html |
在启动集群系统之前,我们认为包括Load Balancer和Backup在内的所有服务器都是Real Server。
在服务器上添加以下脚本/etc/init.d/tunl,用来配置tunl端口,应用arp补丁:
#!/bin/sh
# chkconfig: 2345 70 10
# description: Config tunl port and apply arp patch
VIP=192.168.136.10
. /etc/rc.d/init.d/functions
case "$1" in
start)
echo "Tunl port starting"
ifconfig tunl0 $VIP netmask 255.255.255.255 broadcast $VIP up
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/conf/all/hidden
echo 1 > /proc/sys/net/ipv4/conf/tunl0/hidden
;;
stop)
echo "Tunl port closing"
ifconfig tunl0 down
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 0 > /proc/sys/net/ipv4/conf/all/hidden
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac |
如果有多个Virutal IP,可以使用tunl0:0,tunl0:1...。
chmod 755 /etc/init.d/tunl
chkconfig --add tunl |
在Load Balancer和Backup上,这个脚本的启动级必须先于heartbeat,关闭级必须后于heartbeat。
在HeartBeat资源文件(/etc/ha.d/haresources)中定义了实现集群所需的各个软件的启动脚本。
这些脚本必须放在/etc/init.d或者/etc/ha.d/resource.d目录里,启动顺序不能变:
loadbalancer lvs IPaddr::192.168.136.10/24/eth0 ipvsadm ldirectord |
IPaddr的作用是启动Virutal IP,它是HeartBeart自带的一个脚本。
ipvsadm的作用是在启动的时候把所有Real Server加入群中。
ldirectord的作用是启动ldirectord监控程序。
lvs的作用是为启动Load Balancer做准备,关闭tunl端口,取消arp补丁:
#!/bin/sh
# chkconfig: 2345 90 10
# description: Preparing for Load Balancer and Real Server switching
VIP=192.168.136.10
. /etc/rc.d/init.d/functions
case "$1" in
start)
echo "Preparing for Load Balancer"
ifconfig tunl0 down
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 0 > /proc/sys/net/ipv4/conf/all/hidden
;;
stop)
echo "Preparing for Real Server"
ifconfig tunl0 $VIP netmask 255.255.255.255 broadcast $VIP up
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/conf/all/hidden
echo 1 > /proc/sys/net/ipv4/conf/tunl0/hidden
;;
*)
echo "Usage: lvs {start|stop}"
exit 1
esac |
chmod 755 /etc/ha.d/resource.d/lvs |
启动集群系统:
/etc/init.d/heartbeat start |
http://www-900.ibm.com/developerWorks/cn/linux/theme/special/index.shtml#cluster
LVS手册
LVS手册是为了给LVS用户和开发者提供一个完整的参考手册。也希望大家提供LVS相关的好文章以及使用经验。
手册
如何给出合理的框架和有效的设计方法,来建立高性能、高可伸缩、高可用的网络服务,这是摆在研究者和系统设计者面前极富挑战性的任务。下面文章就是围绕这一任务展开的。
文章
关于用LVS搭建web服务器集群
文嵩您好!大家好!我想使用LVS搭建一个web服务器集群,用于解决我们一个项目中突发访问量过大导致的服务器宕机问题。我的实验环境是:实验室几台普通的PC机(配置不太相同),一个10M的HUB,PC机安装的系统是RedHat9.0,内核版本是2.4.20,web服务器为Apache2.0。我现在设计的步骤是: 1、重新下载内核 2、然后打上LVS补丁 linux-2.4.20-ipvs-1.0.8.patch.gz ipvs-1.0.8.tar.tar 3、配置核心选项 4、重新编译内核 5、重启系统 6、安装ipvsadm-1.21-4.src.rpm 7、调试 想请教一下:这样的实验环境可不可以?实验步骤可不可行? 另外:我想采用基于内容的负载分配机制,请问: web服务器的内容应该怎样部署?(应用系统需要在一两天的时间内完成几万名学生的网上选课) LVS中基于内容的负载均衡机制是怎样的?可以和IP负载均衡配合使用么? (注:关于数据库服务器的集群另外有人在做,暂且认为使用单台高性能机器作为数据库服务器呵呵) 谢谢:)
利用集群技术实现Web服务器的负载均衡
本文出自《网管员世界》2002年第10期“系统维护”栏目
集群和负载均衡的概念
集群(Cluster)
所谓集群是指一组独立的计算机系统构成的一个松耦合的多处理器系统,它们之间通过网络实现进程间的通信。应用程序可以通过网络共享内存进行消息传送,实现分布式计算机。
负载均衡(Load Balance)
网络的负载均衡是一种动态均衡技术,通过一些工具实时地分析数据包,掌握网络中的数据流量状况,把任务合理均衡地分配出去。这种技术基于现有网络结构,提供了一种扩展服务器带宽和增加服务器吞吐量的廉价有效的方法,加强了网络数据处理能力,提高了网络的灵活性和可用性。
特点
(1)高可靠性(HA)。利用集群管理软件,当主服务器故障时,备份服务器能够自动接管主服务器的工作,并及时切换过去,以实现对用户的不间断服务。
(2)高性能计算(HP)。即充分利用集群中的每一台计算机的资源,实现复杂运算的并行处理,通常用于科学计算领域,比如基因分析、化学分析等。
(3)负载平衡。即把负载压力根据某种算法合理分配到集群中的每一台计算机上,以减轻主服务器的压力,降低对主服务器的硬件和软件要求。
LVS系统结构与特点
1. Linux Virtual Server:简称LVS。是由中国一个Linux程序员章文嵩博士发起和领导的,基于Linux系统的服务器集群解决方案,其实现目标是创建一个具有良好的扩展性、高可靠性、高性能和高可用性的体系。许多商业的集群产品,比如RedHat的Piranha、 Turbo Linux公司的Turbo Cluster等,都是基于LVS的核心代码的。
2. 体系结构:使用LVS架设的服务器集群系统从体系结构上看是透明的,最终用户只感觉到一个虚拟服务器。物理服务器之间可以通过高速的 LAN或分布在各地的WAN相连。最前端是负载均衡器,它负责将各种服务请求分发给后面的物理服务器,让整个集群表现得像一个服务于同一IP地址的虚拟服务器。
3. LVS的三种模式工作原理和优缺点: Linux Virtual Server主要是在负载均衡器上实现的,负载均衡器是一台加了 LVS Patch的2.2.x版内核的Linux系统。LVS Patch可以通过重新编译内核的方法加入内核,也可以当作一个动态的模块插入现在的内核中。
负载均衡器可以运行在以下三种模式下:
(1)Virtual Server via NAT(VS-NAT):用地址翻译实现虚拟服务器。地址转换器有能被外界访问到的合法IP地址,它修改来自专有网络的流出包的地址。外界看起来包是来自地址转换器本身,当外界包送到转换器时,它能判断出应该将包送到内部网的哪个节点。优点是节省IP 地址,能对内部进行伪装;缺点是效率低,因为返回给请求方的流量经过转换器。
(2)Virtual Server via IP Tunneling (VS-TUN):用IP隧道技术实现虚拟服务器。这种方式是在集群的节点不在同一个网段时可用的转发机制,是将IP包封装在其他网络流量中的方法。为了安全的考虑,应该使用隧道技术中的VPN,也可使用租用专线。 集群所能提供的服务是基于TCP/IP的Web服务、Mail服务、News服务、DNS服务、Proxy服务器等等.
(3)Virtual Server via Direct Routing(VS-DR):用直接路由技术实现虚拟服务器。当参与集群的计算机和作为控制管理的计算机在同一个网段时可以用此法,控制管理的计算机接收到请求包时直接送到参与集群的节点。优点是返回给客户的流量不经过控制主机,速度快开销少。
以四台服务器为例实现负载均衡:
安装配置LVS
1. 安装前准备:
(1)首先说明,LVS并不要求集群中的服务器规格划一,相反,可以根据服务器的不同配置和负载状况,调整负载分配策略,充分利用集群环境中的每一台服务器。如下表:
Srv Eth0 Eth0:0 Eth1 Eth1:0
vs1 10.0.0.1 10.0.0.2 192.168.10.1 192.168.10.254
vsbak 10.0.0.3 192.168.10.102
real1 192.168.10.100
real2 192.168.10.101
其中,10.0.0.2是允许用户访问的IP。
(2)这4台服务器中,vs1作为虚拟服务器(即负载平衡服务器),负责将用户的访问请求转发到集群内部的real1,real2,然后由real1,real2分别处理。 Client为客户端测试机器,可以为任意操作系统。
(3)所有OS为redhat6.2,其中vs1 和vsbak 的核心是2.2.19, 而且patch过ipvs的包, 所有real server的Subnet mask 都是24位, vs1和vsbak 的10.0.0. 网段是24 位。
2.理解LVS中的相关术语
(1) ipvsadm :ipvsadm是LVS的一个用户界面。在负载均衡器上编译、安装ipvsadm。
(2) 调度算法: LVS的负载均衡器有以下几种调度规则:Round-robin,简称rr;weighted Round-robin,简称wrr;每个新的连接被轮流指派到每个物理服务器。Least-connected,简称lc;weighted Least-connected,简称wlc,每个新的连接被分配到负担最小的服务器。
(3) Persistent client connection,简称pcc,(持续的客户端连接,内核2.2.10版以后才支持)。所有来自同一个IP的客户端将一直连接到同一个物理服务器。超时时间被设置为360秒。Pcc是为https和cookie服务设置的。在这处调度规则下,第一次连接后,所有以后来自相同客户端的连接(包括来自其它端口)将会发送到相同的物理服务器。但这也会带来一个问题,因为大约有25%的Internet 可能具有相同的IP地址。
(4) Persistent port connection调度算法:在内核2.2.12版以后,pcc功能已从一个调度算法(你可以选择不同的调度算法:rr、wrr、lc、wlc、pcc)演变成为了一个开关选项(你可以让rr、 wrr、lc、wlc具备pcc的属性)。在设置时,如果你没有选择调度算法时,ipvsadm将默认为wlc算法。 在Persistent port connection(ppc)算法下,连接的指派是基于端口的,例如,来自相同终端的80端口与443端口的请求,将被分配到不同的物理服务器上。不幸的是,如果你需要在的网站上采用cookies时将出问题,因为http是使用80端口,然而cookies需要使用443端口,这种方法下,很可能会出现cookies不正常的情况。
(5)Load Node Feature of Linux Director:让Load balancer 也可以处理users 请求。
(6)IPVS connection synchronization。
(7)ARP Problem of LVS/TUN and LVS/DR:这个问题只在LVS/DR,LVS/TUN 时存在。
3. 配置实例
(1) 需要的软件包和包的安装:
I. piranha-gui-0.4.12-2*.rpm (GUI接口cluster设定工具);
II. piranha-0.4.12-2*.rpm;
III. ipchains-1.3.9-6lp*.rpm (架设NAT)。
取得套件或mount到光盘,进入RPMS目录进行安装:
# rpm -Uvh piranha*
# rpm -Uvh ipchains*
(2) real server群:
真正提供服务的server(如web server),在NAT形式下是以内部虚拟网域的形式,设定如同一般虚拟网域中Client端使用网域:192.168.10.0/24 架设方式同一般使用虚拟IP之局域网络。
a. 设网卡IP
real1 :192.168.10.100/24
real2 :192.168.10.101/24
b.每台server均将default gateway指向192.168.10.254。 192.168.10.254为该网域唯一对外之信道,设定在virtual server上,使该网域进出均需通过virtual server 。
c.每台server均开启httpd功能供web server服务,可以在各real server上放置不同内容之网页,可由浏览器观察其对各real server读取网页的情形。
d.每台server都开启rstatd、sshd、rwalld、ruser、rsh、rsync,并且从Vserver上面拿到相同的lvs.conf文件。
(3) virtual server:
作用在导引封包的对外主机,专职负责封包的转送,不提供服务,但因为在NAT型式下必须对进出封包进行改写,所以负担亦重。
a.IP设置:
对外eth0:IP:10.0.0.1 eth0:0 :10.0.0.2
对内eth1:192.168.10.1 eth1:0 :192.168.10.254
NAT形式下仅virtual server有真实IP,real server群则为透过virtual server.
b.设定NAT功能
# echo 1 >; /proc/sys/net/ipv4/ip_forward
# echo 1 >; /proc/sys/net/ipv4/ip_always_defrag
# ipchains -P forward MASQ
c.设定piranha 进入X-window中 (也可以直接编辑/etc/lvs.cf )
a).执行面板系统piranha
b).设定“整体配置”(Global Settings) 主LVS服务器主机IP:10.0.0.2, 选定网络地址翻译(预设) NAT路径名称: 192.168.10.254, NAT 路径装置: eth1:0
c).设定虚拟服务器(Virtual Servers) 添加编辑虚拟服务器部分:(Virtual Server)名称:(任意取名);应用:http;协议: tcp;连接:80;地址:10.0..0.2;装置:eth0:0; 重入时间:180 (预设);服务延时:10 (预设);加载监控工具:ruptime (预设);调度策略:Weighted least-connections; 持续性:0 (预设); 持续性屏蔽: 255.255.255.255 (预设); 按下激活:实时服务器部分:(Real Servers); 添加编辑:名字:(任意取名); 地址: 192.168.10.100; 权重:1 (预设) 按下激活
另一架real server同上,地址:192.168.10.101。
d). 控制/监控(Controls/Monitoring) 控制:piranha功能的激活与停止,上述内容设定完成后即可按开始键激活piranha.监控器:显示ipvsadm设定之routing table内容 可立即更新或定时更新。
(4)备援主机的设定(HA)
单一virtual server的cluster架构virtual server 负担较大,提供另一主机担任备援,可避免virtual server的故障而使对外服务工作终止;备份主机随时处于预备状态与virtual server相互侦测
a.备份主机:
eth0: IP 10.0.0.3
eth1: IP 192.168.10.102 同样需安装piranha,ipvsadm,ipchains等套件
b.开启NAT功能(同上面所述)。
c.在virtual server(10.0.0.2)主机上设定。
a).执行piranha冗余度 ;
b).按下“激活冗余度”;
冗余LVS服务器IP: 10.0.0.3;HEARTBEAT间隔(秒数): 2 (预设)
假定在…秒后进入DEAD状态: 5 (预设); HEARTBEAT连接埠: 539 (预设)
c).按下“套用”;
d).至“控制/监控”页,按下“在当前执行层添加PULSE DEAMON” ,按下“开始”;
e).在监控器按下“自动更新”,这样可由窗口中看到ipvsadm所设定的routing table,并且动态显示real server联机情形,若real server故障,该主机亦会从监视窗口中消失。
d.激活备份主机之pulse daemon (执行# /etc/rc.d/init.d/pulse start)。
至此,HA功能已经激活,备份主机及virtual server由pulse daemon定时相互探询,一但virtual server故障,备份主机立刻激活代替;至virtual server 正常上线后随即将工作交还virtual server。
LVS测试
经过了上面的配置步骤,现在可以测试LVS了,步骤如下:
1. 分别在vs1,real1,real2上运行/etc/lvs/rc.lvs_dr。注意,real1,real2上面的/etc/lvs 目录是vs2输出的。如果您的NFS配置没有成功,也可以把vs1上/etc/lvs/rc.lvs_dr复制到real1,real2上,然后分别运行。确保real1,real2上面的apache已经启动并且允许telnet。
2. 测试Telnet:从client运行telnet 10.0.0.2, 如果登录后看到如下输出就说明集群已经开始工作了:(假设以guest用户身份登录)
[guest@real1 guest]$——说明已经登录到服务器real1上。
再开启一个telnet窗口,登录后会发现系统提示变为:
[guest@real2 guest]$——说明已经登录到服务器real2上。
3. 测试http:从client运行iexplore
因为在real1 和real2 上面的测试页不同,所以登录几次之后,显示出的页面也会有所不同,这样说明real server 已经在正常工作了。
集群LVS+GFS+ISCSI+TOMCAT
作者: hosyp 发表于:2006-02-15 21:24:28
LVS是中国人发起的项目,真是意外呀!大家可以看
我是从最初的HA(高可用性)开始的,别人的例子是用VMWARE,可以做试验但不能实际应用,我又没有光纤卡的Share Storage,于是就选用ISCSI,成功后又发现ISCSI+EXT3不能用于LVS,倒最后发现GFS可用,我最终成功配成可实际应用的LVS,前后断断续续花了四个月,走了很多弯路。我花了三天时间写下这篇文章,希望对大家有用。
这里要感谢linuxfans.org、linuxsir.com、chinaunix.com以及其它很多网站,很多资料都是从他们的论坛上找到的。参考文档及下载点
a.
b.
c.ftp://ftp.redhat.com/pub/redhat/linux/updates/enterprise/3ES/en/RHGFS/SRPMS
d.
LVS结构图:
eth0=10.3.1.101
eth0:1=10.3.1.254
Load Balance
Router
eth1=192.168.1.71
eth1:1=192.168.1.1
| |
| |
Real1 Real2
eth0=192.168.1.68 eth0=192.168.1.67
(eth0 gateway=192.168.1.1)
eth1=192.168.0.1---eth1=192.168.0.2
(双机互联线)
|
|
GFS
ISCSI
Share storage
eth0=192.168.1.124
1.Setup ISCSI Server
Server: PIII 1.4,512M, Dell 1650,Redhat 9,IP=192.168.1.124
从下载ISCSI TARGET的Source code
()
我选了iscsitarget-0.3.8.tar.gz,要求kernel 2.4.29
从kernel.org下载kernel 2.4.29,解开编译重启后编译安装iscsitarget-0.3.8:
#make KERNELSRC=/usr/src/linux-2.4.29
#make KERNELSRC=/usr/src/linux-2.4.29 install
#cp ietd.conf /etc
#vi /etc/ietd.conf
# Example iscsi target configuration
#
# Everything until the first target definition belongs
# to the global configuration.
# Right now this is only the user configuration used
# during discovery sessions:
# Users, who can access this target
# (no users means anyone can access the target)
User iscsiuser 1234567890abc
Target iqn.2005-04.com.my:storage.disk2.sys1.iraw1
User iscsiuser 1234567890abc
Lun 0 /dev/sda5 fileio
Alias iraw1
Target iqn.2005-04.com.my:storage.disk2.sys1.iraw2
User iscsiuser 1234567890abc
Lun 1 /dev/sda6 fileio
Alias iraw2
Target iqn.2005-04.com.my:storage.disk2.sys2.idisk
User iscsiuser 1234567890abc
Lun 2 /dev/sda3 fileio
Alias idisk
Target iqn.2005-04.com.my:storage.disk2.sys2.icca
User iscsiuser 1234567890abc
Lun 3 /dev/sda7 fileio
Alias icca
说明:password 长度必须不小于12个字符, Alias是别名, 不知为何这个别名在Client端显示不出来.
分区:我只有一个SCSI盘,所以:
/dev/sda3: Share storage,容量越大越好
/dev/sda5: raw1, 建Cluster要的rawdevice, 我给了900M
/dev/sda6: raw2, 建Cluster要的rawdevice, 我给了900M
/dev/sda7: cca, 建GFS要的,我给了64M
(/dev/sda4是Extended分区,在其中建了sda5,6,7)
#Reboot,用service iscsi-target start启ISCSI server(我觉得比建议的好,可以
用service iscsi-target status看状态)
2.Setup ISCSI Client(on two real server)
Server: PIII 1.4,512M, Dell 1650,Redhat AS3U4(用AS3U5更好),2.4.21-27.EL
#vi /etc/iscsi.conf
DiscoveryAddress=192.168.1.124
OutgoingUsername=iscsiuser
OutgoingPassword=1234567890abc
Username=iscsiuser
Password=1234567890abc
LoginTimeout=15
IncomingUsername=iscsiuser
IncomingPassword=1234567890abc
SendAsyncTest=yes
#service iscsi restart
#iscsi-ls -l
..., 精简如下:
/dev/sdb:iraw2
/dev/sdc:iraw1
/dev/sdd:idisk
/dev/sde:icca
注意: 在real server中ISCSI device的顺序很重要,两个real server中一定要一样,如不一样
就改ISCSI Server中的设置,多试几次
3.Install Redhat Cluster suite
先下载Cluster Suite的ISO, AS3的我是从ChinaUnix.net找到的下载点, 安装clumanager和
redhat-config-cluster。没有Cluster Suite的ISO也没关系,从
ftp://ftp.redhat.com/pub/redhat/linux/updates/enterprise/3ES/en/RHCS/SRPMS/下载
clumanager-1.2.xx.src.rpm,redhat-config-cluster-1.0.x.src.rpm,编译后安装,应该更好:
#rpm -Uvh clumanager-1.2.26.1-1.src.rpm
#rpmbuild -bs /usr/src/redhat/SPECS/clumanager.spec
#rpmbuild --rebuild --target i686 /usr/src/redhat/SRPMS/clumanager-1.2.26.1-1.src.rpm
还有redhat-config-cluster-1.0.x.src.rpm,也装好
4.Setup Cluster as HA module
详细步骤我就不写了,网上有很多文章,我也是看了别人的文章学会的,不过人家是用VMWARE,
而我是用真的机子+ISCSI,raw device就是/dev/sdb,/dev/sdc, 然后就
mount /dev/sdd /u01, mkfs.ext3 /u01 ......
设好后会发现ISCSI有问题:同时只能有一个Client联接写盘,如果
两个Client同时联ISCSI的Share Storge,一个Client写,另一个Client是看不到的,而且此时文
件系统已经破坏了,Client重联ISCSI时会发现文件是坏的,用fsck也修复不了。
ISCSI真的是鸡肋吗?
NO!从GOOGLE上我终于查到ISCSI只有用Cluster File System才能真正用于Share Storage!
而Redhat买下的GFS就是一个!
5.Setup GFS on ISCSI
GFS只有Fedora Core4才自带了,而GFS又一定要用到Cluster Suite产生的/etc/cluster.xml文件,
我没见FC4有Cluster Suite,真不知Redhat给FC4带GFS干嘛,馋人吗?
好,闲话少说,下载:c处的GFS-6.0.2.20-2.src.rpm, 按a处的gfs.txt编译安装,不过关于
cluster.ccs,fence.ccs,nodes.ccs的设置没说,看b的文档,我总算弄出来了,都存在
/root/cluster下,存在别的地方也行,不过我不知道有没有错,我没有光纤卡,文档又没讲ISCSI
的例子,不过GFS能启动的。
#cat cluster.ccs
cluster {
name = "Cluster_1"
lock_gulm {
servers = ["cluster1", "cluster2"]
heartbeat_rate = 0.9
allowed_misses = 10
}
}
注:name就是Cluster Suite设置的Cluster name, servers就是Cluster member的Hostname,别忘
了加进/etc/hosts;allowed_misses我开始设为1,结果跑二天GFS就会死掉,改为10就没死过了。
#cat fence.ccs
fence_devices{
admin {
agent = "fence_manual"
}
}
#cat nodes.ccs
nodes {
cluster1 {
ip_interfaces {
hsi0 = "192.168.0.1"
}
fence {
human {
admin {
ipaddr = "192.168.0.1"
}
}
}
}
cluster2 {
ip_interfaces {
hsi0 = "192.168.0.2"
}
fence {
human {
admin {
ipaddr = "192.168.0.2"
}
}
}
}
}
注:ip就是心跳线的ip
这三个文件建在/root/cluster下,先建立Cluster Configuration System:
a.#vi /etc/gfs/pool0.cfg
poolname pool0
minor 1 subpools 1
subpool 0 8 1 gfs_data
pooldevice 0 0 /dev/sde1
b.#pool_assemble -a pool0
c.#ccs_tool create /root/cluster /dev/pool/pool0
d.#vi /etc/sysconfig/gfs
CCS_ARCHIVE="/dev/pool/pool0"
再Creating a Pool Volume,就是我们要的共享磁盘啦,
a.#vi /etc/gfs/pool1.cfg
poolname pool1
minor 2 subpools 1
subpool 0 128 1 gfs_data
pooldevice 0 0 /dev/sdd1
b.#pool_assemble -a pool1
c.#gfs_mkfs -p lock_gulm -t Cluster_1:gfs1 -j 8 /dev/pool/pool1
d.#mount -t gfs -o noatime /dev/pool/pool1 /u01
下面是个GFS的启动脚本,注意real1和real2必须同时启动lock_gulmd进程,第一台lock_gulmd
会成为Server并等Client的lock_gulmd,几十秒后没有响应会fail,GFS启动失败。Redhat建议
GFS盘不要写进/etc/fstab。
#cat gfstart.sh
#!/bin/sh
depmod -a
modprobe pool
modprobe lock_gulm
modprobe gfs
sleep 5
service iscsi start
sleep 20
service rawdevices restart
pool_assemble -a pool0
pool_assemble -a pool1
service ccsd start
service lock_gulmd start
mount -t gfs /dev/pool/pool1 /s02 -o noatime
service gfs status
6. Setup Linux LVS
LVS是章文嵩博士发起和领导的优秀的集群解决方案,许多商业的集群产品,比如RedHat的Piranha,Turbolinux公司的Turbo Cluster等,都是基于LVS的核心代码的。
我的系统是Redhat AS3U4,就用Piranha了。从rhel-3-u5-rhcs-i386.iso安装piranha-0.7.10-2.i386.rpm,ipvsadm-1.21-9.ipvs108.i386.rpm () 装完后service httpd start & service piranha-gui start,就可以从管理或设置了,当然了,手工改/etc/sysconfig/ha/lvs.cf也一样。
#cat /etc/sysconfig/ha/lvs.cf
serial_no = 80
primary = 10.3.1.101
service = lvs
rsh_command = ssh
backup_active = 0
backup = 0.0.0.0
heartbeat = 1
heartbeat_port = 1050
keepalive = 6
deadtime = 18
network = nat
nat_router = 192.168.1.1 eth1:1
nat_nmask = 255.255.255.0
reservation_conflict_action = preempt
debug_level = NONE
virtual lvs1 {
active = 1
address = 10.3.1.254 eth0:1
vip_nmask = 255.255.255.0
fwmark = 100
port = 80
persistent = 60
pmask = 255.255.255.255
send = "GET / HTTP/1.0\r\n\r\n"
expect = "HTTP"
load_monitor = ruptime
scheduler = wlc
protocol = tcp
timeout = 6
reentry = 15
quiesce_server = 1
server Real1 {
address = 192.168.1.68
active = 1
weight = 1
}
server Real2 {
address = 192.168.1.67
active = 1
weight = 1
}
}
virtual lvs2 {
active = 1
address = 10.3.1.254 eth0:1
vip_nmask = 255.255.255.0
port = 21
send = "\n"
use_regex = 0
load_monitor = ruptime
scheduler = wlc
protocol = tcp
timeout = 6
reentry = 15
quiesce_server = 0
server ftp1 {
address = 192.168.1.68
active = 1
weight = 1
}
server ftp2 {
address = 192.168.1.67
active = 1
weight = 1
}
}
设置完后service pulse start, 别忘了把相关的client加进/etc/hosts
#iptables -t mangle -A PREROUTING -p tcp -d 10.3.1.254/32 --dport 80 -j MARK --set-mark 100
#iptables -t mangle -A PREROUTING -p tcp -d 10.3.1.254/32 --dport 443 -j MARK --set-mark 100
#iptables -A POSTROUTING -t nat -p tcp -s 10.3.1.0/24 --sport 20 -j MASQUERADE
运行以上三行命令并存入/etc/rc.d/rc.local,用ipvsadm看状态:
#ipvsadm
IP Virtual Server version 1.0.8 (size=65536)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.3.1.254:ftp wlc
-> cluster2:ftp Masq 1 0 0
-> cluster1:ftp Masq 1 0 0
FWM 100 wlc persistent 60
-> cluster1:0 Masq 1 0 0
-> cluster2:0 Masq 1 0 0
注意:a.Firewall Mark可以不要,我反正是加了,文档说有https的话加上,值我选了100,
b.Virtual IP别加进/etc/hosts,我上过当,80端口时有时无的,
c.eth0:1,eth1:1是piranha产生的,别自己手工设置,我干过这画蛇添足的事,网上有
些帖子没说清,最后是看Redhat的文档才弄清楚的。
d.The LVS router can monitor the load on the various real servers by using
either rup or ruptime. If you select rup from the drop-down menu, each real
server must run the rstatd service. If you select ruptime, each real server
must run the rwhod service.Redhat的原话,就是如选rup的监控模式real server上
都要运行rstatd进程,如选ruptime就要运行rwhod进程。
e.Real Server同Router相联的网卡的Gateway必须是Router的那块网卡的VIP,举本例:
Router的eth1同两个real server的eth0相联,如VIP eth1:1=192.168.1.1,则real
server 的eth0的Gateway=192.168.1.1
echo "1" > /proc/sys/net/ipv4/ip_forward
7.Setup TOMCAT5.59+JDK1.5(用Redhat自带的Apache)
a.#tar xzvf jakarta-tomcat-5.5.9.tar.gz
#mv jakarta-tomcat-5.5.9 /usr/local
#ln -s /usr/local/jakarta-tomcat-5.5.9 /usr/local/tomcat
b.#jdk-1_5_0_04-linux-i586.bin
#mv jdk1.5.0_4 /usr/java
#ln -s /usr/java/jdk1.5.0_4 /usr/java/jdk
c.#vi /etc/profile.d/tomcat.sh
export CATALINA_HOME=/usr/local/tomcat
export TOMCAT_HOME=/usr/local/tomcat
d.#vi /etc/profile.d/jdk.sh
if ! echo ${PATH} | grep "/usr/java/jdk/bin" ; then
JAVA_HOME=/usr/java/jdk
export JAVA_HOME
export PATH=/usr/java/jdk/bin:${PATH}
export CLASSPATH=$JAVA_HOME/lib
fi
e.#chmod 755 /etc/profile.d/*.sh
f.重新用root登录,让tomcat.sh和jdk.sh起作用,
#tar xzvf jakarta-tomcat-connectors-jk2-src-current.tar.gz
#cd jakarta-tomcat-connectors-jk2-2.0.4-src/jk/native2/
#./configure --with-apxs2=/usr/sbin/apxs --with-jni --with-apr-lib=/usr/lib
#make
#libtool --finish /usr/lib/httpd/modules
#cp ../build/jk2/apache2/mod_jk2.so ../build/jk2/apache2/libjkjni.so /usr/lib/httpd/modules/
g.#vi /usr/local/tomcat/bin/catalina.sh
在# Only set CATALINA_HOME if not already set后加上以下两行:
serverRoot=/etc/httpd
export serverRoot
h.#vi /usr/local/tomcat/conf/jk2.properties
serverRoot=/etc/httpd
apr.NativeSo=/usr/lib/httpd/modules/libjkjni.so
apr.jniModeSo=/usr/lib/httpd/modules/mod_jk2.so
i.#vi /usr/local/tomcat/conf/server.xml,
在前加上以下几行,建了两个VirtualPath:myjsp和local,一个指向share storage,一个指向real server本地
j.#vi /etc/httpd/conf/workers2.properties
#[logger.apache2]
#level=DEBUG
[shm]
file=/var/log/httpd/shm.file
size=1048576
[channel.socket:localhost:8009]
tomcatId=localhost:8009
keepalive=1
info=Ajp13 forwarding over socket
[ajp13:localhost:8009]
channel=channel.socket:localhost:8009
[status:status]
info=Status worker, displays runtime informations
[uri:/*.jsp]
worker=ajp13:localhost:8009
context=/
k.#vi /etc/httpd/conf/httpd.conf
改:DocumentRoot "/u01/www"
加:
在LoadModule最后加:
LoadModule jk2_module modules/mod_jk2.so
JkSet config.file /etc/httpd/conf/workers2.properties
在#之前加:
Order allow,deny
Deny from all
l:#mkdir /u01/ftproot
#mkdir /u01/www
#mkdir /u01/www/myjsp
m:在每个real server上生成index.jsp
#vi /var/www/html/index.jsp
<%@ page import="java.util.*,java.sql.*,java.text.*" contentType="text/html"
%>
<%
out.println("test page on real server 1");
%>
在real server2上就是"test page on real server 2"
n:下载jdbc Driver
可惜只有for JDK1.4的,在两台real server上分别
#cp -R /usr/local/tomcat/webapps/webdav/WEB-INF /u01/www/myjsp
#cp ojdbc14.jar ojdbc14_g.jar ocrs12.zip /u01/www/myjsp/WEB-INF/lib
o: 假设我有一台OracleServer,ip=10.3.1.211,sid=MYID,username=my,password=1234,
并有Oracle的例子employees的read权限,或干脆把这个table拷过来,我是Oracle9i中的
#vi /u01/www/myjsp/testoracle.jsp
<%@ page contentType="text/html" %>;
<%@ page import="java.sql.*"%>;
;
;
;
;
;Test ORACLE Employees;
;
;
<%
String OracleDBDriver="oracle.jdbc.driver.OracleDriver";
String DBUrl="jdbc:oracle:thin:@10.3.1.211:1521:MYID";
String UserID="my";
String UserPWD="1234";
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
try
{
Class.forName(OracleDBDriver);
}
catch(ClassNotFoundException ex)
{
System.out.println("Class.forname:"+ex);
}
conn=DriverManager.getConnection(DBUrl,UserID,UserPWD);
stmt=conn.createStatement();
String sql="select * from EMPLOYEES";
rs = stmt.executeQuery(sql);
out.print("
;");
out.print(";");
out.print(";"+"EMPLOYEE_ID");
out.print(" | ;"+"FIRST_NAME");
out.print(" | ;"+"LAST_NAME");
out.print(" | ;"+"EMAIL");
out.print(" | ;"+"PHONE_NUMBER");
out.print(" | ;"+"HIRE_DATE");
out.print(" | ;"+"JOB_ID");
out.print(" |
;");
try
{
while(rs.next())
{
out.print("
;");
int n=rs.getInt(1);
out.print(";"+n+" | ;");
String e=rs.getString(2);
out.print(";"+e+" | ;");
//String e=rs.getString(3);
out.print(";"+rs.getString(3)+" | ;");
out.print(";"+rs.getString(4)+" | ;");
out.print(";"+rs.getString(5)+" | ;");
out.print(";"+rs.getString(6)+" | ;");
out.print(";"+rs.getString(7)+" | ;");
out.print("
;");
}
}
catch(SQLException ex)
{
System.err.println("ConnDB.Main:"+ex.getMessage());
}
out.print("
;");
rs.close();
stmt.close();
conn.close();
%>;
;
;
p:#vi /u01/www/index.html
;WEB Local
;Test Oracle WEB
q:在两台real server上分别
#vi /usr/local/tomcat/conf/tomcat-users.xml
加下面一行,允许页面管理:
r:在两台real server上分别
#service httpd restart
#/usr/local/tomcat/bin/startup.sh
s:打开和,选Tomcat Manager,用
manager/tomcat登录,虚拟目录/myjsp和/local应该Start了
在两台机子上分别打开网页,选WEB Local,可以看到一台显示:
"test page on real server 1",另一台为"test page on real server 2",同时在Router上
ipvsadm可以看到每个real server的联接数
8.设置FTP服务
#vi /etc/vsftpd/vsftp.conf,在两台real server上分别加入以下几行:
anon_root=/u01/ftproot
local_root=/u01/ftproot
setproctitle_enable=YES
#service vsftpd start
现在LVM+GFS+ISCSI+TOMCAT就设置好了,我们可以用Apache Jmeter来测试LVM的性能,两台机子上分别运行jmeter,都指向10.3.1.254/myjsp/testoracle.jsp,各200个threads同时运行,在Router上用ipvsadm可以监控,Oracle Server的性能可要好,否则大量的http进程会hang在real server上,ipvsadm也会显示有个real server失去了。测试时real server的CPU idle会降到70%,而Router的CPU idle几乎不动。
LVS集群系统网络核心原理分析
2004-6-30 / (LinuxAid)
Internet的快速增长使多媒体网络服务器面对的访问数量快速增加,服务器需要具备提供大量并发访问服务的能力,因此对于大负载的服务器来讲, CPU、I/O处理能力很快会成为瓶颈。由于单台服务器的性能总是有限的,简单的提高硬件性能并不能真正解决这个问题。为此,必须采用多服务器和负载均衡技术才能满足大量并发访问的需要。Linux 虚拟服务器(Linux Virtual Servers,LVS) 使用负载均衡技术将多台服务器组成一个虚拟服务器。它为适应快速增长的网络访问需求提供了一个负载能力易于扩展,而价格低廉的解决方案。
1、LVS结构与工作原理
LVS由前端的负载均衡器(Load Balancer,LB)和后端的真实服务器(Real Server,RS)群组成。RS间可通过局域网或广域网连接。LVS的这种结构对用户是透明的,用户只能看见一台作为LB的虚拟服务器(Virtual Server),而看不到提供服务的RS群。
当用户的请求发往虚拟服务器,LB根据设定的包转发策略和负载均衡调度算法将用户请求转发给RS。RS再将用户请求结果返回给用户。同请求包一样,应答包的返回方式也与包转发策略有关。
LVS的包转发策略有三种:
NAT (Network Address Translation)模式。LB收到用户请求包后,LB将请求包中虚拟服务器的IP地址转换为某个选定RS的IP地址,转发给RS;RS将应答包发给LB,LB将应答包中RS的IP转为虚拟服务器的IP地址,回送给用户。
IP隧道 (IP Tunneling)模式。LB收到用户请求包后,根据IP隧道协议封装该包,然后传给某个选定的RS;RS解出请求信息,直接将应答内容传给用户。此时要求RS和LB都要支持IP隧道协议。
DR(Direct Routing)模式。LB收到请求包后,将请求包中目标MAC地址转换为某个选定RS的MAC地址后将包转发出去,RS收到请求包后 ,可直接将应答内容传给用户。此时要求LB和所有RS都必须在一个物理段内,且LB与RS群共享一个虚拟IP。
2、IPVS软件结构与实现
LVS软件的核心是运行在LB上的IPVS,它使用基于IP层的负载均衡方法。IPVS的总体结构主要由IP包处理、负载均衡算法、系统配置与管理三个模块及虚拟服务器与真实服务器链表组成。
2.1 LVS对 IP包的处理模式
IP包处理用Linux 2.4内核的Netfilter框架完成。一个数据包通过Netfilter框架的过程如图所示:
通俗的说,netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上上登记了一些处理函数进行处理(如包过滤,NAT等,甚至可以是用户自定义的功能)。
NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),源地址转换在此点进行;
NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
NF_IP_FORWARD:要转发的包通过此检测点,FORWORD包过滤在此点进行;
NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行;
NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行。
在IP层代码中,有一些带有NF_HOOK宏的语句,如IP的转发函数中有:
NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,ip_forward_finish);
//其中NF_HOOK宏的定义基本如下:
#ifdef CONFIG_NETFILTER
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)
(list_empty(&nf_hooks[(pf)][(hook)])
? (okfn)(skb)
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
#else /* !CONFIG_NETFILTER */
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)
#endif /*CONFIG_NETFILTER*/
如果在编译内核时没有配置netfilter时,就相当于调用最后一个参数,此例中即执行ip_forward_finish函数;否则进入HOOK 点,执行通过nf_register_hook()登记的功能(这句话表达的可能比较含糊,实际是进入nf_hook_slow()函数,再由它执行登记的函数)。
NF_HOOK宏的参数分别为:
pf:协议族名,netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6,PF_DECnet等名字。
hook:HOOK点的名字,对于IP层,就是取上面的五个值;
skb:顾名思义
indev:进来的设备,以struct net_device结构表示;
outdev:出去的设备,以struct net_device结构表示;
okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。
这些点是已经在内核中定义好的,除非你是这部分内核代码的维护者,否则无权增加或修改,而在此检测点进行的处理,则可由用户指定。像packet filter,NAT,connection track这些功能,也是以这种方式提供的。正如netfilter的当初的设计目标--提供一个完善灵活的框架,为扩展功能提供方便。
如果我们想加入自己的代码,便要用nf_register_hook函数,其函数原型为:
int nf_register_hook(struct nf_hook_ops *reg)
struct nf_hook_ops://结构
struct nf_hook_ops
{
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;
int pf;
int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
其实,类似LVS的做法就是生成一个struct nf_hook_ops结构的实例,并用nf_register_hook将其HOOK上。其中list项要初始化为{NULL,NULL};由于一般在 IP层工作,pf总是PF_INET;hooknum就是HOOK点;一个HOOK点可能挂多个处理函数,谁先谁后,便要看优先级,即priority的指定了。netfilter_ipv4.h中用一个枚举类型指定了内置的处理函数的优先级:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
};
hook是提供的处理函数,也就是我们的主要工作,其原型为:
unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
它的五个参数将由NFHOOK宏传进去。
以上是NetFillter编写自己模块时的一些基本用法,接下来,我们来看一下LVS中是如何实现的。
3、LVS中Netfiler的实现
利用Netfilter,LVS处理数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子函数NF_IP_PRE_ROUTING [HOOK1]进行处理;然后进行路由选择,决定该数据报是需要转发还是发给本机;若该数据报是发被本机的,则该数据经过钩子函数 NF_IP_LOCAL_IN[HOOK2]处理后传递给上层协议;若该数据报应该被转发,则它被NF_IP_FORWARD[HOOK3]处理;经过转发的数据报经过最后一个钩子函数NF_IP_POST_ROUTING[HOOK4]处理以后,再传输到网络上。本地产生的数据经过钩子函数 NF_IP_LOCAL_OUT[HOOK5]处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING[HOOK4]处理后发送到网络上。
当启动IPVS加载ip_vs模块时,模块的初始化函数ip_vs_init( )注册了NF_IP_LOCAL_IN[HOOK2]、NF_IP_FORWARD[HOOK3]、NF_IP_POST_ROUTING[HOOK4] 钩子函数用于处理进出的数据报。
3.1 NF_IP_LOCAL_IN处理过程
用户向虚拟服务器发起请求,数据报经过NF_IP_LOCAL_IN[HOOK2],进入ip_vs_in( )进行处理。如果传入的是icmp数据报,则调用ip_vs_in_icmp( );否则继续判断是否为tcp/udp数据报,如果不是tcp/udp数据报,则函数返回NF_ACCEPT(让内核继续处理该数据报);余下情况便是处理tcp/udp数据报。首先,调用ip_vs_header_check( )检查报头,如果异常,则函数返回NF_DROP(丢弃该数据报)。接着,调用ip_vs_conn_in_get( )去ip_vs_conn_tab表中查找是否存在这样的连接:它的客户机和虚拟服务器的ip地址和端口号以及协议类型均与数据报中的相应信息一致。如果不存在相应连接,则意味着连接尚未建立,此时如果数据报为tcp的sync报文或udp数据报则查找相应的虚拟服务器;如果相应虚拟服务器存在但是已经满负荷,则返回NF_DROP;如果相应虚拟服务器存在并且未满负荷,那么调用ip_vs_schedule( )调度一个RS并创建一个新的连接,如果调度失败则调用ip_vs_leave( )继续传递或者丢弃数据报。如果存在相应连接,首先判断连接上的RS是否可用,如果不可用则处理相关信息后返回NF_DROP。找到已存在的连接或建立新的连接后,修改系统记录的相关信息如传入的数据报的个数等。如果这个连接在创建时绑定了特定的数据报传输函数,调用这个函数传输数据报,否则返回 NF_ACCEPT。
ip_vs_in()调用的ip_vs_in_icmp( )处理icmp报文。函数开始时检查数据报的长度,如果异常则返回NF_DROP。函数只处理由tcp/udp报文传送错误引起的目的不可达、源端被关闭或超时的icmp报文,其他情况则让内核处理。针对上述三类报文,首先检查检验和。如果检验和错误,直接返回NF_DROP;否则,分析返回的icmp差错信息,查找相应的连接是否存在。如果连接不存在,返回NF_ACCEPT;如果连接存在,根据连接信息,依次修改差错信息包头的ip地址与端口号及 ICMP数据报包头的ip地址,并重新计算和修改各个包头中的检验和,之后查找路由调用ip_send( )发送修改过的数据报,并返回NF_STOLEN(退出数据报的处理过程)。
ip_vs_in()调用的函数ip_vs_schedule( )为虚拟服务器调度可用的RS并建立相应连接。它将根据虚拟服务器绑定的调度算法分配一个RS,如果成功,则调用ip_vs_conn_new( )建立连接。ip_vs_conn_new( )将进行一系列初始化操作:设置连接的协议、ip地址、端口号、协议超时信息,绑定application helper、RS和数据报传输函数,最后调用ip_vs_conn_hash( )将这个连接插入哈希表ip_vs_conn_tab中。一个连接绑定的数据报传输函数,依据IPVS工作方式可分为ip_vs_nat_xmit( )、ip_vs_tunnel_xmit( )、ip_vs_dr_xmit( )。例如ip_vs_nat_xmit( )的主要操作是:修改报文的目的地址和目的端口为RS信息,重新计算并设置检验和,调用ip_send( )发送修改后的数据报。
3.2 NF_IP_FORWARD处理过程
数据报进入NF_IP_FORWARD后,将进入ip_vs_out( )进行处理。这个函数只在NAT方式下被调用。它首先判断数据报类型,如果为icmp数据报则直接调用ip_vs_out_icmp( );其次判断是否为tcp/udp数据报,如果不是这二者则返回NF_ACCEPT。余下就是tcp/udp数据报的处理。首先,调用 ip_vs_header_check( )检查报头,如果异常则返回NF_DROP。其次,调用ip_vs_conn_out_get( )判断是否存在相应的连接。若不存在相应连接:调用ip_vs_lookup_real_service( )去哈希表中查找发送数据报的RS是否仍然存在,如果RS存在且报文是tcp非复位报文或udp 报文,则调用icmp_send( )给RS发送目的不可达icmp报文并返回NF_STOLEN;其余情况下均返回NF_ACCEPT。若存在相应连接:检查数据报的检验和,如果错误则返回NF_DROP,如果正确,修改数据报,将源地址修改为虚拟服务器ip地址,源端口修改为虚拟服务器端口号,重新计算并设置检验和,并返回 NF_ACCEPT。
ip_vs_out_icmp( )的流程与ip_vs_in_icmp( )类似,只是修改数据报时有所区别:ip报头的源地址和差错信息中udp或tcp报头的目的地址均修改为虚拟服务器地址,差错信息中udp或tcp报头的目的端口号修改为虚拟服务器的端口号。
3.3 NF_IP_POST_ROUTING处理过程
NF_IP_POST_ROUTING钩子函数只在NAT方式下使用。数据报进入NF_IP_POST_ROUTING后,由 ip_vs_post_routing( )进行处理。它首先判断数据报是否经过IPVS,如果未经过则返回NF_ACCEPT;否则立刻传输数据报,函数返回NF_STOLEN,防止数据报被 iptable的规则修改。
4、LVS系统配置与管理
IPVS模块初始化时注册了setsockopt/getsockopt( ),ipvsadm命令调用这两个函数向IPVS内核模块传递ip_vs_rule_user结构的系统配置数据,完成系统的配置,实现虚拟服务器和RS 地址的添加、修改、删除操作。系统通过这些操作完成对虚拟服务器和RS链表的管理。
虚拟服务器的添加操作由ip_vs_add_service( )完成,该函数根据哈希算法向虚拟服务器哈希表添加一个新的节点,查找用户设定的调度算法并将此算法绑定到该节点;虚拟服务器的修改由 ip_vs_edit_service( )完成,此函数修改指定服务器的调度算法;虚拟服务器的删除由ip_vs_del_service( )完成,在删除一个虚拟服务器之前,必须先删除此虚拟服务器所带的所有RS,并解除虚拟服务器所绑定的调度算法。
与之类似,RS的添加、修改、删除操作分别由ip_vs_add_dest( )、ip_vs_edit_dest( )和ip_vs_edit_server( )完成。
5、负载均衡调度算法
前面已经提到,用户在添加一个虚拟服务时要绑定调度算法,这由ip_vs_bind_scheduler( )完成,调度算法的查找则由ip_vs_scheduler_get( )完成。ip_vs_scheduler_get( )根据调度算法的名字,调用ip_vs_sched_getbyname( )从调度算法队列中查找此调度算法,如果没找到则加载相应调度算法模块再查找,最后返回查找结果。
目前系统有八种负载均衡调度算法,具体如下:
rr:轮循调度(Round-Robin) 它将请求依次分配不同的RS,也就是在RS中均摊请求。这种算法简单,但是只适合于RS处理性能相差不大的情况。
wrr:加权轮循调度(Weighted Round-Robin) 它将依据不同RS的权值分配任务。权值较高的RS将优先获得任务,并且分配到的连接数将比权值较低的RS更多。相同权值的RS得到相同数目的连接数。
dh:目的地址哈希调度 (Destination Hashing) 以目的地址为关键字查找一个静态hash表来获得需要的RS。
sh:源地址哈希调度(Source Hashing) 以源地址为关键字查找一个静态hash表来获得需要的RS。
Lc:最小连接数调度(Least-Connection) IPVS表存储了所有的活动的连接。把新的连接请求发送到当前连接数最小的RS。
Wlc:加权最小连接数调度(Weighted Least-Connection) 假设各台RS的权值依次为Wi(I = 1..n),当前的TCP连接数依次为Ti(I=1..n),依次选取Ti/Wi为最小的RS作为下一个分配的RS。
Lblc:基于地址的最小连接数调度(Locality-Based Least-Connection) 将来自同一目的地址的请求分配给同一台RS如果这台服务器尚未满负荷,否则分配给连接数最小的RS,并以它为下一次分配的首先考虑。
Lblcr:基于地址的带重复最小连接数调度(Locality-Based Least-Connection with Replication) 对于某一目的地址,对应有一个RS子集。对此地址的请求,为它分配子集中连接数最小的RS;如果子集中所有的服务器均已满负荷,则从集群中选择一个连接数较小的服务器,将它加入到此子集并分配连接;若一定时间内,这个子集未被做任何修改,则将子集中负载最大的节点从子集删除。
ipvsadm命令参考
对ipvsadm 的命令参考,并根据自己使用的经验,进行了一个简单的翻译,希望
对ipvsadm 的使用者有一定的帮助。
为了更好的让大家理解这份命令手册,将手册里面用到的几个术语先简单的介绍
一下:
1,virtual-service-address:是指虚拟服务器的ip 地址
2,real-service-address:是指真实服务器的ip 地址
3,scheduler:调度方法
(lna@networksbase.com 翻译 ipvsadm v1.21 2004 年4 月)
ipvsadm 的用法和格式如下:
ipvsadm -A|E -t|u|f virutal-service-address:port [-s scheduler] [-p
[timeout]] [-M netmask]
ipvsadm -D -t|u|f virtual-service-address
ipvsadm -C
ipvsadm -R
ipvsadm -S [-n]
ipvsadm -a|e -t|u|f service-address:port -r real-server-address:port
[-g|i|m] [-w weight]
ipvsadm -d -t|u|f service-address -r server-address
ipvsadm -L|l [options]
ipvsadm -Z [-t|u|f service-address]
ipvsadm --set tcp tcpfin udp
ipvsadm --start-daemon state [--mcast-interface interface]
ipvsadm --stop-daemon
ipvsadm -h
命令选项解释:
有两种命令选项格式,长的和短的,具有相同的意思。在实际使用时,两种都可
以。
-A --add-service 在内核的虚拟服务器表中添加一条新的虚拟服务器记录。也
就是增加一台新的虚拟服务器。
-E --edit-service 编辑内核虚拟服务器表中的一条虚拟服务器记录。
-D --delete-service 删除内核虚拟服务器表中的一条虚拟服务器记录。
-C --clear 清除内核虚拟服务器表中的所有记录。
-R --restore 恢复虚拟服务器规则
-S --save 保存虚拟服务器规则,输出为-R 选项可读的格式
-a --add-server 在内核虚拟服务器表的一条记录里添加一条新的真实服务器
记录。也就是在一个虚拟服务器中增加一台新的真实服务器
-e --edit-server 编辑一条虚拟服务器记录中的某条真实服务器记录
-d --delete-server 删除一条虚拟服务器记录中的某条真实服务器记录
-L|-l --list 显示内核虚拟服务器表
-Z --zero 虚拟服务表计数器清零(清空当前的连接数量等)
--set tcp tcpfin udp 设置连接超时值
--start-daemon 启动同步守护进程。他后面可以是master 或backup,用来说
明LVS Router 是master 或是backup。在这个功能上也可以采用keepalived 的
VRRP 功能。
--stop-daemon 停止同步守护进程
-h --help 显示帮助信息
其他的选项:
-t --tcp-service service-address 说明虚拟服务器提供的是tcp 的服务
[vip:port] or [real-server-ip:port]
-u --udp-service service-address 说明虚拟服务器提供的是udp 的服务
[vip:port] or [real-server-ip:port]
-f --fwmark-service fwmark 说明是经过iptables 标记过的服务类型。
-s --scheduler scheduler 使用的调度算法,有这样几个选项
rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq,
默认的调度算法是: wlc.
-p --persistent [timeout] 持久稳固的服务。这个选项的意思是来自同一个客
户的多次请求,将被同一台真实的服务器处理。timeout 的默认值为300 秒。
-M --netmask netmask persistent granularity mask
-r --real-server server-address 真实的服务器[Real-Server:port]
-g --gatewaying 指定LVS 的工作模式为直接路由模式(也是LVS 默认的模式)
-i --ipip 指定LVS 的工作模式为隧道模式
-m --masquerading 指定LVS 的工作模式为NAT 模式
-w --weight weight 真实服务器的权值
--mcast-interface interface 指定组播的同步接口
-c --connection 显示LVS 目前的连接 如:ipvsadm -L -c
--timeout 显示tcp tcpfin udp 的timeout 值 如:ipvsadm -L --timeout
--daemon 显示同步守护进程状态
--stats 显示统计信息
--rate 显示速率信息
--sort 对虚拟服务器和真实服务器排序输出
--numeric -n 输出IP 地址和端口的数字形式
集群技术在不同领域应用的关键要素以及不同
集群技术的思想可以在不同领域予以应用,但是针对不同应用需要有特定的策略才行,策略是不同的,但是在确定策略时所考虑的关键要素是相同的。需要有一个关键要素范围列表。
不知 wensong 能否提供这方面的建议或者参考?
手册没有删除功能,我已经将此手册内容转移至个人BLOG,但是无法删除此项手册。
可伸缩网络服务的设计与实现
人类社会正在进入以网络为中心的信息时代,人们需要更快捷、更可靠、功能更丰富的网络服务。万维网的流行促进互联网使用的指数级增长,现在很多站点收到前所未有的访问负载,经常担心系统如何被扩展来满足不断增长的性能需求,同时系统如何保持24x7的可用性。未来的应用将需要更高的吞吐率、更好的交互性、更高的安全性,这要求服务平台具有更强的处理能力和更高的可用性。所以,如何给出合理的框架和有效的设计方法,来建立高性能、高可伸缩、高可用的网络服务,这是摆在研究者和系统设计者面前极富挑战性的任务。本文研究和设计的可伸缩网络服务便是围绕这一任务展开的。
可伸缩网络服务的体系结构
可伸缩网络服务的定义
可伸缩性(Scalability)是在当今计算机技术中经常用到的词汇。对于不同的人,可伸缩性有不同的含义。 现在,我们来定义可伸缩网络服务的含义。
可伸缩网络服务是指网络服务能随着用户数目的增长而扩展其性能,如在系统中增加服务器、内存或硬盘等;整个系统很容易被扩展,无需重新设置整个系统,无需中断服务。换句话说,系统管理员扩展系统的操作对最终用户是透明的,他们不会知道系统的改变。
可伸缩系统通常是高可用的系统。在部分硬件(如硬盘、服务器、子网络)和部分软件(如操作系统、服务进程)的失效情况下,系统可以继续提供服务,最终用户不会感知到整个服务的中断,除了正在失效点上处理请求的部分用户可能会收到服务处理失败,需要重新提交请求。Caching和复制是建立高可用系统的常用技术,建立多个副本会导致如何将原件的修改传播到多个副本上的问题。
实现可伸缩网络服务的方法一般是通过一对多的映射机制,将服务请求流分而治之(Divide and Conquer)到多个结点上处理。一对多的映射可以在很多层次上存在,如主机名上的DNS系统、网络层的TCP/IP、文件系统等。虚拟(Virtual)是描述一对多映射机制的词汇,将多个实体组成一个逻辑上的、虚拟的整体。例如,虚存(Virtual Memory)是现代操作系统中最典型的一对多映射机制,虚存建立一个虚拟内存空间,将它映射到多个物理内存上。
网络服务的需求
随着Internet的飞速发展和对我们生活的深入影响,越来越多的个人在互联网上购物、娱乐、休闲、与人沟通、获取信息;越来越多的企业把他们与顾客和业务伙伴之间的联络搬到互联网上,通过网络来完成交易,建立与客户之间的联系。互联网的用户数和网络流量正以几何级数增长,这对网络服务的可伸缩性提出很高的要求。例如,比较热门的Web站点会因为被访问次数急剧增长而不能及时处理用户的请求,导致用户进行长时间的等待,大大降低了服务质量。另外,随着电子商务等关键性应用在网上运行,任何例外的服务中断都将造成不可估量的损失,服务的高可用性也越来越重要。所以,对用硬件和软件方法实现高可伸缩、高可用网络服务的需求不断增长,这种需求可以归结以下几点:
- 可伸缩性(Scalability),当服务的负载增长时,系统能被扩展来满足需求,且不降低服务质量。
- 高可用性(Availability),尽管部分硬件和软件会发生故障,整个系统的服务必须是每天24小时每星期7天可用的。
- 可管理性(Manageability),整个系统可能在物理上很大,但应该容易管理。
- 价格有效性(Cost-effectiveness),整个系统实现是经济的、易支付的。
单服务器显然不能处理不断增长的负载。这种服务器升级方法有下列不足:一是升级过程繁琐,机器切换会使服务暂时中断,并造成原有计算资源的浪费;二是越往高端的服务器,所花费的代价越大;三是一旦该服务器或应用软件失效,会导致整个服务的中断。
通过高性能网络或局域网互联的服务器集群正成为实现高可伸缩的、高可用网络服务的有效结构。这种松耦合结构比紧耦合的多处理器系统具有更好的伸缩性和性能价格比,组成集群的PC服务器或RISC服务器和标准网络设备因为大规模生产,价格低,具有很高的性能价格比。但是,这里有很多挑战性的工作,如何在集群系统实现并行网络服务,它对外是透明的,它具有良好的可伸缩性和可用性。
针对上述需求,我们给出了基于IP层和基于内容请求分发的负载平衡调度解决方法,并在Linux内核中实现了这些方法,将一组服务器构成一个实现可伸缩的、高可用网络服务的服务器集群,我们称之为Linux虚拟服务器(Linux Virtual Server)。在LVS集群中,使得服务器集群的结构对客户是透明的,客户访问集群提供的网络服务就像访问一台高性能、高可用的服务器一样。客户程序不受服务器集群的影响不需作任何修改。系统的伸缩性通过在服务机群中透明地加入和删除一个节点来达到,通过检测节点或服务进程故障和正确地重置系统达到高可用性。
LVS集群的体系结构
LVS集群的通用结构
LVS集群采用IP负载均衡技术和基于内容请求分发技术。调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器。
图2.1:LVS集群的体系结构
为此,在设计时需要考虑系统的透明性、可伸缩性、高可用性和易管理性。LVS集群的体系结构如图2.1所示,它有三个主要组成部分:
- 负载调度器(load balancer),它是整个集群对外面的前端机,负责将客户的请求发送到一组服务器上执行,而客户认为服务是来自一个IP地址上的。它可以是用IP负载均衡技术的负载调度器,也可以是基于内容请求分发的负载调度器,还可以是两者的结合。
- 服务器池(server pool),是一组真正执行客户请求的服务器,执行的服务有WEB、MAIL、FTP和DNS等。
- 后端存储(backend storage),它为服务器池提供一个共享的存储区,这样很容易使得服务器池拥有相同的内容,提供相同的服务。
调度器采用IP负载均衡技术、基于内容请求分发技术或者两者相结合。在IP负载均衡技术中,需要服务器池拥有相同的内容提供相同的服务。当客户请求到达时,调度器只根据负载情况从服务器池中选出一个服务器,将该请求转发到选出的服务器,并记录这个调度;当这个请求的其他报文到达,也会被转发到前面选出的服务器。在基于内容请求分发技术中,服务器可以提供不同的服务,当客户请求到达时,调度器可根据请求的内容和服务器的情况选择服务器执行请求。因为所有的操作都是在操作系统核心空间中将完成的,它的调度开销很小,所以它具有很高的吞吐率。
服务器池的结点数目是可变的。当整个系统收到的负载超过目前所有结点的处理能力时,可以在服务器池中增加服务器来满足不断增长的请求负载。对大多数网络服务来说,结点与结点间不存在很强的相关性,所以整个系统的性能可以随着服务器池的结点数目增加而线性增长。
后端存储通常用容错的分布式文件系统,如AFS、GFS、Coda和Intermezzo等。分布式文件系统为各服务器提供共享的存储区,它们访问分布式文件系统就像访问本地文件系统一样。同时,分布式文件系统提供良好的伸缩性和可用性。然而,当不同服务器上的应用程序同时访问分布式文件系统上同一资源时,应用程序的访问冲突需要消解才能使得资源处于一致状态。这需要一个分布式锁管理器(Distributed Lock Manager),它可能是分布式文件系统内部提供的,也可能是外部的。开发者在写应用程序时,可以使用分布式锁管理器来保证应用程序在不同结点上并发访问的一致性。
负载调度器、服务器池和分布式文件系统通过高速网络相连,如100Mbps交换机、Myrinet、CompactNET和Gigabit交换机等。使用高速的网络,主要为避免当系统规模扩大时互联网络成为瓶颈。
Graphic Monitor是为系统管理员提供整个集群系统的监视器,它可以监视系统中每个结点的状况。Graphic Monitor是基于浏览器的,所以无论管理员在本地还是异地都可以监测系统的状况。为了安全的原因,浏览器要通过HTTPS(Secure HTTP)协议和身份认证后,才能进行系统监测,并进行系统的配置和管理。
为什么使用层次的体系结构
层次的体系结构可以使得层与层之间相互独立,允许在一个层次的已有软件在不同的系统中被重用。例如,调度器层提供了负载平衡、可伸缩性和高可用性等,在服务器层可以运行不同的网络服务,如Web、Cache、Mail和Media等,来提供不同的可伸缩网络服务。
为什么是共享存储
共享存储如分布式文件系统在这个LVS集群系统是可选项。当网络服务需要有相同的内容,共享存储是很好的选择,否则每台服务器需要将相同的内容复制到本地硬盘上。当系统存储的内容越多,这种不共享结构(Shared-nothing Structure)的代价越大,因为每台服务器需要一样大的存储空间,所有的更新需要涉及到每台服务器,系统的维护代价也很高。
共享存储为服务器组提供统一的存储空间,这使得系统的维护工作比较轻松,如Webmaster只需要更新共享存储中的页面,对所有的服务器都有效。分布式文件系统提供良好的伸缩性和可用性,当分布式文件系统的存储空间增加时,所有服务器的存储空间也随之增大。对于大多数Internet服务来说,它们都是读密集型(Read-intensive)的应用,分布式文件系统在每台服务器使用本地硬盘作Cache(如2Gbytes的空间),可以使得访问分布式文件系统本地的速度接近于访问本地硬盘。
高可用性
集群系统的特点是它在软硬件上都有冗余。系统的高可用性可以通过检测节点或服务进程故障和正确地重置系统来实现,使得系统收到的请求能被存活的结点处理。通常,我们在调度器上有资源监视进程来时刻监视各个服务器结点的健康状况,当服务器对ICMP ping不可达时或者她的网络服务在指定的时间没有响应时,资源监视进程通知操作系统内核将该服务器从调度列表中删除或者失效。这样,新的服务请求就不会被调度到坏的结点。资源监测程序能通过电子邮件或传呼机向管理员报告故障,一旦监测到服务进程恢复工作,通知调度器将其加入调度列表进行调度。另外,通过系统提供的管理程序,管理员可发命令随时将一台机器加入服务或切出服务,很方便进行系统维护。
现在前端的调度器有可能成为系统的单一失效点。为了避免调度器失效导致整个系统不能工作,我们需要设立调度器的备份。两个心跳进程(Heartbeat Daemon)分别在主、从调度器上运行,它们通过串口线和UDP等心跳线来相互汇报各自的健康情况。当从调度器不能听得主调度器的心跳时,从调度器会接管主调度器的工作来提供负载调度服务。这里,一般通过ARP欺骗(Gratuitous ARP)来接管集群的Virtual IP Address。当主调度器恢复时,这里有两种方法,一是主调度器自动变成从调度器,二是从调度器释放Virtual IP Address,主调度器收回Virtual IP Address并提供负载调度服务。然而,当主调度器故障后或者接管后,会导致已有的调度信息丢失,这需要客户程序重新发送请求。
可伸缩Web和媒体服务
基于LVS可伸缩Web和媒体服务的体系结构如图2.2所示:在前端是一个负载调度器,一般采用IP负载均衡技术来获得整个系统的高吞吐率;在第二层是服务器池,Web服务和媒体服务分别运行在每个结点上;第三层是数据存储,通过分布式文件系统使得每个服务器结点共享相同的数据。集群中结点间是通过高速网络相连的。
图2.2:基于LVS的可伸缩Web和媒体集群
分布式文件系统提供统一的存储空间,这使得系统的维护工作比较方便,且系统运行比较高效。当所有服务器结点超载时,管理员可以很快地加入新的结点来处理请求,而无需将Web文档等复制到结点的本地硬盘上。Webmaster可以看到统一的文档存储空间,维护和更新页面比较方便,对分布式文件系统中页面的修改对所有的服务器都有效。大的媒体文件(如视频文件)分段存储在分布式文件系统的多个结点上,可以提高文件系统的性能和文件服务器间的负载均衡。
IP负载调度器(即VS/DR方法,将在下一章详细叙述)可以分别将Web服务和媒体服务负载均衡地分发到各个服务器上,服务器将响应数据直接返回给客户,这样可以极大地提高系统的吞吐率。
Real公司以其高压缩比的音频视频格式和音频视频播放器RealPlayer而闻名。Real公司正在使用以上结构将由20台服务器组成的LVS可伸缩Web和媒体集群,为其全球用户提供Web和音频视频服务。Real公司的高级技术主管声称LVS击败所有他们尝试的商品化负载均衡产品。
可伸缩Cache服务
有效的网络Cache系统可以大大地减少网络流量、降低响应延时以及服务器的负载。但是,若Cache服务器超载而不能及时地处理请求,反而会增加响应延时。所以,Cache服务的可伸缩性很重要,当系统负载不断增长时,整个系统能被扩展来提高Cache服务的处理能力。尤其,在主干上的Cache服务可能需要几个Gbps的吞吐率,单台服务器(如SUN目前最高端的Enterprise 10000服务器)远不能达到这个吞吐率。可见,通过PC服务器集群实现可伸缩Cache服务是很有效的方法,也是性能价格比最高的方法。
基于LVS可伸缩Cache集群的体系结构如图2.3所示:在前端是一个负载调度器,一般采用IP负载均衡技术来获得整个系统的高吞吐率;在第二层是Cache服务器池,一般Cache服务器放置在接近主干Internet连接处,它们可以分布在不同的网络中。调度器可以有多个,放在离客户接近的地方,可实现透明的Cache服务。
图2.3:基于LVS的可伸缩Cache集群
Cache服务器采用本地硬盘来存储可缓存的对象,因为存储可缓存的对象是写操作,且占有一定的比例,通过本地硬盘可以提高I/O的访问速度。Cache服务器间有专用的多播通道,通过ICP协议(Internet Cache Protocol)来交互信息。当一台Cache服务器在本地硬盘中未命中当前请求时,它可以通过ICP查询其他Cache服务器是否有请求对象的副本,若存在,则从邻近的Cache服务器取该对象的副本,这样可以进一步提高Cache服务的命中率。
为150多所大学和地区服务的英国国家Cache网在1999年11月用以上LVS结构实现可伸缩的Cache集群,只用了原有50多台相互独立Cache服务器的一半,用户反映网络速度跟夏天一样快(学生放暑假)。可见,通过负载调度可以摸平单台服务器访问的毛刺(Burst),提高整个系统的资源利用率。
可伸缩邮件服务
随着Internet用户不断增长,很多ISP面临他们邮件服务器超载的问题。当邮件服务器不能容纳更多的用户帐号时,有些ISP买更高档的服务器来代替原有的,将原有服务器的信息(如用户邮件)迁移到新服务器是很繁琐的工作,会造成服务的中断;有些ISP设置新的服务器和新的邮件域名,新的邮件用户放置在新的服务器上,如上海电信现在用不同的邮件服务器public1.sta.net.cn、public2.sta.net.cn到public9.sta.net.cn放置用户的邮件帐号,这样静态地将用户分割到不同的服务器上,会造成邮件服务器负载不平衡,系统的资源利用率低,对用户来说邮件的地址比较难记。
图2.4:基于LVS的可伸缩邮件集群
可以利用LVS框架实现可伸缩邮件服务。它的体系结构如图2.4所示:在前端是一个采用IP负载均衡技术的负载调度器;在第二层是服务器池,有LDAP(Light-weight Directory Access Protocol)服务器和一组邮件服务器。第三层是数据存储,通过分布式文件系统来存储用户的邮件。集群中结点间是通过高速网络相连的。
用户的信息如用户名、口令、主目录和邮件容量限额等存储在LDAP服务器中,可以通过HTTPS让管理员进行用户管理。在各个邮件服务器上运行SMTP(Simple Mail Transfer Protocol)、POP3(Post Office Protocol version 3)、IMAP4(Internet Message Access Protocol version 4)和HTTP服务。SMTP接受和转发用户的邮件,SMTP服务进程查询LDAP服务器获得用户信息,再存储邮件。POP3和IMAP4通过LDAP服务器获得用户信息,口令验证后,处理用户的邮件访问请求。SMTP、POP3和IMAP4服务进程需要有机制避免用户邮件的读写冲突。HTTP服务是让用户通过浏览器可以访问邮件。负载调度器将四种服务请求负载均衡地调度到各个服务器上。
系统中可能的瓶颈是LDAP服务器,对LDAP服务中B+树的参数进行优化,再结合高端的服务器,可以获得较高的性能。若分布式文件系统没有多个存储结点间的负载均衡机制,则需要相应的邮件迁移机制来避免邮件访问的倾斜。
这样,这个集群系统对用户来说就像一个高性能、高可靠的邮件服务器(上海电信只要用一个邮件域名public.sta.net.cn)。当邮件用户不断增长时,只要在集群中增加服务器结点和存储结点。
地理分布LVS集群的体系结构
由于互联网用户分布在世界各地,通过地理分布的服务器让用户访问就近的服务器,来节省网络流量和提高响应速度。以下,我们给出地理分布的LVS集群系统,通过BGP路由插入使得用户访问离他们最近的服务器集群,并提供服务器集群之间的负载平衡。
体系结构
地理分布LVS集群的体系结构如图2.5所示:有三个LVS集群系统分布在Internet上,他们一般放置在不同区域的Internet数据中心(Internet Data Center)中,例如他们分别放在中国、美国和德国的三个不同的IDC中。三个LVS集群系统都有自己的分布式文件系统,它们的内容是相互复制的,提供相同的网络服务。它们共享一个Virtual IP Address来提供网络服务。当用户通过Virtual IP Address访问网络服务,离用户最近的LVS集群提供服务。例如,中国的用户访问在中国的LVS集群系统,美国的用户使用美国的LVS集群系统,这一切对用户来说是透明的。
图2.5:地理分布LVS集群的体系结构
地理分布LVS集群系统可以带来以下好处:
- 使得用户访问离他们最近的系统,对用户来说体验到更快的响应速度,对服务提供商来说节约网络带宽,降低成本。
- 避免灾难导致系统中止服务。当一个地点发生地震、火灾等使得系统或者网络连接瘫痪时,所有的用户访问可以很快由其他地点的LVS集群来提供。除了已建立的连接中断以外,这一切对用户来说都是透明的。
基于BGP的地理分布服务器集群调度
BGP(Border Gateway Protocol)是用于自治系统(Autonomous Systems)之间交换路由信息的协议,BGP可以设置路由策略,如政策、安全和经济上的考虑。
我们可以利用BGP协议在Internet的BGP路由器插入到Virtual IP Address的路由信息。在不同区域的LVS集群向它附近的BGP路由器广播到Virtual IP Address的路由信息,这样就存在多条到Virtual IP Address的路径,Internet的BGP路由器会根据评价函数选出最近的一条路径。这样,我们可以使得用户访问离他们最近的LVS集群。当一个LVS集群系统失效时,它的路由信息自然不会在Internet的BGP路由器中交换,BGP路由器会选择其他到Virtual IP Address的路径。这样,可以做到抗灾害性(Disaster Tolerance)。
图2.6:基于BGP的地理分布服务器集群调度例子
下面我们举一个基于BGP的地理分布服务器集群调度例子,它也是我们在实验室中测试的。测试的例子如图2.6所示,R11、R22分别在不同自治系统AS1和AS2中的Internet服务提供商(Internet Service Provider),R31和R32表示在自治系统AS3中两个ISP。两个LVS集群系统分别放置在两个不同数据中心IDC1和IDC2中,LB1和LB2分别是两个LVS集群系统的负载调度器,它们对外提供网络服务的IP地址为10.101.4.1。在第一个集群中,请求被调度到服务器10.101.5.11和10.101.5.12执行。在第二个集群中,请求被调度到服务器10.101.6.11和10.101.6.12。10.101.4.1是在自治系统AS5的网络10.101.4.0/24中。LB1上的BGP服务进程将网络10.101.4.0/24的路由信息广播到邻近的AS1,LB2上的BGP服务进程将网络10.101.4.0/24的路由信息广播到邻近的AS2中。
我们在R31端口10.101.3.1相连的BGP路由器上查到AS5的自治系统路径为3→1→5。在与R32端口10.101.3.9相连的BGP路由器上查到AS5的自治系统路径为3→2→5,并在该路由器上访问10.101.4.1上提供的Web服务,是由服务器10.101.6.11处理的。当我们关掉LB2后,在该BGP路由器上查到AS5的自治系统路径变为3→1→5。
服务器集群间的负载均衡
通过BGP插入路由信息的方法可以使得用户访问邻近的服务器集群,但是用户访问存在突发性,在某个区域的访问高峰可能会导致该区域的服务器集群系统超载,而其他服务器集群系统可能处于低负载状态,这时与其将请求在超载系统上排队等候,不如将请求送到远处的低负载系统上执行,可以提高响应速度。例如,中国用户在白天时间的一段访问高峰使得在中国的镜像服务器集群系统超载,而此时美国是晚上时间其镜像服务器集群系统处于低负载状态。
我们提出通过IP隧道的方法将请求从一个调度器转发到另一个调度器,再调度到真实服务器上执行。在下一章中将详细描述如何通过IP隧道作IP负载均衡调度。在通过IP隧道转发请求前,各个服务器集群需要定时交换负载信息(如2分钟交换一次),当确信远处的集群系统处于低负载状态,再转发请求。例如,当本地集群系统的综合负载大于1.1和远处集群系统的负载小于0.7时,调度器通过IP隧道将新的请求转发到远处的集群系统。若远处集群系统的负载超过0.7时,停止转发请求。当本地集群系统的负载降至1.0时,也停止转发请求,由本地服务器处理。这样,基本上可以避免两个调度器间相互转发一个请求。即使两个调度器间相互转发一个请求报文的例外情况发生,报文的TTL会降到零,报文被丢掉。
小结
我们分析了现在和将来网络服务的需求,提出可伸缩网络服务的体系结构,分为负载调度器、服务器池和后端存储三层结构。负载调度器采用IP负载均衡技术和基于内容请求分发技术。它的实现将在Linux操作系统进行,将一组服务器组成一个高可伸缩的、高可用的服务器,故称之为Linux Virtual Server。它提供了负载平衡、可伸缩性和高可用性,可以应用于建立很多可伸缩网络服务,如Web、Cache、Mail和Media等服务。
在此基础上,我们给出了地理分布的LVS集群系统,通过BGP插入路由信息的方法可以使得用户访问邻近的服务器集群,通过IP隧道实现服务器集群间的负载均衡,进一步提高响应速度。地理分布的LVS集群系统可以节约网络带宽,改善网络服务质量,有很好的抗灾害性。
IP负载均衡技术
上一章节讲述了可伸缩网络服务的几种结构,它们都需要一个前端调度器。在调度器的实现技术中,IP负载均衡技术是效率最高的。在已有的IP负载均衡技术中有通过网络地址转换(Network Address Translation)将一组服务器构成一个高性能的、高可用的虚拟服务器,我们称之为VS/NAT技术(Virtual Server via Network Address Translation),大多数商品化的IP负载均衡调度器产品都是使用此方法,如Cisco的LocalDirector、F5的Big/IP和Alteon的ACEDirector。在分析VS/NAT的缺点和网络服务的非对称性的基础上,我们提出通过IP隧道实现虚拟服务器的方法VS/TUN(Virtual Server via IP Tunneling),和通过直接路由实现虚拟服务器的方法VS/DR(Virtual Server via Direct Routing),它们可以极大地提高系统的伸缩性。
本章节将描述三种IP负载均衡技术VS/NAT、VS/TUN和VS/DR的工作原理,以及它们的优缺点。在以下描述中,我们称客户的socket和服务器的socket之间的数据通讯为连接,无论它们是使用TCP还是UDP协议。
通过NAT实现虚拟服务器(VS/NAT)
由于IPv4中IP地址空间的日益紧张和安全方面的原因,很多网络使用保留IP地址(10.0.0.0/255.0.0.0、172.16.0.0/255.128.0.0和192.168.0.0/255.255.0.0)[64, 65, 66]。这些地址不在Internet上使用,而是专门为内部网络预留的。当内部网络中的主机要访问Internet或被Internet访问时,就需要采用网络地址转换(Network Address Translation, 以下简称NAT),将内部地址转化为Internets上可用的外部地址。NAT的工作原理是报文头(目标地址、源地址和端口等)被正确改写后,客户相信它们连接一个IP地址,而不同IP地址的服务器组也认为它们是与客户直接相连的。由此,可以用NAT方法将不同IP地址的并行网络服务变成在一个IP地址上的一个虚拟服务。
VS/NAT的体系结构如图3.1所示。在一组服务器前有一个调度器,它们是通过Switch/HUB相连接的。这些服务器提供相同的网络服务、相同的内容,即不管请求被发送到哪一台服务器,执行结果是一样的。服务的内容可以复制到每台服务器的本地硬盘上,可以通过网络文件系统(如NFS)共享,也可以通过一个分布式文件系统来提供。
图3.1:VS/NAT的体系结构
客户通过Virtual IP Address(虚拟服务的IP地址)访问网络服务时,请求报文到达调度器,调度器根据连接调度算法从一组真实服务器中选出一台服务器,将报文的目标地址Virtual IP Address改写成选定服务器的地址,报文的目标端口改写成选定服务器的相应端口,最后将修改后的报文发送给选出的服务器。同时,调度器在连接Hash表中记录这个连接,当这个连接的下一个报文到达时,从连接Hash表中可以得到原选定服务器的地址和端口,进行同样的改写操作,并将报文传给原选定的服务器。当来自真实服务器的响应报文经过调度器时,调度器将报文的源地址和源端口改为Virtual IP Address和相应的端口,再把报文发给用户。我们在连接上引入一个状态机,不同的报文会使得连接处于不同的状态,不同的状态有不同的超时值。在TCP连接中,根据标准的TCP有限状态机进行状态迁移;在UDP中,我们只设置一个UDP状态。不同状态的超时值是可以设置的,在缺省情况下,SYN状态的超时为1分钟,ESTABLISHED状态的超时为15分钟,FIN状态的超时为1分钟;UDP状态的超时为5分钟。当连接终止或超时,调度器将这个连接从连接Hash表中删除。
这样,客户所看到的只是在Virtual IP Address上提供的服务,而服务器集群的结构对用户是透明的。对改写后的报文,应用增量调整Checksum的算法调整TCP Checksum的值,避免了扫描整个报文来计算Checksum的开销。
在一些网络服务中,它们将IP地址或者端口号在报文的数据中传送,若我们只对报文头的IP地址和端口号作转换,这样就会出现不一致性,服务会中断。所以,针对这些服务,需要编写相应的应用模块来转换报文数据中的IP地址或者端口号。我们所知道有这个问题的网络服务有FTP、IRC、H.323、CUSeeMe、Real Audio、Real Video、Vxtreme / Vosiac、VDOLive、VIVOActive、True Speech、RSTP、PPTP、StreamWorks、NTT AudioLink、NTT SoftwareVision、Yamaha MIDPlug、iChat Pager、Quake和Diablo。
下面,举个例子来进一步说明VS/NAT,如图3.2所示:
图3.2:VS/NAT的例子
VS/NAT的配置如下表所示,所有到IP地址为202.103.106.5和端口为80的流量都被负载均衡地调度的真实服务器172.16.0.2:80和172.16.0.3:8000上。目标地址为202.103.106.5:21的报文被转移到172.16.0.3:21上。而到其他端口的报文将被拒绝。
Protocol |
Virtual IP Address |
Port |
Real IP Address |
Port |
Weight |
TCP |
202.103.106.5 |
80 |
172.16.0.2 |
80 |
1 |
172.16.0.3 |
8000 |
2 |
TCP |
202.103.106.5 |
21 |
172.16.0.3 |
21 |
1 |
从以下的例子中,我们可以更详细地了解报文改写的流程。
访问Web服务的报文可能有以下的源地址和目标地址:
SOURCE |
202.100.1.2:3456 |
DEST |
202.103.106.5:80 |
调度器从调度列表中选出一台服务器,例如是172.16.0.3:8000。该报文会被改写为如下地址,并将它发送给选出的服务器。
SOURCE |
202.100.1.2:3456 |
DEST |
172.16.0.3:8000 |
从服务器返回到调度器的响应报文如下:
SOURCE |
172.16.0.3:8000 |
DEST |
202.100.1.2:3456 |
响应报文的源地址会被改写为虚拟服务的地址,再将报文发送给客户:
SOURCE |
202.103.106.5:80 |
DEST |
202.100.1.2:3456 |
这样,客户认为是从202.103.106.5:80服务得到正确的响应,而不会知道该请求是服务器172.16.0.2还是服务器172.16.0.3处理的。
通过IP隧道实现虚拟服务器(VS/TUN)
在VS/NAT的集群系统中,请求和响应的数据报文都需要通过负载调度器,当真实服务器的数目在10台和20台之间时,负载调度器将成为整个集群系统的新瓶颈。大多数Internet服务都有这样的特点:请求报文较短而响应报文往往包含大量的数据。如果能将请求和响应分开处理,即在负载调度器中只负责调度请求而响应直接返回给客户,将极大地提高整个集群系统的吞吐量。
IP隧道(IP tunneling)是将一个IP报文封装在另一个IP报文的技术,这可以使得目标为一个IP地址的数据报文能被封装和转发到另一个IP地址。IP隧道技术亦称为IP封装技术(IP encapsulation)。IP隧道主要用于移动主机和虚拟私有网络(Virtual Private Network),在其中隧道都是静态建立的,隧道一端有一个IP地址,另一端也有唯一的IP地址。
我们利用IP隧道技术将请求报文封装转发给后端服务器,响应报文能从后端服务器直接返回给客户。但在这里,后端服务器有一组而非一个,所以我们不可能静态地建立一一对应的隧道,而是动态地选择一台服务器,将请求报文封装和转发给选出的服务器。这样,我们可以利用IP隧道的原理将一组服务器上的网络服务组成在一个IP地址上的虚拟网络服务。VS/TUN的体系结构如图3.3所示,各个服务器将VIP地址配置在自己的IP隧道设备上。
图3.3:VS/TUN的体系结构
VS/TUN的工作流程如图3.4所示:它的连接调度和管理与VS/NAT中的一样,只是它的报文转发方法不同。调度器根据各个服务器的负载情况,动态地选择一台服务器,将请求报文封装在另一个IP报文中,再将封装后的IP报文转发给选出的服务器;服务器收到报文后,先将报文解封获得原来目标地址为VIP的报文,服务器发现VIP地址被配置在本地的IP隧道设备上,所以就处理这个请求,然后根据路由表将响应报文直接返回给客户。
图3.4:VS/TUN的工作流程
在这里,请求报文的目标地址为VIP,响应报文的源地址也为VIP,所以响应报文不需要作任何修改,可以直接返回给客户,客户认为得到正常的服务,而不会知道是哪一台服务器处理的。
在VS/TUN中,响应报文根据服务器的路由表直接返回给客户,而不经过负载调度器,所以负载调度器只处于从客户到服务器的半连接中,VS/TUN的TCP状态迁移与VS/NAT的不同。我们给出半连接的TCP有限状态机,如图3.5所示,圈表示状态,箭头表示状态间的转换,箭头上的标识表示在当前状态上收到该标识的输入,迁移到下一个状态。VS/TUN的TCP状态迁移是按照半连接的TCP有限状态机进行的。
图3.5:半连接的TCP有限状态机
通过直接路由实现虚拟服务器(VS/DR)
跟VS/TUN方法相同,VS/DR利用大多数Internet服务的非对称特点,负载调度器中只负责调度请求,而服务器直接将响应返回给客户,可以极大地提高整个集群系统的吞吐量。该方法与IBM的NetDispatcher产品中使用的方法类似,但IBM的NetDispatcher是非常昂贵的商品化产品,我们也不知道它内部所使用的机制,其中有些是IBM的专利。
VS/DR的体系结构如图3.6所示:调度器和服务器组都必须在物理上有一个网卡通过不分断的局域网相连,如通过交换机或者高速的HUB相连。VIP地址为调度器和服务器组共享,调度器配置的VIP地址是对外可见的,用于接收虚拟服务的请求报文;所有的服务器把VIP地址配置在各自的Non-ARP网络设备上,它对外面是不可见的,只是用于处理目标地址为VIP的网络请求。
图3.6:VS/DR的体系结构
VS/DR的工作流程如图3.7所示:它的连接调度和管理与VS/NAT和VS/TUN中的一样,它的报文转发方法又有不同,将报文直接路由给目标服务器。在VS/DR中,调度器根据各个服务器的负载情况,动态地选择一台服务器,不修改也不封装IP报文,而是将数据帧的MAC地址改为选出服务器的MAC地址,再将修改后的数据帧在与服务器组的局域网上发送。因为数据帧的MAC地址是选出的服务器,所以服务器肯定可以收到这个数据帧,从中可以获得该IP报文。当服务器发现报文的目标地址VIP是在本地的网络设备上,服务器处理这个报文,然后根据路由表将响应报文直接返回给客户。
图3.7:VS/DR的工作流程
在VS/DR中,请求报文的目标地址为VIP,响应报文的源地址也为VIP,所以响应报文不需要作任何修改,可以直接返回给客户,客户认为得到正常的服务,而不会知道是哪一台服务器处理的。
VS/DR负载调度器也只处于从客户到服务器的半连接中,按照半连接的TCP有限状态机进行状态迁移。
三种方法的优缺点比较
三种IP负载均衡技术的优缺点归纳在下表中:
|
VS/NAT |
VS/TUN |
VS/DR |
Server |
any |
Tunneling |
Non-arp device |
server network |
private |
LAN/WAN |
LAN |
server number |
low (10~20) |
High (100) |
High (100) |
server gateway |
load balancer |
own router |
Own router |
注:以上三种方法所能支持最大服务器数目的估计是假设调度器使用100M网卡,调度器的硬件配置与后端服务器的硬件配置相同,而且是对一般Web服务。使用更高的硬件配置(如千兆网卡和更快的处理器)作为调度器,调度器所能调度的服务器数量会相应增加。当应用不同时,服务器的数目也会相应地改变。所以,以上数据估计主要是为三种方法的伸缩性进行量化比较。
Virtual Server via NAT
VS/NAT 的优点是服务器可以运行任何支持TCP/IP的操作系统,它只需要一个IP地址配置在调度器上,服务器组可以用私有的IP地址。缺点是它的伸缩能力有限,当服务器结点数目升到20时,调度器本身有可能成为系统的新瓶颈,因为在VS/NAT中请求和响应报文都需要通过负载调度器。 我们在Pentium 166 处理器的主机上测得重写报文的平均延时为60us,性能更高的处理器上延时会短一些。假设TCP报文的平均长度为536 Bytes,则调度器的最大吞吐量为8.93 MBytes/s. 我们再假设每台服务器的吞吐量为800KBytes/s,这样一个调度器可以带动10台服务器。(注:这是很早以前测得的数据)
基于 VS/NAT的的集群系统可以适合许多服务器的性能要求。如果负载调度器成为系统新的瓶颈,可以有三种方法解决这个问题:混合方法、VS/TUN和 VS/DR。在DNS混合集群系统中,有若干个VS/NAT负载调度器,每个负载调度器带自己的服务器集群,同时这些负载调度器又通过RR-DNS组成简单的域名。但VS/TUN和VS/DR是提高系统吞吐量的更好方法。
对于那些将IP地址或者端口号在报文数据中传送的网络服务,需要编写相应的应用模块来转换报文数据中的IP地址或者端口号。这会带来实现的工作量,同时应用模块检查报文的开销会降低系统的吞吐率。
Virtual Server via IP Tunneling
在VS/TUN 的集群系统中,负载调度器只将请求调度到不同的后端服务器,后端服务器将应答的数据直接返回给用户。这样,负载调度器就可以处理大量的请求,它甚至可以调度百台以上的服务器(同等规模的服务器),而它不会成为系统的瓶颈。即使负载调度器只有100Mbps的全双工网卡,整个系统的最大吞吐量可超过 1Gbps。所以,VS/TUN可以极大地增加负载调度器调度的服务器数量。VS/TUN调度器可以调度上百台服务器,而它本身不会成为系统的瓶颈,可以用来构建高性能的超级服务器。
VS/TUN技术对服务器有要求,即所有的服务器必须支持“IP Tunneling”或者“IP Encapsulation”协议。目前,VS/TUN的后端服务器主要运行Linux操作系统,我们没对其他操作系统进行测试。因为“IP Tunneling”正成为各个操作系统的标准协议,所以VS/TUN应该会适用运行其他操作系统的后端服务器。
Virtual Server via Direct Routing
跟VS/TUN方法一样,VS/DR调度器只处理客户到服务器端的连接,响应数据可以直接从独立的网络路由返回给客户。这可以极大地提高LVS集群系统的伸缩性。
跟VS/TUN相比,这种方法没有IP隧道的开销,但是要求负载调度器与实际服务器都有一块网卡连在同一物理网段上,服务器网络设备(或者设备别名)不作ARP响应,或者能将报文重定向(Redirect)到本地的Socket端口上。
小结
本章主要讲述了可伸缩网络服务LVS框架中的三种IP负载均衡技术。在分析网络地址转换方法(VS/NAT)的缺点和网络服务的非对称性的基础上,我们给出了通过IP隧道实现虚拟服务器的方法VS/TUN,和通过直接路由实现虚拟服务器的方法VS/DR,极大地提高了系统的伸缩性。
负载调度
在上一章中,我们主要讲述了LVS集群中实现的三种IP负载均衡技术,它们主要解决系统的可伸缩性和透明性问题,如何通过负载调度器将请求高效地分发到不同的服务器执行,使得由多台独立计算机组成的集群系统成为一台虚拟服务器;客户端应用程序与集群系统交互时,就像与一台高性能的服务器交互一样。
本章节将主要讲述在负载调度器上的负载调度策略和算法,如何将请求流调度到各台服务器,使得各台服务器尽可能地保持负载均衡。文章主要由两个部分组成。第一部分描述IP负载均衡软件IPVS在内核中所实现的各种连接调度算法;第二部分给出一个动态反馈负载均衡算法(Dynamic-feedback load balancing),它结合内核中的加权连接调度算法,根据动态反馈回来的负载信息来调整服务器的权值,来进一步避免服务器间的负载不平衡。
在下面描述中,我们称客户的socket和服务器的socket之间的数据通讯为连接,无论它们是使用TCP还是UDP协议。对于UDP数据报文的调度,IPVS调度器也会为之建立调度记录并设置超时值(如5分钟);在设定的时间内,来自同一地址(IP地址和端口)的UDP数据包会被调度到同一台服务器。
内核中的连接调度算法
IPVS在内核中的负载均衡调度是以连接为粒度的。在HTTP协议(非持久)中,每个对象从WEB服务器上获取都需要建立一个TCP连接,同一用户的不同请求会被调度到不同的服务器上,所以这种细粒度的调度在一定程度上可以避免单个用户访问的突发性引起服务器间的负载不平衡。
在内核中的连接调度算法上,IPVS已实现了以下十种调度算法:
- 轮叫调度(Round-Robin Scheduling)
- 加权轮叫调度(Weighted Round-Robin Scheduling)
- 最小连接调度(Least-Connection Scheduling)
- 加权最小连接调度(Weighted Least-Connection Scheduling)
- 基于局部性的最少链接(Locality-Based Least Connections Scheduling)
- 带复制的基于局部性最少链接(Locality-Based Least Connections with Replication Scheduling)
- 目标地址散列调度(Destination Hashing Scheduling)
- 源地址散列调度(Source Hashing Scheduling)
- 最短预期延时调度(Shortest Expected Delay Scheduling)
- 不排队调度(Never Queue Scheduling)
下面,我们先介绍这八种连接调度算法的工作原理和算法流程,会在以后的文章中描述怎么用它们。
轮叫调度(Round-Robin Scheduling)
轮叫调度(Round Robin Scheduling)算法就是以轮叫的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。
在系统实现时,我们引入了一个额外条件,当服务器的权值为零时,表示该服务器不可用而不被调度。这样做的目的是将服务器切出服务(如屏蔽服务器故障和系统维护),同时与其他加权算法保持一致。所以,算法要作相应的改动,它的算法流程如下:
轮叫调度算法流程
假设有一组服务器S = {S0, S1, …, Sn-1},一个指示变量i表示上一次选择的
服务器,W(Si)表示服务器Si的权值。变量i被初始化为n-1,其中n > 0。
j = i;
do {
j = (j + 1) mod n;
if (W(Sj) > 0) {
i = j;
return Si;
}
} while (j != i);
return NULL;
轮叫调度算法假设所有服务器处理性能均相同,不管服务器的当前连接数和响应速度。该算法相对简单,不适用于服务器组中处理性能不一的情况,而且当请求服务时间变化比较大时,轮叫调度算法容易导致服务器间的负载不平衡。
虽然Round-Robin DNS方法也是以轮叫调度的方式将一个域名解析到多个IP地址,但轮叫DNS方法的调度粒度是基于每个域名服务器的,域名服务器对域名解析的缓存会妨碍轮叫解析域名生效,这会导致服务器间负载的严重不平衡。这里,IPVS轮叫调度算法的粒度是基于每个连接的,同一用户的不同连接都会被调度到不同的服务器上,所以这种细粒度的轮叫调度要比DNS的轮叫调度优越很多。
加权轮叫调度(Weighted Round-Robin Scheduling)
轮叫调度(Round Robin Scheduling)算法就是以轮叫的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。
在系统实现时,我们引入了一个额外条件,当服务器的权值为零时,表示该服务器不可用而不被调度。这样做的目的是将服务器切出服务(如屏蔽服务器故障和系统维护),同时与其他加权算法保持一致。所以,算法要作相应的改动,它的算法流程如下:
加权轮叫调度算法流程
假设有一组服务器S = {S0, S1, …, Sn-1},W(Si)表示服务器Si的权值,一个
指示变量i表示上一次选择的服务器,指示变量cw表示当前调度的权值,max(S)
表示集合S中所有服务器的最大权值,gcd(S)表示集合S中所有服务器权值的最大
公约数。变量i初始化为-1,cw初始化为零。
while (true) {
i = (i + 1) mod n;
if (i == 0) {
cw = cw - gcd(S);
if (cw <= 0) {
cw = max(S);
if (cw == 0)
return NULL;
}
}
if (W(Si) >= cw)
return Si;
}
轮叫调度算法假设所有服务器处理性能均相同,不管服务器的当前连接数和响应速度。该算法相对简单,不适用于服务器组中处理性能不一的情况,而且当请求服务时间变化比较大时,轮叫调度算法容易导致服务器间的负载不平衡。
虽然Round-Robin DNS方法也是以轮叫调度的方式将一个域名解析到多个IP地址,但轮叫DNS方法的调度粒度是基于每个域名服务器的,域名服务器对域名解析的缓存会妨碍轮叫解析域名生效,这会导致服务器间负载的严重不平衡。这里,IPVS轮叫调度算法的粒度是基于每个连接的,同一用户的不同连接都会被调度到不同的服务器上,所以这种细粒度的轮叫调度要比DNS的轮叫调度优越很多。
最小连接调度(Least-Connection Scheduling)
最小连接调度(Least-Connection Scheduling)算法是把新的连接请求分配到当前连接数最小的服务器。最小连接调度是一种动态调度算法,它通过服务器当前所活跃的连接数来估计服务器的负载情况。调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某台服务器,其连接数加1;当连接中止或超时,其连接数减一。
在系统实现时,我们也引入当服务器的权值为零时,表示该服务器不可用而不被调度,它的算法流程如下:
最小连接调度算法流程
假设有一组服务器S = {S0, S1, ..., Sn-1},W(Si)表示服务器Si的权值,
C(Si)表示服务器Si的当前连接数。
for (m = 0; m < n; m++) {
if (W(Sm) > 0) {
for (i = m+1; i < n; i++) {
if (W(Si) <= 0)
continue;
if (C(Si) < C(Sm))
m = i;
}
return Sm;
}
}
return NULL;
当各个服务器有相同的处理性能时,最小连接调度算法能把负载变化大的请求分布平滑到各个服务器上,所有处理时间比较长的请求不可能被发送到同一台服务器上。但是,当各个服务器的处理能力不同时,该算法并不理想,因为TCP连接处理请求后会进入TIME_WAIT状态,TCP的TIME_WAIT一般为2分钟,此时连接还占用服务器的资源,所以会出现这样情形,性能高的服务器已处理所收到的连接,连接处于TIME_WAIT状态,而性能低的服务器已经忙于处理所收到的连接,还不断地收到新的连接请求。
加权最小连接调度(Weighted Least-Connection Scheduling)
加权最小连接调度(Weighted Least-Connection Scheduling)算法是最小连接调度的超集,各个服务器用相应的权值表示其处理性能。服务器的缺省权值为1,系统管理员可以动态地设置服务器的权值。加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。加权最小连接调度的算法流程如下:
加权最小连接调度的算法流程
假设有一组服务器S = {S0, S1, ..., Sn-1},W(Si)表示服务器Si的权值,
C(Si)表示服务器Si的当前连接数。所有服务器当前连接数的总和为
CSUM = ΣC(Si) (i=0, 1, .. , n-1)。当前的新连接请求会被发送服务器Sm,
当且仅当服务器Sm满足以下条件
(C(Sm) / CSUM)/ W(Sm) = min { (C(Si) / CSUM) / W(Si)} (i=0, 1, . , n-1)
其中W(Si)不为零
因为CSUM在这一轮查找中是个常数,所以判断条件可以简化为
C(Sm) / W(Sm) = min { C(Si) / W(Si)} (i=0, 1, . , n-1)
其中W(Si)不为零
因为除法所需的CPU周期比乘法多,且在Linux内核中不允许浮点除法,服务器的
权值都大于零,所以判断条件C(Sm) / W(Sm) > C(Si) / W(Si) 可以进一步优化
为C(Sm)*W(Si) > C(Si)* W(Sm)。同时保证服务器的权值为零时,服务器不被调
度。所以,算法只要执行以下流程。
for (m = 0; m < n; m++) {
if (W(Sm) > 0) {
for (i = m+1; i < n; i++) {
if (C(Sm)*W(Si) > C(Si)*W(Sm))
m = i;
}
return Sm;
}
}
return NULL;
基于局部性的最少链接(Locality-Based Least Connections Scheduling)
基于局部性的最少链接调度(Locality-Based Least Connections Scheduling,以下简称为LBLC)算法是针对请求报文的目标IP地址的负载均衡调度,目前主要用于Cache集群系统,因为在Cache集群中客户请求报文的目标IP地址是变化的。这里假设任何后端服务器都可以处理任一请求,算法的设计目标是在服务器的负载基本平衡情况下,将相同目标IP地址的请求调度到同一台服务器,来提高各台服务器的访问局部性和主存Cache命中率,从而整个集群系统的处理能力。
LBLC调度算法先根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于其一半的工作负载,则用“最少链接”的原则选出一个可用的服务器,将请求发送到该服务器。该算法的详细流程如下:
LBLC调度算法流程
假设有一组服务器S = {S0, S1, ..., Sn-1},W(Si)表示服务器Si的权值,
C(Si)表示服务器Si的当前连接数。ServerNode[dest_ip]是一个关联变量,表示
目标IP地址所对应的服务器结点,一般来说它是通过Hash表实现的。WLC(S)表示
在集合S中的加权最小连接服务器,即前面的加权最小连接调度。Now为当前系统
时间。
if (ServerNode[dest_ip] is NULL) then {
n = WLC(S);
if (n is NULL) then return NULL;
ServerNode[dest_ip].server = n;
} else {
n = ServerNode[dest_ip].server;
if ((n is dead) OR
(C(n) > W(n) AND
there is a node m with C(m) < W(m)/2))) then {
n = WLC(S);
if (n is NULL) then return NULL;
ServerNode[dest_ip].server = n;
}
}
ServerNode[dest_ip].lastuse = Now;
return n;
此外,对关联变量ServerNode[dest_ip]要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。
带复制的基于局部性最少链接(Locality-Based Least Connections with Replication Scheduling)
带复制的基于局部性最少链接调度(Locality-Based Least Connections with Replication Scheduling,以下简称为LBLCR)算法也是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。它与LBLC算法的不同之处是它要维护从一个目标IP地址到一组服务器的映射,而LBLC算法维护从一个目标IP地址到一台服务器的映射。对于一个“热门”站点的服务请求,一台Cache 服务器可能会忙不过来处理这些请求。这时,LBLC调度算法会从所有的Cache服务器中按“最小连接”原则选出一台Cache服务器,映射该“热门”站点到这台Cache服务器,很快这台Cache服务器也会超载,就会重复上述过程选出新的Cache服务器。这样,可能会导致该“热门”站点的映像会出现在所有的Cache服务器上,降低了Cache服务器的使用效率。LBLCR调度算法将“热门”站点映射到一组Cache服务器(服务器集合),当该“热门”站点的请求负载增加时,会增加集合里的Cache服务器,来处理不断增长的负载;当该“热门”站点的请求负载降低时,会减少集合里的Cache服务器数目。这样,该“热门”站点的映像不太可能出现在所有的Cache服务器上,从而提供Cache集群系统的使用效率。
LBLCR算法先根据请求的目标IP地址找出该目标IP地址对应的服务器组;按“最小连接”原则从该服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载;则按“最小连接”原则从整个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。LBLCR调度算法的流程如下:
LBLCR调度算法流程
假设有一组服务器S = {S0, S1, ..., Sn-1},W(Si)表示服务器Si的权值,
C(Si)表示服务器Si的当前连接数。ServerSet[dest_ip]是一个关联变量,表示
目标IP地址所对应的服务器集合,一般来说它是通过Hash表实现的。WLC(S)表示
在集合S中的加权最小连接服务器,即前面的加权最小连接调度;WGC(S)表示在
集合S中的加权最大连接服务器。Now为当前系统时间,lastmod表示集合的最近
修改时间,T为对集合进行调整的设定时间。
if (ServerSet[dest_ip] is NULL) then {
n = WLC(S);
if (n is NULL) then return NULL;
add n into ServerSet[dest_ip];
} else {
n = WLC(ServerSet[dest_ip]);
if ((n is NULL) OR
(n is dead) OR
(C(n) > W(n) AND
there is a node m with C(m) < W(m)/2))) then {
n = WLC(S);
if (n is NULL) then return NULL;
add n into ServerSet[dest_ip];
} else
if (|ServerSet[dest_ip]| > 1 AND
Now - ServerSet[dest_ip].lastmod > T) then {
m = WGC(ServerSet[dest_ip]);
remove m from ServerSet[dest_ip];
}
}
ServerSet[dest_ip].lastuse = Now;
if (ServerSet[dest_ip] changed) then
ServerSet[dest_ip].lastmod = Now;
return n;
此外,对关联变量ServerSet[dest_ip]也要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间(lastuse)超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。
目标地址散列调度(Destination Hashing Scheduling)
目标地址散列调度(Destination Hashing Scheduling)算法也是针对目标IP地址的负载均衡,但它是一种静态映射算法,通过一个散列(Hash)函数将一个目标IP地址映射到一台服务器。
目标地址散列调度算法先根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。该算法的流程如下:
目标地址散列调度算法流程
假设有一组服务器S = {S0, S1, ..., Sn-1},W(Si)表示服务器Si的权值,
C(Si)表示服务器Si的当前连接数。ServerNode[]是一个有256个桶(Bucket)的
Hash表,一般来说服务器的数目会运小于256,当然表的大小也是可以调整的。
算法的初始化是将所有服务器顺序、循环地放置到ServerNode表中。若服务器的
连接数目大于2倍的权值,则表示服务器已超载。
n = ServerNode[hashkey(dest_ip)];
if ((n is dead) OR
(W(n) == 0) OR
(C(n) > 2*W(n))) then
return NULL;
return n;
在实现时,我们采用素数乘法Hash函数,通过乘以素数使得散列键值尽可能地达到较均匀的分布。所采用的素数乘法Hash函数如下:
素数乘法Hash函数
static inline unsigned hashkey(unsigned int dest_ip)
{
return (dest_ip* 2654435761UL) & HASH_TAB_MASK;
}
其中,2654435761UL是2到2^32 (4294967296)间接近于黄金分割的素数,
(sqrt(5) - 1) / 2 = 0.618033989
2654435761 / 4294967296 = 0.618033987
源地址散列调度(Source Hashing Scheduling)
源地址散列调度(Source Hashing Scheduling)算法正好与目标地址散列调度算法相反,它根据请求的源IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。它采用的散列函数与目标地址散列调度算法的相同。它的算法流程与目标地址散列调度算法的基本相似,除了将请求的目标IP地址换成请求的源IP地址,所以这里不一一叙述。
在实际应用中,源地址散列调度和目标地址散列调度可以结合使用在防火墙集群中,它们可以保证整个系统的唯一出入口。
动态反馈负载均衡算法
动态反馈负载均衡算法考虑服务器的实时负载和响应情况,不断调整服务器间处理请求的比例,来避免有些服务器超载时依然收到大量请求,从而提高整个系统的吞吐率。图4.1显示了该算法的工作环境,在负载调度器上运行Monitor Daemon进程,Monitor Daemon来监视和收集各个服务器的负载信息。Monitor Daemon可根据多个负载信息算出一个综合负载值。Monitor Daemon将各个服务器的综合负载值和当前权值算出一组新的权值,若新权值和当前权值的差值大于设定的阀值,Monitor Daemon将该服务器的权值设置到内核中的IPVS调度中,而在内核中连接调度一般采用加权轮叫调度算法或者加权最小连接调度算法。
图4.1:动态反馈负载均衡算法的工作环境
连接调度
当客户通过TCP连接访问网络访问时,服务所需的时间和所要消耗的计算资源是千差万别的,它依赖于很多因素。例如,它依赖于请求的服务类型、当前网络带宽的情况、以及当前服务器资源利用的情况。一些负载比较重的请求需要进行计算密集的查询、数据库访问、很长响应数据流;而负载比较轻的请求往往只需要读一个HTML页面或者进行很简单的计算。
请求处理时间的千差万别可能会导致服务器利用的倾斜(Skew),即服务器间的负载不平衡。例如,有一个WEB页面有A、B、C和D文件,其中D是大图像文件,浏览器需要建立四个连接来取这些文件。当多个用户通过浏览器同时访问该页面时,最极端的情况是所有D文件的请求被发到同一台服务器。所以说,有可能存在这样情况,有些服务器已经超负荷运行,而其他服务器基本是闲置着。同时,有些服务器已经忙不过来,有很长的请求队列,还不断地收到新的请求。反过来说,这会导致客户长时间的等待,觉得系统的服务质量差。
简单连接调度
简单连接调度可能会使得服务器倾斜的发生。在上面的例子中,若采用轮叫调度算法,且集群中正好有四台服务器,必有一台服务器总是收到D文件的请求。这种调度策略会导致整个系统资源的低利用率,因为有些资源被用尽导致客户的长时间等待,而其他资源空闲着。
实际TCP/IP流量的特征
文献说明网络流量是呈波浪型发生的,在一段较长时间的小流量后,会有一段大流量的访问,然后是小流量,这样跟波浪一样周期性地发生。文献揭示在WAN和LAN上网络流量存在自相似的特征,在WEB访问流也存在自相似性。这就需要一个动态反馈机制,利用服务器组的状态来应对访问流的自相似性。
动态反馈负载均衡机制
TCP/IP流量的特征通俗地说是有许多短事务和一些长事务组成,而长事务的工作量在整个工作量占有较高的比例。所以,我们要设计一种负载均衡算法,来避免长事务的请求总被分配到一些机器上,而是尽可能将带有毛刺(Burst)的分布分割成相对较均匀的分布。
我们提出基于动态反馈负载均衡机制,来控制新连接的分配,从而控制各个服务器的负载。例如,在IPVS调度器的内核中使用加权轮叫调度(Weighted Round-Robin Scheduling)算法来调度新的请求连接;在负载调度器的用户空间中运行Monitor Daemon。Monitor Daemon定时地监视和收集各个服务器的负载信息,根据多个负载信息算出一个综合负载值。Monitor Daemon将各个服务器的综合负载值和当前权值算出一组新的权值。当综合负载值表示服务器比较忙时,新算出的权值会比其当前权值要小,这样新分配到该服务器的请求数就会少一些。当综合负载值表示服务器处于低利用率时,新算出的权值会比其当前权值要大,来增加新分配到该服务器的请求数。若新权值和当前权值的差值大于设定的阀值,Monitor Daemon将该服务器的权值设置到内核中的IPVS调度中。过了一定的时间间隔(如2秒钟),Monitor Daemon再查询各个服务器的情况,并相应调整服务器的权值;这样周期性地进行。可以说,这是一个负反馈机制,使得服务器保持较好的利用率。
在加权轮叫调度算法中,当服务器的权值为零,已建立的连接会继续得到该服务器的服务,而新的连接不会分配到该服务器。系统管理员可以将一台服务器的权值设置为零,使得该服务器安静下来,当已有的连接都结束后,他可以将该服务器切出,对其进行维护。维护工作对系统都是不可少的,比如硬件升级和软件更新等,零权值使得服务器安静的功能很主要。所以,在动态反馈负载均衡机制中我们要保证该功能,当服务器的权值为零时,我们不对服务器的权值进行调整。
综合负载
在计算综合负载时,我们主要使用两大类负载信息:输入指标和服务器指标。输入指标是在调度器上收集到的,而服务器指标是在服务器上的各种负载信息。我们用综合负载来反映服务器当前的比较确切负载情况,对于不同的应用,会有不同的负载情况,这里我们引入各个负载信息的系数,来表示各个负载信息在综合负载中轻重。系统管理员根据不同应用的需求,调整各个负载信息的系数。另外,系统管理员设置收集负载信息的时间间隔。
输入指标主要是在单位时间内服务器收到新连接数与平均连接数的比例,它是在调度器上收集到的,所以这个指标是对服务器负载情况的一个估计值。在调度器上有各个服务器收到连接数的计数器,对于服务器Si,可以得到分别在时间T1和T2时的计数器值Ci1和Ci2,计算出在时间间隔T2-T1内服务器 Si收到新连接数Ni = Ci2 - Ci1。这样,得到一组服务器在时间间隔T2-T1内服务器Si收到新连接数{Ni},服务器Si的输入指标INPUTi为其新连接数与n台服务器收到平均连接数的比值,其公式为
服务器指标主要记录服务器各种负载信息,如服务器当前CPU负载LOADi、服务器当前磁盘使用情况Di、当前内存利用情况Mi和当前进程数目 Pi。有两种方法可以获得这些信息;一是在所有的服务器上运行着SNMP(Simple Network Management Protocol)服务进程,而在调度器上的Monitor Daemon通过SNMP向各个服务器查询获得这些信息;二是在服务器上实现和运行收集信息的Agent,由Agent定时地向Monitor Daemon报告负载信息。若服务器在设定的时间间隔内没有响应,Monitor Daemon认为服务器是不可达的,将服务器在调度器中的权值设置为零,不会有新的连接再被分配到该服务器;若在下一次服务器有响应,再对服务器的权值进行调整。再对这些数据进行处理,使其落在[0, ∞)的区间内,1表示负载正好,大于1表示服务器超载,小于1表示服务器处于低负载状态。获得调整后的数据有DISKi、MEMORYi和 PROCESSi。
另一个重要的服务器指标是服务器所提供服务的响应时间,它能比较好地反映服务器上请求等待队列的长度和请求的处理时间。调度器上的Monitor Daemon作为客户访问服务器所提供的服务,测得其响应时间。例如,测试从WEB服务器取一个HTML页面的响应延时,Monitor Daemon只要发送一个“GET /”请求到每个服务器,然后记录响应时间。若服务器在设定的时间间隔内没有响应,Monitor Daemon认为服务器是不可达的,将服务器在调度器中的权值设置为零。同样,我们对响应时间进行如上调整,得到RESPONSEi。
这里,我们引入一组可以动态调整的系数Ri来表示各个负载参数的重要程度,其中ΣRi = 1。综合负载可以通过以下公式计算出:
例如,在WEB服务器集群中,我们采用以下系数{0.1, 0.3, 0.1, 0.1, 0.1, 0.3},认为服务器的CPU负载和请求响应时间较其他参数重要一些。若当前的系数Ri不能很好地反映应用的负载,系统管理员可以对系数不断地修正,直到找到贴近当前应用的一组系数。
另外,关于查询时间间隔的设置,虽然很短的间隔可以更确切地反映各个服务器的负载,但是很频繁地查询(如1秒钟几次)会给调度器和服务器带来一定的负载,如频繁执行的Monitor Daemon在调度器会有一定的开销,同样频繁地查询服务器指标会服务器带来一定的开销。所以,这里要有个折衷(Tradeoff),我们一般建议将时间间隔设置在5到20秒之间。
权值计算
当服务器投入集群系统中使用时,系统管理员对服务器都设定一个初始权值DEFAULT_WEIGHTi,在内核的IPVS调度中也先使用这个权值。然后,随着服务器负载的变化,对权值进行调整。为了避免权值变成一个很大的值,我们对权值的范围作一个限制[DEFAULT_WEIGHTi, SCALE*DEFAULT_WEIGHTi],SCALE是可以调整的,它的缺省值为10。
Monitor Daemon周期性地运行,若DEFAULT_WEIGHTi不为零,则查询该服务器的各负载参数,并计算出综合负载值AGGREGATE_LOADi。我们引入以下权值计算公式,根据服务器的综合负载值调整其权值。
在公式中,0.95是我们想要达到的系统利用率,A是一个可调整的系数(缺省值为5)。当综合负载值为0.95时,服务器权值不变;当综合负载值大于0.95时,权值变小;当综合负载值小于0.95时,权值变大。若新权值大于SCALE*DEFAULT_WEIGHTi,我们将新权值设为 SCALE*DEFAULT_WEIGHTi。若新权值与当前权值的差异超过设定的阀值,则将新权值设置到内核中的IPVS调度参数中,否则避免打断 IPVS调度的开销。我们可以看出这是一个负反馈公式,会使得权值调整到一个稳定点,如系统达到理想利用率时,权值是不变的。
在实际使用中,若发现所有服务器的权值都小于他们的DEFAULT_WEIGHT,则说明整个服务器集群处于超载状态,这时需要加入新的服务器结点到集群中来处理部分负载;反之,若所有服务器的权值都接近于SCALE*DEFAULT_WEIGHT,则说明当前系统的负载都比较轻。
一个实现例子
我们在RedHat集群管理工具Piranha[6]中实现了一个简单的动态反馈负载均衡算法。在综合负载上,它只考虑服务器的CPU负载(Load Average),使用以下公式进行权值调整:
服务器权值调整区间为[DEFAULT_WEIGHTi, 10*DEFAULT_WEIGHTi],A为DEFAULT_WEIGHTi /2,而权值调整的阀值为DEFAULT_WEIGHTi /4。1是所想要达到的系统利用率。Piranha每隔20秒查询各台服务器的CPU负载,进行权值计算和调整。
小结
本章主要讲述了IP虚拟服务器在内核中实现的八种连接调度算法:
- 轮叫调度(Round-Robin Scheduling)
- 加权轮叫调度(Weighted Round-Robin Scheduling)
- 最小连接调度(Least-Connection Scheduling)
- 加权最小连接调度(Weighted Least-Connection Scheduling)
- 基于局部性的最少链接(Locality-Based Least Connections Scheduling)
- 带复制的基于局部性最少链接(Locality-Based Least Connections with Replication Scheduling)
- 目标地址散列调度(Destination Hashing Scheduling)
- 源地址散列调度(Source Hashing Scheduling)
因为请求的服务时间差异较大,内核中的连接调度算法容易使得服务器运行出现倾斜。为此,给出了一个动态反馈负载均衡算法,结合内核中的加权连接调度算法,根据动态反馈回来的负载信息来调整服务器的权值,来调整服务器间处理请求数的比例,从而避免服务器间的负载不平衡。动态反馈负载算法可以较好地避免服务器的倾斜,提高系统的资源使用效率,从而提高系统的吞吐率。
IP虚拟服务器的实现和性能测试
本章主要讲述IP负载均衡技术和连接调度算法在Linux内核中的实现,称之为IP虚拟服务器(IP Virtual Server,简写为IPVS),再叙述了实现中所遇到关键问题的解决方法和优化。最后,对IPVS软件进行性能测试,并列举该软件的应用情况。
系统实现的基本框架
我们分别在Linux 内核2.0和内核2.2中修改了TCP/IP协议栈,在IP层截取和改写/转发IP报文,实现了三种IP负载均衡技术,并提供了一个ipvsadm程序进行虚拟服务器的配置和管理。在Linux 内核2.4和2.6中,我们把它实现为NetFilter的一个模块,很多代码作了改写和进一步优化,目前版本已在网上发布,根据反馈信息该版本已经较稳定。
图5.1:系统的主要功能模块
系统的主要功能模块如图5.1所示,“VS Schedule & Control Module”是虚拟服务器的主控模块,它挂接在IP报文遍历的LOCAL_IN链和IP_FORWARD链两处,用于截取/改写IP报文;“VS Rules Table”用于存放虚拟服务器的规则,“Connections Hash Table”表是用于记录当前连接的Hash表;“Stale Connection Collector”模块用于回收已经过时的连接;“Statistics Data”表记录IPVS的统计信息。用户空间的ipvsadm管理程序通过setsockopt()函数将虚拟服务器的规则写入“VS Rules Table”表中,通过/proc文件系统把“VS Rules Table”表中的规则读出。
当一个IP报文到达时,若报文的目标地址是本地的IP地址,IP报文会转到LOCAL_IN链上,否则转到IP_FORWARD链上。IPVS模块主要挂接在LOCAL_IN链和IP_FORWARD链两处。当一个目标地址为Virtual IP Address的报文到达时,该报文会被挂接在LOCAL_IN链上的IPVS程序捕获,若该报文属于在连接Hash表中一个已建立的连接,则根据连接的信息将该报文发送到目标服务器,否则该报文为SYN时,根据连接调度算法从一组真实服务器中选出一台服务器,根据IP负载调度设置的规则将报文发送给选出的服务器,并在连接Hash表中记录这个连接。挂接在IP_FORWARD链上的IPVS程序是改写VS/NAT中服务器响应报文的地址。
连接的Hash表可以容纳几百万个并发连接,在Linux内核2.2和内核2.4的IP虚拟服务器版本中每个连接只占用128Bytes有效内存,例如一个有256M可用内存的调度器就可调度两百万个并发连接。连接Hash表的桶个数可以由用户根据实际应用来设定,来降低Hash的冲突率。
在每个连接的结构中有连接的报文发送方式、状态和超时等。报文发送方式有VS/NAT、VS/TUN、VS/DR和本地结点,报文会被以连接中设定的方式发送到目标服务器。这意味着在一个服务器集群中,我们可以用不同的方式(VS/NAT、VS/TUN或VS/DR)来调度不同的服务器。连接的状态和超时用于记录连接当前所在的状态,如SYN_REC、ESTABLISHED和FIN_WAIT等,不同的状态有不同的超时值。
系统实现的若干问题
Hash表
在系统实现中,我们多处用到Hash表,如连接的查找和虚拟服务的查找。选择Hash表优先Tree等复杂数据结构的原因是Hash表的插入和删除的复杂度为O(1),而Tree的复杂度为O(log(n))。Hash表的查找复杂度为O(n/m),其中n为Hash表中对象的个数,m为Hash表的桶个数。当对象在Hash表中均匀分布和Hash表的桶个数与对象个数一样多时,Hash表的查找复杂度可以接近O(1)。
因为连接的Hash表要容纳几百万个并发连接,并且连接的Hash表是系统使用最频繁的部分,任何一个报文到达都需要查找连接Hash表,所以如何选择一个高效的连接Hash函数直接影响到系统的性能。连接Hash函数的选择要考虑到两个因素,一个是尽可能地降低Hash表的冲突率,另一个是Hash函数的计算不是很复杂。
一个连接有客户的
、虚拟服务的
和目标服务器的
等元素,其中客户的
是每个连接都不相同的,后两者在不同的连接经常重叠。所以,我们选择客户的
来计算Hash Key。在IPVS版本中,我们用以下快速的移位异或Hash函数来计算。
#define IP_VS_TAB_BITS CONFIG_IP _VS_TAB_BITS
#define IP_VS_TAB_SIZE (1 << IP_VS_TAB_BITS)
#define IP_VS_TAB_MASK (IP_VS_TAB_SIZE - 1)
inline unsigned ip_vs_hash_key(unsigned proto, unsigned addr, unsigned port)
{
return (proto ^ addr ^ (addr>>IP_VS_TAB_BITS) ^ port)
& IP_VS_TAB_MASK;
}
为了评价Hash函数的效率,我们从一个运行IPVS的真实站点上取当前连接的样本,它一共含有35652个并发连接。在有64K桶的Hash表中,连接分布如下:
桶的长度(Lj) 该长度桶的个数(Nj)
5 16
4 126
3 980
2 5614
1 20900
通过以下公式算出所有连接查找一次的代价:
所有连接查找一次的代价为45122,每个连接查找的平均代价为1.266(即45122/35652)。我们对素数乘法Hash函数进行分析,素数乘法Hash函数是通过乘以素数使得Hash键值达到较均匀的分布。
inline unsigned ip_vs_hash_key(unsigned proto, unsigned addr, unsigned port)
{
return ((proto+addr+port)* 2654435761UL) & IP_VS_TAB_MASK;
}
其中,2654435761UL是2到2^32间黄金分割的素数,
2654435761 / 4294967296 = 0.618033987
在有64K桶的Hash表中,素数乘法Hash函数的总查找代价为45287。可见,现在IPVS中使用的移位异或Hash函数还比较高效。
垃圾回收
为了将不再被使用的连接单元回收,我们在连接上设置一个定时器,当连接超时,将该连接回收。因为系统中有可能存在几百万个并发连接,若使用内核中的定时器,几百万个连接单元系统的定时器的列表中,系统每隔1/100秒进行单元的迁移和回收已超时的连接单元,这会占用很多系统的开销。
因为连接的回收并不需要很精确,我们可以让系统的定时器每隔1秒启动连接回收程序来回收那些超时的连接。为此,我们设计了一个慢定时器,连接的定时都是以1秒钟为单位。用三个时间大转盘,第一个转盘有1024个刻度,定时在1024秒钟之内的连接都挂接在第一个转盘的各个刻度上;第二个转盘有256个刻度,定时在[210, 218)区间的落在第二个转盘上;第三个转盘有256个刻度,定时在[218, 226)区间的落在第三个转盘上。
慢定时器处理程序每隔1秒由系统定时器启动运行一次,将第一个转盘当前指针上的连接进行回收,再将指针顺时针转一格。若指针正好转了一圈,则对第二个转盘当前指针上的连接进行操作,根据他们的定时迁移到第一个转盘上,再将指针顺时针转一格。若第二个转盘的指针正好转了一圈,则对第三个转盘当前指针上的连接进行操作,根据他们的定时迁移到第二个转盘上。
使用这种慢定时器极大地提高了过期连接的回收问题。在最初的版本中,我们是直接使用系统的定时器进行超时连接的回收,但是当并发连接数增加到500,000时,系统的CPU使用率已接近饱和,所以我们重新设计了这种高效的垃圾回收机制。
ICMP处理
负载调度器需要实现虚拟服务的ICMP处理,这样进来的虚拟服务ICMP报文会被改写或者转发给正确的后端服务器,出去的ICMP报文也会被正确地改写和发送给客户。ICMP处理对于客户和服务器间的错误和控制通知是非常重要的。
ICMP消息可以发现在客户和服务器间的MTU(Maximum Transfer Unit)值。在客户的请求被VS/DR调度到一台服务器执行,服务器将执行结果直接返回给客户。例如响应报文的MTU为1500个字节,在服务器到客户的路径中有一段线路的MTU值为512个字节,这时路由器会向报文的源地址(即虚拟服务的地址)发送一个需要分段为512个字节的ICMP消息。该ICMP消息会到达调度器,调度器需要将ICMP消息中原报文的头取出,再在Hash表中找到相应的连接,然后将该ICMP消息转发给对应的服务器。这样,服务器就会将原有的报文分段成512个字节进行发送,客户得到服务的响应。
可装卸的调度模块
为了提高系统的灵活性,我们将连接调度做成可装卸的模块(Loadable Modules),如ip_vs_rr.o、ip_vs_wrr.o、ip_vs_lc.o、ip_vs_wlc.o、ip_vs_lblc.o、ip_vs_lblcr.o、ip_vs_dh.o、ip_vs_sh.o、ip_vs_sed.o和ip_vs_nq.o。当虚拟服务设置时,会将相应的模块调到内核中。这样,有助于提高系统的使用效率,不装载不被使用的资源。
锁的处理和优化
在系统中虚拟服务规则的读和写需要锁来保证处理的一致性。在连接的Hash表中,同样需要锁来保证连接加入和删除的一致性。连接的Hash表是系统使用最频繁的资源,任何一个报文到达都需要查找连接Hash表。如果只有一个锁来管理连接Hash表的操作,锁的冲突率会很高。为此,我们引入有n个元素的锁数组,每个锁分别控制1/n的连接Hash表,增加锁的粒度,降低锁的冲突率。在两个CPU的SMP机器上,假设CPU操作Hash表的部位是随机分布的,则两个CPU同时操作同一区域的概率为1/n。在系统中n的缺省值为16。
连接的相关性
到现在为止,我们假设每个连接都相互独立的,所以每个连接被分配到一个服务器,跟过去和现在的分配没有任何关系。但是,有时由于功能或者性能方面的原因,一些来自同一用户的不同连接必须被分配到同一台服务器上。
FTP是一个因为功能设计导致连接相关性的例子。在FTP使用中,客户需要建立一个控制连接与服务器交互命令,建立其他数据连接来传输大量的数据。在主动的FTP模式下,客户通知FTP服务器它所监听的端口,服务器主动地建立到客户的数据连接,服务器的端口一般为20。IPVS调度器可以检查报文的内容,可以获得客户通知FTP服务器它所监听的端口,然后在调度器的连接Hash表中建立一个相应的连接,这样服务器主动建立的连接可以经过调度器。但是,在被动的FTP模式下,服务器告诉客户它所监听的数据端口,服务器被动地等待客户的连接。在VS/TUN或VS/DR下,IPVS调度器是在从客户到服务器的半连接上,服务器将响应报文直接发给客户,IPVS调度器不可能获得服务器告诉客户它所监听的数据端口。
SSL(Secure Socket Layer)是一个因为性能方面原因导致连接相关性的例子。当一个SSL连接请求建立时,一个SSL的键值(SSL Key)必须要在服务器和客户进行选择和交换,然后数据的传送都要经过这个键值进行加密,来保证数据的安全性。因为客户和服务器协商和生成SSL Key是非常耗时的,所以SSL协议在SSL Key的生命周期内,以后的连接可以用这个SSL Key和服务器交换数据。如果IPVS调度器将以后的连接调度到其他服务器,这会导致连接的失败。
我们现在解决连接相关性的方法是持久服务(Persistent Service)的处理。使用两个模板来表示客户和服务器之间的持久服务,模板〈protocol, client_ip, 0, virtual_ip, virtual_port, dest_ip, dest_port〉表示来自同一客户client_ip到虚拟服务〈virtual_ip, virtual_port〉的任何连接都会被转发到目标服务器〈dest_ip, dest_port〉,模板〈protocol, client_ip, 0, virtual_ip, 0 dest_ip, 0〉表示来自同一客户client_ip到虚拟服务器virtual_ip的任何连接都会被转发到目标服务器dest_ip,前者用于单一的持久服务,后者用于所有端口的持久服务。当一个客户访问一个持久服务时,IPVS调度器会在连接Hash表中建立一个模板,这个模板会在一个可设置的时间内过期,如果模板有所控制的连接没有过期,则这个模板不会过期。在这个模板没有过期前,所有来自这个客户到相应服务的任何连接会被发送到同一台服务器。
持久服务还可设置持久的粒度,即可设置将来自一个C类地址范围的所有客户请求发送到同一台服务器。这个特征可以保证当使用多个代理服务器的客户访问集群时,所有的连接会被发送到同一服务器。
虽然持久服务可能会导致服务器间轻微的负载不平衡,因为持久服务的一般调度粒度是基于每个客户机的,但是这有效地解决连接相关性问题,如FTP、SSL和HTTP Cookie等。
本地结点
本地结点(Local Node)功能是让调度器本身也能处理请求,在调度时就相当一个本地结点一样,在实现时就是根据配置将部分连接转交给在用户空间的服务进程,由服务进程处理完请求将结果返回给客户。该功能的用处如下:
当集群中服务器结点较少时,如只有三、四个结点,调度器在调度它们时,大部分的CPU资源是闲置着,可以利用本地结点功能让调度器也能处理一部分请求,来提高系统资源的利用率。
在分布式服务器中,我们可以利用IPVS调度的本地结点功能,在每台服务器上加载IPVS调度模块,在一般情况下,利用本地结点功能服务器处理到达的请求,当管理程序发现服务器超载时,管理程序将其他服务器加入调度序列中,将部分请求调度到其他负载较轻的服务器上执行。
在地理上分布的服务器镜像上,镜像服务器利用本地结点功能功能处理请求,当服务器超载时,服务器通过VS/TUN将请求调度到邻近且负载较轻的服务器上。
数据统计
在IPVS虚拟服务使用情况的统计上,我们实现了如下计数器:
- 调度器所处理报文的总数
- 调度器所处理连接的总数
- 调度器中所有并发连接的数目
- 每个虚拟服务处理连接的总数
- 每个服务器所有并发连接的数目
在单位时间内,我们可以根据调度器所处理报文总数之差得出调度器的报文处理速率,根据调度器所处理连接总数之差得出调度器的连接处理速率。同样,我们可以算出每个虚拟服务的连接处理速率。
防卫策略
IPVS调度器本身可以利用Linux内核报文过滤功能设置成一个防火墙,只许可虚拟服务的报文进入,丢掉其他报文。调度器的脆弱之处在于它需要记录每个连接的状态,每个连接需要占用128个字节,一些恶意攻击可能使得调度器生成越来越多的并发连接,直到所有的内存耗尽,系统出现拒绝服务(Denial of Service)。但是,一般SYN-Flooding攻击调度器是非常困难的,假设系统有128Mbytes可用内存,则系统可以容纳一百万个并发连接,每个处理接受SYN连接的超时(Timeout)为60秒,SYN-Flooding主机需要生成16,666 Packets/Second的流量,这往往需要分布式SYN-Flooding工具,由许多个SYN-Flooding主机同时来攻击调度器。
为了避免此类大规模的恶意攻击,我们在调度器中实现三种针对DoS攻击的防卫策略。它们是随机丢掉连接、在调度报文前丢掉1/rate的报文、使用更安全的TCP状态转换和更短的超时。在系统中有三个开关分别控制它们,开关处于0表示该功能完全关掉。1和2表示自动状态,当系统的有效内存低于设置的阀值时,该防卫策略被激活,开关从1状态迁移到2状态;当系统的有效内存高于设置的阀值时,该防卫策略被关掉,开关从2状态迁移到1状态。3表示该策略永远被激活。
调度器间的状态同步
尽管IPVS虚拟服务器软件已被证明相当鲁棒,但调度器有可能因为其他原因而失效,如机器的硬件故障和网络线路故障等。所以,我们引入一个从调度器作为主调度器的备份,当主调度器失效时,从调度器将接管VIP等地址进行负载均衡调度。在现在的解决方案中,当主调度器失效时,调度器上所有已建立连接的状态信息将丢失,已有的连接会中断,客户需要向重新连接,从调度器才会将新连接调度到各个服务器上。这对客户会造成一定的不便。为此,我们考虑一种高效机制将主调度器的状态信息及时地复制到从调度器,当从调度器接管时,绝大部分已建立的连接会持续下去。
因为调度器的连接吞吐率是非常高的,如每秒处理一万多个连接,如何将这些变化非常快的状态信息高效地复制到另一台服务器?我们设计利用内核线程实现主从同步进程,在操作系统的内核中,直接将状态信息发送到从调度器上,可以避免用户空间和核心的切换开销。其结构如图5.2所示:
图5.2:主从调度器间的状态同步
在主从调度器的操作系统内核中分别有两个内核线程ConnSyncd,主调度器上的ConnSyncd每隔1/10秒钟唤醒一次从更新队列中将更新信息读出,将更新信息发给从调度器上的ConnSyncd,然后在从调度器内核中生成相应的状态信息。为了减少主从调度器间的通讯开销,在主调度器的更新队列中只放新连接生成的信息,在从调度器中生成连接信息,设置定时器,当连接超时,该连接会自动被删除。
主从调度器间状态复制的代码正在编写中。因为主从调度器间的状态复制会降低调度器的吞吐率,所以主从调度器间状态复制会以模块的形式出现,当用户特别需要时,可以将该模块加入内核中。
性能测试
在美国VA Linux公司的高级工程师告诉我,他们在实验室中用一个IPVS调度器(VS/DR方式)和58台WEB服务器组成一个WEB集群,想测试在真实网络服务负载下IPVS调度器的性能,但是他们没有测试IPVS调度器的性能,当58台WEB服务器都已经满负荷运行时,IPVS调度器还处于很低的利用率(小于0.2)。他们认为系统的瓶颈可能在网卡的速度和报文的转发速度,估计要到几百台服务器时,IPVS调度器会成为整个系统的瓶颈。
我们没有足够的物理设备来测试在真实网络服务负载下IPVS调度器的性能,况且在更高的硬件配置下(如两块1Gbps网卡和SMP机器),调度器肯定会有更高的性能。为了更好地估计在VS/DR和VS/TUN方式下IPVS调度器的性能,我们专门写一个测试程序testlvs,程序不断地生成SYN的报文发送给调度器上的虚拟服务,调度器会生成一个连接并将SYN报文转发给后端服务器,我们设置后端服务器在路由时将这些SYN报文丢掉,在后端服务器的网卡上我们可以获得进来的报文速率,从而估计出调度器的报文处理速率。
我们的测试环境如图5.3所示:有四台客户机、三台服务器和一个Pentium III 500MHz、128M内存和2块100M网卡的调度器,四台客户机和调度器通过一个100M交换机相连,三台服务器和调度器也通过一个100M交换机相连。它们都运行Linux操作系统。
图5.3:IPVS调度器的性能测试环境
在VS/NAT的性能测试中,我们分别在三台服务器启动三个Netpipe服务进程,在调度器上开启三个虚拟服务通过网络地址转换到三台服务器,用三台客户机运行Netpipe分别向三个虚拟服务进行测试,调度器已经满负荷运行,获得三个Netpipe的累计吞吐率为89Mbps。在正常网络服务下,我们假设每个连接的平均数据量为10Kbytes,VS/NAT每秒处理的连接数为1112.5 Connections/Second。
在VS/DR和VS/TUN的性能测试中,我们设置后端服务器在路由时将这些SYN报文丢掉,后端服务器就像一个黑洞将报文吸掉,它的处理开销很小,所以我们在后端服务器只用两台。我们在后端服务器上运行程序来测试进来报文的速率,在调度器上将一虚拟服务负载均衡到两台后端服务器,然后在四台客户机上运行testlvs不断地向虚拟服务发SYN报文,报文的源地址是随机生成的,每个报文的大小为40个字节。测试得VS/DR的处理速率为150,100 packets/second,VS/TUN的处理速率为141,000packets/second,可见将IP隧道的开销要比修改MAC地址要大一些。在实际实验中,我们测得平均文件长度为10K的HTTP连接,从客户到服务器方向的报文为6个。这样,我们可以推出VS/DR或VS/TUN调度器的最大吞吐率为25,000 Connections/Second。
LVS集群的应用
Linux虚拟服务器项目(Linux Virtual Server Project)的网址是, LVS中的IPVS第一个版本源程序于1998年5月在网上发布。至今,本项目受到不少关注,LVS系统已被用于很多重负载的站点,就我们所知该系统已在美、中、英、德、澳等国的近百个站点上正式使用。
我们没有上百台机器和高速的网络来测试LVS的终极性能,所以举LVS的应用实例来说明LVS的高性能和稳定性。我们所知的一些大型LVS应用实例如下:
- 英国国家JANET Cache Service(wwwcache.ja.net)是为英国150所以上的大学提供Web Cache服务。他们用28个结点的LVS集群代替了原有现50多台相互独立的Cache服务器,用他们的话说现在速度就跟夏天一样,因为夏天是放假期间没有很多人使用网络。
- Linux的门户站点()用LVS将很多台VA Linux SMP服务器组成高性能的WEB服务,已使用将近一年。
- SourceForge(sourceforge.net)是在全球范围内为开发源码项目提供WEB、FTP、Mailing List和CVS等服务,他们也使用LVS将负载调度到十几台机器上。
- 世界上最大的PC制造商之一采用了两个LVS集群系统,一个在美洲,一个在欧洲,用于网上直销系统。
- 以RealPlayer提供音频视频服务而闻名的Real公司()使用由20台服务器组成的LVS集群,为其全球用户提供音频视频服务。在2000年3月时,整个集群系统已收到平均每秒20,000个连接的请求流。
- NetWalk()用多台服务器构造LVS系统,提供1024个虚拟服务,其中本项目的一个美国镜像站点()。
- RedHat()从其6.1发行版起已包含LVS代码,他们开发了一个LVS集群管理工具叫Piranha,用于控制LVS集群,并提供了一个图形化的配置界面。
- VA Linux()向客户提供基于LVS的服务器集群系统,并且提供相关的服务和支持。
- TurboLinux的“世界一流Linux集群产品”TurboCluster实际上是基于LVS的想法和代码的,只是他们在新闻发布和产品演示时忘了致谢 。
- 红旗Linux和中软都提供基于LVS的集群解决方案,并在2000年9月召开的Linux World China 2000上展示。
小结
本章主要讲述了IP虚拟服务器在Linux内核中的实现、关键问题的解决方法和优化处理、以及IP虚拟服务器的性能测试和应用情况。IP虚拟服务器具有以下特点:
- 三种IP负载均衡技术,在一个服务器集群中,不同的服务器可以使用不同的IP负载均衡技术。
- 可装卸连接调度模块,共有五种连接调度算法。
- 高效的Hash函数
- 高效的垃圾回收机制
- 虚拟服务的数目没有限制,每个虚拟服务有自己的服务器集。
- 支持持久的虚拟服务
- 正确的ICMP处理
- 拥有本地结点功能
- 提供系统使用的统计数据
- 针对大规模DoS攻击的三种防卫策略
通过IP虚拟服务器软件和集群管理工具可以将一组服务器组成一个高性能、高可用的网络服务。该系统具有良好的伸缩性,支持几百万个并发连接。无需对客户机和服务器作任何修改,可适用任何Internet站点。该系统已经在很多大型的站点得到很好的应用。
内核中的基于内容请求分发
前面几章讲述了在Linux虚拟服务器(Linux Virtual Server)的框架下,先在Linux内核中实现了含有三种IP负载均衡技术的IP虚拟服务器,可将一组服务器构成一个实现高可伸缩、高可用的网络服务的服务器集群。在IPVS中,使得服务器集群的结构对客户是透明的,客户访问集群提供的网络服务就像访问一台高性能、高可用的服务器一样。客户程序不受服务器集群的影响不需作任何修改。系统的伸缩性通过在服务机群中透明地加入和删除一个节点来达到,通过检测节点或服务进程故障和正确地重置系统达到高可用性。
IPVS基本上是一种高效的Layer-4交换机,它提供负载平衡的功能。当一个TCP连接的初始SYN报文到达时,IPVS就选择一台服务器,将报文转发给它。此后通过查发报文的IP和TCP报文头地址,保证此连接的后继报文被转发到相同的服务器。这样,IPVS无法检查到请求的内容再选择服务器,这就要求后端的服务器组是提供相同的服务,不管请求被送到哪一台服务器,返回结果都应该是一样的。但是在有一些应用中后端的服务器可能功能不一,有的是提供HTML文档的Web服务器,有的是提供图片的Web服务器,有的是提供CGI的Web服务器。这时,就需要基于内容请求分发 (Content-Based Request Distribution),同时基于内容请求分发可以提高后端服务器上访问的局部性。
基于内容的请求分发
根据应用层(Layer-7)的信息调度TCP负载不是很容易实现,对于所有的TCP服务,应用层信息必须等到TCP连接建立(三次握手协议)以后才能获得。这就说明连接不能用Layer-4交换机收到一个SYN报文时进行调度。来自客户的TCP连接必须在交换机上被接受,建立在客户与交换机之间 TCP连接,来获得应用的请求信息。一旦获得请求信息,分析其内容来决定哪一个后端服务器来处理,再将请求调度到该服务器。
现有两种方法实现基于内容的调度。一种是TCP网关(TCP Gateway),交换机建立一个到后端服务器的TCP连接,将客户请求通过这个连接发到服务器,服务器将响应结果通过该连接返回到交换机,交换机再将结果通过客户到交换机的连接返回给客户。另一种是TCP迁移(TCP Migration),将客户到交换机TCP连接的交换机端迁移到服务器,这样客户与服务器就可以建立直接的TCP连接,但请求报文还需要经过交换机调度到服务器,响应报文直接返回给客户。
就我们所知,Resonate实现了TCP迁移方法,他们称之为TCP连接跳动(TCP Connection Hop),他们的软件是专有的。TCP迁移需要修改交换机的TCP/IP协议栈,同时需要修改所有后端服务器的TCP/IP协议栈,才能实现将TCP连接的一端从一台机器及其迁移到另一台上。Rice大学和IBM的研究人员简要地描述了它们的TCP Handoff Protocol,在交换机和后端服务器上安装TCP Handoff协议,交换机获得客户请求后,通过TCP Handoff协议将TCP连接的交换机端转给后端服务器。但是,他们没有提供任何其他文档或研究报告描述他们的TCP Handoff协议和实现。他们提出的提高局部性调度算法LARD只考虑静态文档。虽然在交换机获得客户请求后,TCP迁移方法比较高效,但是该方法实现工作量大,要修改交换机和后端服务器的操作系统,不具有一般性。
因此,TCP网关方法被绝大部分Layer-7交换的商业产品和自由软件所使用,如ArrowPoint的CS-100和CS-800 Web交换机(Web Switch)、Zeus负载调度器、爱立信实验室的EDDIE、自由软件Apache和Squid等。TCP网关方法不需要更改服务器的操作系统,服务器只要支持TCP/IP即可,它具有很好的通用性。但是,TCP网关一般都是在用户空间实现的,其处理开销比较大,如图6.1所示。
图6.1:用户空间的TCP Gateway
从图中我们可以看出,对于任一TCP请求,客户将请求发到交换机,计算机将请求报文从内核传给用户空间的Layer-7交换程序,Layer-7交换程序根据请求选出服务器,再建一个TCP连接将请求发给服务器,返回的响应报文必须先由内核传给用户空间的Layer-7交换程序,再由Layer-7交换程序通过内核将结果返回给客户。对于一个请求报文和对应的响应报文,都需要四次内核与用户空间的切换,切换和内存复制的开销是非常高的,所以用户空间TCP Gateway的伸缩能力很有限,一般来说一个用户空间TCP Gateway只能调度三、四台服务器,当连接的速率到达每秒1000个连接时,TCP Gateway本身会成为瓶颈。所以,在ArrowPoint的CS-800 Web交换机中集成了多个TCP Gateway。
虽然Layer-7交换比Layer-4交换处理复杂,但Layer-7交换带来以下好处:
- 相同页面的请求被发送到同一台的服务器,所请求的页面很有可能会被服务器缓存,可以提高单台服务器的主存Cache使用效率。
- 一些研究[94]表明WEB访问流中存在空间的局部性。Layer-7交换可以充分利用访问的局部性,将相同类型的请求发送到同一台服务器,使得每个后端服务器收到的请求相似性好,有利于进一步提高单台服务器的主存Cache使用效率,从而在有限的硬件配置下提高系统的整体性能。
- 后端的服务器可运行不同类型的服务,如文档服务,图片服务,CGI服务和数据库服务等。
图6.2:基于内容的请求分发
图6.2举例说明了基于内容请求分发可以提高后端服务器的Cache命中率。在例子中,一个有两台后端服务器的集群来处理进来的请求序列AACBAACABC。在基于内容的请求分发中,调度器将所有请求A发到后端服务器1,将请求B和C序列发到后端服务器2。这样,有很大的可能性请求所要的对象会在后端服务器的Cache中找到。相反在轮叫请求分发中,后端服务器都收到请求A、B和C,这增加了Cache不命中的可能性。因为分散的请求序列会增大工作集,当工作集的大小大于后端服务器的主存Cache时,导致Cache不命中。
在基于内容的请求分发中,不同的请求被发送到不同的服务器,当然这有可能导致后端服务器的负载不平衡,也有可能导致更差的性能。所以,在设计基于内容请求分发的服务器集群中,我们不仅要考虑如何高效地进行基于内容的请求分发,而且要设计有效的算法来保证后端服务器间的负载平衡和提高单个服务器的Cache命中率。
内核中的基于内容请求分发KTCPVS
由于用户空间TCP Gateway的开销太大,导致其伸缩能力有限。为此,我们提出在操作系统的内核中实现Layer-7交换方法,来避免用户空间与核心空间的切换开销和内存复制的开销。在Linux操作系统的内核中,我们实现了Layer-7交换,称之为KTCPVS(Kernel TCP Virtual Server)。以下几小节将介绍KTCPVS的体系结构、实现方法、负载平衡和高可用特征等。
KTCPVS的体系结构
KTCPVS集群的体系结构如图6.3所示:它主要由两个组成部分,一是KTCPVS交换机,根据内容不同将请求发送到不同的服务器上;二是后端服务器,可运行不同的网络服务。KTCPVS交换机和后端服务器通过LAN/WAN互联。
图6.3 :KTCPVS集群的体系结构
KTCPVS交换机能进行根据内容的调度,将不同类型的请求发送到不同的后端服务器,再将结果返回给客户,后端服务器对客户是不可见的。所以,KTCPVS集群的结构对客户是透明的,客户访问集群提供的网络服务就像访问一台高性能、高可用的服务器一样,故我们也称之为虚拟服务器。客户程序不受服务器集群的影响不需作任何修改。
KTCPVS实现
在Linux 内核2.4中,我们用内核线程(Kernel thread)实现Layer-7交换服务程序,并把所有的程序封装在可装卸的KTCPVS模块。KTCPVS的主要功能模块如图6.4所示:当KTCPVS模块被加载到内核中时,KTCPVS主内核线程被激活,并在/proc文件系统和setsockopt上挂接KTCPVS的内核控制程序,用户空间的管理程序tcpvsadm通过setsockopt函数来设置KTCPVS服务器的规则,通过/proc文件系统把KTCPVS服务器的规则读出。基于内容调度的模块被做成可装卸的模块(Loadable Module),不同的网络服务可以使用不同的基于内容调度模块,如HTTP和RTSP等。另外,系统的结构灵活,用户也可以为自己不同类型的网络服务编写相应的模块。
图6.4:KTCPVS系统的主要功能模块
可以通过tcpvsadm命令使得KTCPVS主线程生成多个子线程监听在某个端口。这组子线程可以接受来自客户的请求,通过与其绑定的基于内容的调度模块获得能处理当前请求的服务器,并建立一个TCP连接到该服务器,将请求发到服务器;子线程获得来自服务器的响应结果后,再将结果返回给客户。所有的请求和响应数据处理都是在操作系统的内核中进行的,所以没有用户空间与核心空间的切换开销和内存复制的开销,其处理开销比用户空间的TCP Gateway小很多。
KTCPVS高可用性
KTCPVS集群系统的高可用性可分为二部分达到,一是服务器故障处理,二是KTCPVS调度器故障处理。
在服务器故障处理上,我们可以一种或者组合多种方法来检测服务器或者网络服务是否可用。例如,一是资源监测器每隔t个毫秒对每个服务器发ARP(Address Resolve Protocol)请求,若有服务器过了r毫秒没有响应,则说明该服务器已发生故障,资源监测器通知调度器将该服务器的所有服务进程调度从调度列表中删除。二是资源监测器定时地向每个服务进程发请求,若不能返回结果则说明该服务进程发生故障,资源监测器通知调度器将该服务进程调度从KTCPVS调度列表中删除。资源监测器能通过电子邮件或传呼机向管理员报告故障,一旦监测到服务进程恢复工作,通知调度器将其加入调度列表进行调度。通过检测节点或服务进程故障和正确地重置系统,可以将部分结点或软件故障对用户屏蔽掉,从而实现系统的高可用性。
在KTCPVS调度器故障处理上,跟IPVS调度器故障处理类似,通过心跳或者VRRP来实现KTCPVS调度器的高可用性。
KTCPVS的调度算法
KTCPVS的负载均衡调度是以TCP连接为粒度的。同一用户的不同连接可能会被调度到不同的服务器上,所以这种细粒度的调度可避免不同用户的访问差异引起服务器间的负载不平衡。
在调度算法上,我们先实现了加权最小连接调度(Weighted Least-Connection Scheduling),因为该算法都较容易实现,便于调试和测试。另外,KTCPVS交换机网络配置比Layer-4交换机的简单,KTCPVS交换机和服务器只要有网络相连,能进行TCP/IP通讯即可,安装使用比较方便,所以在整个系统规模不大(例如不超过10个结点)且结点提供的服务相同时,可以利用以上算法来调度这些服务器,KTCPVS交换机仍是不错的选择。同时,在实际的性能测试中,这些调度算法可以用作比较。
我们给出基于局部性的最小连接调度(Locality-Based Least-Connection Scheduling)和基于内容的调度(Content-based Scheduling)算法。基于局部性的最小连接调度是假设后端服务器都是相同的,在后端服务器的负载基本平衡情况下,尽可能将相同的请求分到同一台服务器,以提高后端服务器的访问局部性,从而提高后端服务器的Cache命中率。基于内容的调度是考虑后端服务器不相同时,若同一类型的请求有多个服务器可以选择时,将请求负载均衡地调度到这些服务器上。
加权最小连接调度
最小连接调度(Least-Connection Scheduling)是把新的连接请求分配到当前连接数最小的服务器。最小连接调度是一种动态调度算法,它通过服务器当前所活跃的连接数来估计服务器的负载情况。调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某台服务器,其连接数加1;当连接中止或超时,其连接数减一。
加权最小连接调度(Weighted Least-Connection Scheduling)是最小连接调度的超集,各个服务器用相应的权值表示其处理性能。服务器的缺省权值为1,系统管理员可以动态地设置服务器的权值。加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。
基于局部性的最小连接调度
在基于局部性的最小连接负载调度(Locality-Based Least-Connection Scheduling)中,我们假设任何后端服务器都可以处理任一请求。算法的目标是在后端服务器的负载平衡情况下,提高后端服务器的访问局部性,从而提高后端服务器的主存Cache命中率。
在WEB应用中,来自不同用户的请求很有可能重叠,WEB访问流中存在空间的局部性,容易获得WEB请求的URL,所以,LBLC算法主要针对WEB应用。静态WEB页面的请求格式如下:
://:/
其中,区分页面不同的变量是文件的路径,我们将之记为请求的目标。CGI动态页面的格式如下:
://:/?
我们将其中的CGI文件路径记为请求的目标。因为CGI程序一般要读一个或者多个文件来生成一个动态页面,若在服务器负载基本平衡情况下将相同的CGI请求发送到同一服务器,可以提高服务器文件系统的Cache命中率。
LBLC算法的基本流程如下:
while (true) {
get next request r;
r.target = { extract path from static/dynamic request r };
if (server[r.target] == NULL) then {
n = { least connection node };
server[r.target] = n;
} else {
n = server[r.target];
if (n.conns > n.high && a node with node.conns < node.low) ||
n.conns >= 2*n.high then {
n = { least connection node };
server[r.target] = n;
}
}
if (r is dynamic request) then
n.conns = n.conns + 2;
else
n.conns = n.conns + 1;
send r to n and return results to the client;
if (r is dynamic request) then
n.conns = n.conns - 2;
else
n.conns = n.conns - 1;
}
在算法中,我们用后端服务器的活跃连接数(n.conns)来表示它的负载,server是个关联变量,它将访问的目标和服务器对应起来。在这里,假设动态页面的处理是静态页面的2倍。开销算法的意图是将请求序列进行分割,相同的请求去同一服务器,可以提高访问的局部性;除非出现严重的负载不平衡了,我们进行调整,重新分配该请求的服务器。我们不想因为微小或者临时的负载不平衡,进行重新分配服务器,会导致Cache不命中和磁盘访问。所以,“严重的负载不平衡”是为了避免有些服务器空闲着而有服务器超载了。
我们定义每个结点有连接数目的高低阀值,当结点的连接数(node.conns)小于其连接数低阀值(node.low)时,表明该结点有空闲资源;当结点的连接数(node.conns)大于其连接数高阀值(node.high)时,表明该结点在处理请求时可能会有一定的延时。在调度中,当一个结点的连接数大于其高阀值并且有结点的连接数小于其低阀值时,重新分配,将请求发到负载较轻的结点上。还有,为了限制结点过长的响应延时,当结点的连接数大于2倍的高阀值时,将请求重新分配到负载较轻的结点,不管是否有结点的连接数小于它的低阀值。
我们在调度器上进行限制后端服务器的正在处理连接数必须小于∑node.high,这样可以避免所有结点的连接数大于它们的2倍的高阀值。这样,可以保证当有结点的连接数大于2倍的高阀值时,必有结点的连接数小于其高阀值。
正确选择结点的高低阀值是根结点的处理性能相关的。在实践中,结点的低阀值应尽可能高来避免空闲资源,否则会造成结点的低吞吐率;结点的高阀值应该一个较高的值并且结点的响应延时不大。选择结点的高低阀值是一个折衷平衡的过程,结点的高低阀值之差有一定空间,这样可以限制负载不平衡和短期负载不平衡,而不破坏访问的局部性。对于一般结点,我们选择高低阀值分别为30和60;对于性能很高的结点,可以将其阀值相应调高。
在基本的LBLC算法中,在任何时刻,任一请求目标只能由一个结点来服务。然而,有可能一个请求目标会导致一个后端服务器进入超载状态,这样比较理想的方法就是由多个服务器来服务这个文档,将请求负载均衡地分发到这些服务器上。这样,我们设计了带复制的LBLC算法,其流程如下:
while (true) {
get next request r;
r.target = { extract path from static/dynamic request r };
if (serverSet[r.target] == ∮) then {
n = { least connection node };
add n to serverSet[r.target];
} else {
n = {least connection node in serverSet[r.target]};
if (n.conns > n.high && a node with node.conns < node.low) ||
n.conns >= 2*n.high then {
n = { least connection node };
add n to serverSet[r.target];
}
if |serverSet[r.target]| > 1
&& time()-serverSet[r.target].lastMod > K then {
m = {most connection node in serverSet[r.target]};
remove m from serverSet[r.target];
}
}
if (r is dynamic request) then
n.conns = n.conns + 2;
else
n.conns = n.conns + 1;
send r to n and return results to the client;
if (r is dynamic request) then
n.conns = n.conns - 2;
else
n.conns = n.conns - 1;
if (serverSet[r.target] changed) then
serverSet[r.target].lastMod = time();
}
这个算法与原来算法的差别是调度器维护请求目标到一个能服务该目标的结点集合。请求会被分发到其目标的结点集中负载最轻的一个,调度器会检查是否发生结点集中存在负载不平衡,若是,则挑选所有结点中负载最轻的一个,将它加入该目标的结点集中,让它来服务该请求。另一方面,当请求目标的结点集有多个服务器,并且上次结点集的修改时间之差大于K秒时,将最忙的一个结点从该目标的结点集中删除。在实验中,K的缺省值为60秒。
基于内容的调度
在基于内容的调度(Content-based Scheduling)中,不同类型的请求会被送到不同的服务器,但是同一类型的请求有多个服务器可以选择,例如,CGI应用往往是CPU密集型的,需要多台CGI服务器去完成,这时需要将请求负载均衡地调度到这些服务器上。其基本算法如下:
while (true) {
get next request r;
extract path from static/dynamic request and set r.target;
if (definedServerSet[r.target] ==∮) then
n = {least connection node in defaultServerSet};
else
n = {least connection node in definedServerSet[r.target]};
send r to n and return results to the client;
}
当请求目标有定义的结点集,即该类型的请求有一些服务器能处理,从该结点集中挑选负载最轻的一个,把当前的请求发到该服务器。当请求目标没有定义好的结点集,则从缺省的结点集中选负载最轻的结点,由它来处理该请求。
在上面的算法中,只考虑了结点间的负载平衡和结点上静态分割好的局部性,没有考虑动态访问时的局部性。我们对该算法改进如下:
while (true) {
get next request r;
r.target = { extract path from static/dynamic request r };
if (definedServerSet[r.target] ==∮) then
staticServerSet = defaultServerSet;
else
staticServerSet = definedServerSet[r.target];
if (serverSet[r.target] == ∮) then {
n = { least connection node in statisServerSet};
add n to serverSet[r.target];
} else {
n = {least connection node in serverSet[r.target]};
if (n.conns > n.high && a node in staticServerSet with
node.conns < node.low) ||
n.conns >= 2*n.high then {
n = { least connection node in staticServerSet};
add n to serverSet[r.target];
}
if |serverSet[r.target]| >1
&& time()-serverSet[r.target].lastMod > K then {
m = {most connection node in serverSet[r.target]};
remove m from serverSet[r.target];
}
}
if (r is dynamic request) then
n.conns = n.conns + 2;
else
n.conns = n.conns + 1;
send r to n and return results to the client;
if (r is dynamic request) then
n.conns = n.conns - 2;
else
n.conns = n.conns - 1;
if serverSet[r.target] changed then
serverSet[r.target].lastMod = time();
}
TCPHA的设计与实现