本篇还是为了随接之前的四篇内容,本篇涉及到的持久连接和简单健康检查都不会跳出LVS软件本身,在了解了LVS本身如何实现这些问题后,后面再说和其他软件结合的方案 。

一、持久连接

在LVS中,持久连接是为了用来保证当来自同一个用户的请求时能够定位到同一台服务器。lvs的持久连接有三种模式,无论哪种模式都会用到ipvsadm里的-p参数,配置起来也都不复杂 。不过在配置之前我们先要了解下理论性的东西。

1、为什么用到持久连接?

在Web服务通信中,当用户在一个网站浏览了A网页并跳转到B网页,此时服务器就认为B网页是一个新的用户请求,你之前的登陆的信息就都丢失了。为了记录用户的会话信息,我们的开发者就在客户端/服务器端软件提供了cookie/session机制,当你访问网站时,服务器端建立一个session会话区,并建立一个cookie与这个session绑定,将信息发送给你的浏览器。这样,只要你的cookie存在,服务器端的session存在,那么当你打开新页面的时候,服务器依然会认识你!

在做了负载均衡的时候,上面的机制就出现了问题。假设场景为:某电商网站为了实现更多用户的访问,提供了A、B两台服务器,并在前面做了LVS负载均衡。于是某用户打开了该购物网站,选中了一件衣服,并加入了购物车(此时背后的操作是:LVS负载均衡器接受了用户请求,并将其分发到了选中的服务器,并将用户添加了一件衣服记录到这个会话的session中)。这时当用户打开了第二个网页,又选中了一件帽子并加入购物车(此时背后的操作是:LVS负载均衡器接受了用户请求,进行计算,将其发送到选中的服务器上,该服务器将用户添加了一件帽子记录到session中)。

由于LVS是一个四层负载均衡器,仅能根据IP:Port对数据报文进行分发,不能确保将同一用户根据session发往同一个服务器,也就是用户第一次被分配到了A服务器,而第二次可能分配到了B服务器,但是B服务器并没有A服务器用户的session记录,直接导致这个例子里的用户发现自己的购物车没有了之前的衣服,而仅有帽子。这是不可接受的。

为避免上面的问题,一般站点会有两种方法解决该问题:

1、将来自于同一个用户的请求发往同一个服务器
2、将session信息在服务器集群内共享,每个服务器都保存整个集群的session信息
3、建立一个session存储池,所有session信息都保存到存储池中

当然通过session共享解决是比较完美的,但实现起来相对复杂:一需要额外增加服务器设备;二需要代码改动,在用户操作前,需要先获取该用户的session信息。而第一种方法是最简单的。

2、hash算法与持久连接

LVS的八种轮询算法中有(Source Hashing)源地址hash,它和持久连接的作用都是”将来自同一个IP的请求都转发到同一个Server”,从而保证了session会话定位的问题。两者的不同是:

Source Hashing算法在内核中会自动维护一个哈希表,此哈希表中用每一个请求的源IP地址经过哈希计算得出的值作为键,把请求所到达的RS的地址作为值。在后面的请求中,每一个请求会先经过此哈希表,如果请求在此哈希表中有键值,那么直接定向至特定RS,如没有,则会新生成一个键值,以便后续请求的定向。但是此种方法在时间的记录上比较模糊(依据TCP的连接时长计算)。而且通过hash算法无法公平均担后端realserver的请求,即不能与rr等算法同时使用。

持久连接:此种方法实现了无论使用哪一种调度方法,持久连接功能都能保证在指定时间范围之内,来自于同一个IP的请求将始终被定向至同一个RS,还可以把多种服务绑定后统一进行调度。在director内有一个LVS持久连接模板,模板中记录了每一个请求的来源、调度至的RS、维护时长等等,所以,在新的请求进入时,首先在此模板中检查是否有记录(有内置的时间限制,比如限制是300秒,当在到达300秒时依然有用户访问,那么持久连接模板就会将时间增加两分钟,再计数,依次类推,每次只延长2分钟),如果该记录未超时,则使用该记录所指向的RS,如果是超时记录或者是新请求,则会根据调度算法先调度至特定RS,再将调度的记录添加至此表中。这并不与SH算法冲突,lvs持久连接会在新请求达到时,检查后端RS的负载状况,这就是比较精细的调度和会话保持方法。

二、持久连接的配置

LVS的持久连接有三种方式,PCC、PPC、PNMPP 。三者都用到ipvsadm里的-p参数(persistent)。

(1)PCC(Persistent Port Connections):每客户端持久;将来自于同一个客户端的所有请求统统定向至此前选定的RS;也就是只要IP相同,分配的服务器始终相同。其配置方法如下:

1ipvsadm -A -t 172.16.100.1:0 -s wrr -p 3600
2ipvsadm -a -t 172.16.100.1:0 -r 172.16.100.10 -g -w 2
3ipvsadm -a -t 172.16.100.1:0 -r 172.16.100.11 -g -w 2

上面端口可以不写端口号(或者端口号为0)。

(2)PPC(Persistent Client Connections):每端口持久;将来自于同一个客户端对同一个服务(端口)的请求,始终定向至此前选定的RS。例如:来自同一个IP的用户第一次访问集群的80端口分配到A服务器,25号端口分配到B服务器。当之后这个用户继续访问80端口仍然分配到A服务器,25号端口仍然分配到B服务器。其配置如下:

1ipvsadm -A -t 172.16.100.1:80 -s wrr -p 3600
2ipvsadm -A -t 172.16.100.1:25 -s wrr -p 3600
3ipvsadm -a -t 172.16.100.1:80 -r 172.16.100.10 -g -w 2
4ipvsadm -a -t 172.16.100.1:80 -r 172.16.100.11 -g -w 2
5ipvsadm -a -t 172.16.100.1:25 -r 172.16.100.11 -g -w 2

上面命令的意思是:添加一个集群服务为172.16.100.1:80,使用的调度算法为wrr,持久连接的保持时间是3600秒。当超过3600秒都没有请求时,则清空LVS的持久连接模板。这里我们可以在上面的策略后面加其他服务,指向其他realserver服务器 。

(3)PNMPP(Persistent Netfilter Marked Packet Persistence):持久防火墙标记连接;将来自于同一客户端对指定服务(端口)的请求,始终定向至此选定的RS;不过它可以将两个毫不相干的端口定义为一个集群服务,例如:合并http的80端口和https的443端口定义为同一个集群服务,当用户第一次访问80端口分配到A服务器,第二次访问443端口时仍然分配到A服务器。配置示例如下:

 1#清除ipvsadm规则表
 2ipvsadm -C
 3#将http与https请求标记为8,将标记为8的请求后给后面两台主机
 4iptables -t mangle -A PREROUTING -d 172.16.100.1 -i eth0 -p tcp --dport 80 -j MARK --set-mark 8
 5iptables -t mangle -A PREROUTING -d 172.16.100.1 -i eth0 -p tcp --dport 443 -j MARK --set-mark 8
 6ipvsadm -A -f 8 -s rr -p 600
 7ipvsadm -a -f 8 -r 172.16.100.10 -g -w 2
 8ipvsadm -a -f 8 -r 172.16.100.11 -g -w 1
 9#ftp的连接在防火墙里标记为10,将标记为10的请求给后面两台主机
10iptables -t mangle -A PREROUTING -d 172.16.100.1 -p tcp --dport 21 -j  MARK --set-mark 10
11iptables -t mangle -A PREROUTING -d 172.16.100.1 -p tcp --dport 10000:20000 -j  MARK --set-mark 10
12ipvsadm -A -f 10 -s rr -p 1800
13ipvsadm -a -f 10 -r 172.16.100.20 -g
14ipvsadm -a -f 10 -r 172.16.100.21 -g

三、健康检测

在LVS模型中,director不负责检查RS的健康状况,这就使得当有的RS出故障了,director还会将服务请求派发至此服务器 。为实现像F5这样专业的LB设备的功能,LVS可以与ldirectord工具结合,自动屏蔽后端异常的服务器。也可以通过简单的脚本来实现后端健康检查,当curl获取后端某服务器的状态发现不对时,自动通过ipvsadm命令将该服务器删除。正常时同样通过ipvsadm命令加入。由于本篇暂时还未跳出LVS工具本身,所以这里通过脚本实现后端健康检查。脚本内容如下:

 1#!/bin/bash
 2VIP=192.168.1.200
 3CPORT=80
 4BACKUP=127.0.0.1
 5STATUS=("1""1")
 6RS=("192.168.1.10" "192.168.1.20")
 7RW=("2" "1")
 8RPORT=80
 9TYPE=g
10while:; do
11    letCOUNT=0
12    add() {
13       ipvsadm -a -t $VIP:$CPORT -r $1:$RPORT -$TYPE -w $2
14       [ $? -eq0 ] && return0 || return1
15    }
16    del() {
17       ipvsadm -d -t $VIP:$CPORT -r $1:$RPORT
18       [ $? -eq0 ] && return0 || return1
19    }
20    for I in${RS[*]}; do
21       if curl --connect-timeout 1 http://$I &> /dev/null; then
22          if[ ${STATUS[$COUNT]} -eq 0 ]; then
23             add $I ${RW[$COUNT]}
24         [ $? -eq 0 ] && STATUS[$COUNT]=1
25          fi
26       else
27          if[ ${STATUS[$COUNT]} -eq 1 ]; then
28             del $I
29         [ $? -eq0 ] && STATUS[COUNT]=0
30          fi
31       fi
32       letCOUNT++
33    done
34    sleep 5
35done