lvs是由章文嵩开发出来的,以开源软件的方式,提供负载均衡,其工作方式与iptables非常相似。当用户请求的数据到达本机,经过PREROUTING,来到INPUT表,ipvs会查看所有报文,如果发现请求的是已经定义好的集群服务的规则,不会让数据报文进入用户空间,而是强行修改报文,把数据经过FORWARD转发到后端的real server,从而完成调度。同时的lvs的负载能力也确实是非常高,据说可以达到400WPV的负载能力,且集群配置较为简单
lvs和iptables有很多相似的地方:
iptables:
iptables: 用户空间规则管理工具;
netfilter:内核空间让规则生效的组件;一共有三种流向:
PREROUTING --> INPUT 到本机内部的
PREROUTING --> FORWARD
--> POSTROUTING 经由本机转发
OUTPUT --> POSTROUTING 本机内部转发出去的
DNAT:PREROUTING;将目标主机转换为后端的某一主机既要在到达本机路由器前进行跳转;但是ipvs不是这样工作的,
ipvs确实类似与netfilter,但是依附在在netfilter上的INPUT链上面的组件,而不是PREROUTING
lvs管理工具
ipvsadm/ipvs 组件
ipvsadm:用户的空间的命令行工具,规则管理器,用于管理集群服务及RealServer;
ipvs:工作于内核空间的netfilter的INPUT钩子之上的框架,可接收来ipvsadm的管理命令;
ipvs支持的服务:支持基于TCP、UDP、SCTP、AH、ESP、AH_ESP等 协议进行调度;
注:lvs的ipvs通常和ipbtales的filter不能一起工作,这里主要是值iptbles的INPUT链,因为用的是一个链,很有可能iptables把lvs的规则就给阻断了,因此用lvs最好在防火墙的INPUT就不要再做其他设置了后者关闭防火墙,因为现在企业大多数有硬件防火墙,这里的风险还是比较小的。
lvs集群的常用术语:
vs: virutal server, 也叫Director(调度器), Dispatcher(分发器),
Balancer(均衡器)
rs: real server, 也叫backend server(反向服务器), upstream
server(上游服务器)
CIP: Client IP (客户端IP)
VIP: Virtual Server IP (lvs前端的ip,它是负责往里面转的,同时也是用户的目标地址)
DIP: Director IP (跟real
server交互)
RIP: Real Server IP
整个用户的请求的流程是:
CIP <--> VIP <--> DIP <--> RIP
lvs集群类型(四种):
lvs-nat:
多目标IP的DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发;
要求:
(1) RIP和DIP必须在同一个IP网络,且应该使用私网地址;RS的网关要指向DIP;
(2)
请求报文和响应报文都必须经由Director转发;Director易于成为系统瓶颈;(一个directory可带5-10台RS)
(3) 支持端口映射;VIP上的端口和RIP上的端口不必非得是同一个;
(4) vs必须是Linux系统,rs可以是任意系统;
lvs-dr:(默认模型)
Direct Routing:直接路由;
通过为请求报文重新封装一个MAC首部进行转,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源IP/PORT,以及目标IP/PORT均保持原状;
(1) 确保前端路由器将目标IP为VIP的请求报文发往Director:(有三种解决方法)
(a) 在前端路由器绑定; 此方法不太可取
(b) 在RS使用arptables;
(c) 在RS上修改内核参数限制通告及应答级别;
arp_announce 通告
arp_ignore 应答响应
(2) RS的RIP可以私网地址,也可是公网地址;RIP与DIP在同一个IP网络;
(3) RS跟Director要在同一个物理网络,以实现基于MAC地址的转发;
(4) 请求报文要经由Director,但响应不能经由Director;
(5) 不支持端口映射;
lvs-tun:tunnel,隧道; (为了解决lvs和real server 必须在同一网络,不能异地部署问题而出现)
注:此功能容易出现大数据帧,MTU为1500,超过了就要切片,因此此功能很少用
转发方式:不修改请求报文的IP首部(源IP为CIP,目标IP为VIP),而是在IP报文之外再封装一个IP首部(源IP是DIP,目标IP是RIP),将报文发往挑选出的目标RS;
要求:
(1) DIP, VIP, RIP应该都是公网地址;
(2) RS网关必须不能指向DIP;
(3) 请求报文要经由Director,但响应不能经由Director,而是直接发往CIP;
(4) 不支持端口映射;
(5) RS必须支持隧道功能;
注:无论是ipvs-dr和ipvs-tun中的DIP、RIP和VIP不在同一网关没有任何问题;因为对外响应的报文只管目标地址,不管原地址
lvs-fullnat:
通过同时修改请求报文的源IP地址和目标IP地址进行转发;
cip -源修改成-> dip 源
vip -目标修改成-> rip 目标
(1)VIP是公网地址,RIP和DIP是私网地址,且通常不在同一IP网络;(与nat的不同点)
(2) RS收到的请求报文源地址为DIP,应响应给DIP即可;
(3) 请求和响应报文都经由Director;
(4) 支持端口映射;
fullnat注意:默认不支持;需要定制版的内核
ipvs scheduler:调度算法
根据其调度时是否考虑各RS当前的负载状态,可分为静态方法和动态方法;(10种)
静态方法:仅根据算法本身进行调度,注重起点公平;
RR |
:roundrobin,轮询; |
WRR |
:Weighted RR,加权轮询;针对不同的服务器本来负载能力就不同,负载能力强的权重大,负载能力小的权重小 |
SH |
Source Hashing,源地址哈希,(在指定的时间内)将来自于同一个IP地址的请求始终发往第一次挑中的RS,从而实现了会话绑定; 会话不会丢失,但是就怕服务器宕机;还有就是现在上网都是通过nat实现内网上网,很多用户访问一个网页会形成一个ip,这样大量的请求就指向了同一台服务器,绑定力度太粗糙(有人会说绑定cookie,问题就在这,ipvs基于iso第四层网络传输层,不属于应用层,所有无法实现 |
DH | Destination Hashing,目标地址哈希,将发往同一个目标地址的请求,始终转发至第一次挑中的RS;不管是谁访问的,只要访问的资源为同一个资源,都发往同一个server,常用于正向代理当中 |
动态方法:主要根据每RS当前的负载状态进行调度,注重结果公平;
负载计算方式:
Overhead = activeconns*256+inactiveconns 活动数*256+非活动数
LC |
least connections,最少连接,谁的小就让请求谁 (如果一开始一样就自上而下以此调度) Overhead |
WLC |
Weighted LC, 加权最少连接,默认算法 Overhead = (activeconns*256+inactiveconns)/weight ;一般权重大的,值比较小,因此会先会被调度;会有一个问题:当响应比较少的时候,最好是让权重比较大的相应,但是这个算法不太好控制 |
SED |
Shortest Expection Delay,最短期望延迟;让系统最好的服务器来响应 Overhead=(activeconns+1)*256/weight 这样权重越大,所得的值越小,因此越先被调度,但是又出了新的问题:权重大的要先响应,而权重小的先不响应,造成了先没事儿干的情况 |
NQ | Never Queue 永不排队,不让一个服务器有队列,而另一个服务器没有请求等情况发生,就是先一人来一个,之后按照SED的算法继续最短期望延迟算法进行后续调度,这个是上一个改进版 |
LBLC |
Locality-Based Least connections,基于本地的最少连接;动态的DH算法; 主要节约的问题吧DH换成动态的,能让同一个网页再访问量比较大的时候,让新的同一网页的请求调度到其他服务器上 |
LBLCR | LBLC with Replication,带复制的基于本地的最少连接;如果服务器负载过大,把访问同一目标地址的请求分发的别的服务器,并把缓存也复制过去 |
搭建lvs-nat模式
搭建lvs-nat模式集群,后端web服务器两台,都跑着php+nginx,后端mysql作为数据共享,搭建wordprss
负载均衡php应用,测试保持会话有无的区别
首先根据图中的拓扑图,将各服务器的ip设定完毕,内网的网关执向设定好;
两台web服务器安装上yum install -y nginx php-fpm php-mysql,并置好相关的网页根目录(/data/www)
把nginx和php的连接配置设置好,并测试成功(详见lnmp的相关配置)
准备好数据库(192.168.1.123)
[[email protected] ~]# yum install mariadb-server
[[email protected] ~]# vim /etc/my.cnf
[mysqld]
socket=/var/lib/mysql/mysql.sock
datadir = /data/mariadb
innodb_file_per_table = ON
skip_name_resolve = ON
[[email protected] ~]# mkdir /data/mariadb
[[email protected] /]# chown mysql.mysql /data/mariadb
[[email protected] ~]# systemctl start mariadb
MariaDB [(none)]> grant all on *.* to ‘zou‘@‘192.168.1.%‘
identified by ‘123.comer‘;
MariaDB [(none)]> create database wordpress;
MariaDB [(none)]> grant all on wordpress.* to
‘wpuser‘@‘192.168.1.%‘ identified by ‘wppasswd‘;
MariaDB [(none)]> flush privileges;
准备好wordpress网页 (192.168.1.20 和 30)
[[email protected] wordpress]# cp wp-config-sample.php
wp-config.php
[[email protected] wordpress]# vim wp-config.php
define(‘DB_NAME‘, ‘wordpress‘);
define(‘DB_USER‘, ‘wpuser‘);
define(‘DB_PASSWORD‘, ‘wppasswd‘);
define(‘DB_HOST‘, ‘192.168.1.123‘);
创建集群
[[email protected] ~]# ipvsadm -A -t 172.16.1.7:80 -s rr
[[email protected] ~]# ipvsadm -a -t 172.16.1.7:80 -r
192.168.1.20:80 -m -w 2
[[email protected] ~]# ipvsadm -a -t 172.16.1.7:80 -r
192.168.1.30:80 -m -w 1
[[email protected] ~]# ipvsadm -L
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
->
RemoteAddress:Port Forward
Weight ActiveConn InActConn
TCP 172.16.1.7:http rr
-> 192.168.1.20:http Masq 2
0 0
->
192.168.1.30:http Masq 1
0 0
开启路由转发:
[[email protected] ~]# echo 1 > /proc/sys/net/ipv4/ip_forward
在浏览器输入172.16.1.7/wordpress 即可进入安装界面
之后点击浏览网页的各个资源,并做先关测试
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
24 757 194
50738 1634852
->
192.168.1.20:http
12 40 8
2710 1445
->
192.168.1.30:http
12 717 186
48028 1633407
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
31 1389 405
131549 3104513
->
192.168.1.20:http
15 47 8
3074 1445
->
192.168.1.30:http
16 1342 397
128475 3103068
从上面的数据我们可以看出,这个服务器均有响应,
好了我们清空一下计数,准备一下,保持会话,会有什么变化
[[email protected] ~]# ipvsadm -Z
修改为保持会话
[[email protected] ~]# ipvsadm -E -t 172.16.1.7:80 -s sh
查看状态:
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
17 1560 542
117187 3717381
->
192.168.1.20:http
5 7 0
420 0
->
192.168.1.30:http
12 1553 542
116767 3717381
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
28 2172 845
166410 5117125
->
192.168.1.20:http
7 9 0
540 0
->
192.168.1.30:http
21 2163 845
165870 5117125
在这里的数据可以看到ipv的会话绑定在让相同的ip的来源,请求大多都跑向了192.168.1.30
下面我们继续吧调度算法调回rr,轮训,不管权重,同时清空计数
[[email protected] ~]# ipvsadm -Z
[[email protected] ~]# ipvsadm -E -t 172.16.1.7:80 -s rr
之后我们去设置php的的会话保持,修改php.ini配置文件
这里在(192.168.1.20 和192.168.1.30的两台php服务器上设置)
[[email protected] ~]# vim /etc/php.ini
[Session]
session.save_handler = files #session的存储方式
session.use_cookies= 1 #使用cookies在客户端保存会话
session.use_only_cookies = 1 #去保护URL中传送session id的用户
session.name = PHPSESSID #session名称(默认PHPSESSID)
session.auto_start = 0 #不启用请求自动初始化session
session.cookie_lifetime = 0 #cookie存活时间(0为直至浏览器重启,单位秒)
session.cookie_path = / #cookie的有效路径
session.cookie_domain = #cookie的有效域名
session.cookie_httponly = #httponly标记增加到cookie上(脚本语言无法抓取)
session.serialize_handler = php #PHP标准序列化
session.gc_probability =1
session.gc_divisor =1000 #建议设置1000-5000
#概率=session.gc_probability/session.gc_divisor(1/1000)
#页面访问越频繁概率越小
session.gc_maxlifetime =1440 #过期时间(默认24分钟,单位秒)
session.bug_compat_42 = off #全局初始化session变量
session.bug_compat_warn = off
session.referer_check = #防止带有ID的外部URL
session.entopy_length = 0 #读取的字节
session.cache_limiter = {nocache,private,pblic} #HTTP缓冲类型
session.cache_expire = 180 #文档过期时间(分钟)
session.use_trans_sid = 1 #trans_sid支持(默认0)
session.hash_function = 0 #hash方法{0:md5(128 bits),1:SHA-1(160 bits)}
session.hash_bits_per_character = 5 #当转换二进制hash数据奥可读形式是,每个字符保留位数
session.save_path = "/tmp" #session id存放路径
~】# nginx -s reload
~】# systemctl reload php-fpm
默认的,php会将session保存在/tmp目录下,文件名为这个样子:sess_01aab840166fd1dc253e3b4a3f0b8381。每一个文件对应了一个session(会话)。
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
1 96 85
34881 18312
->
192.168.1.20:http
0 2 2
80 80
->
192.168.1.30:http
1 94 83
34801 18232
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
1 148 134
55902 29343
->
192.168.1.20:http
0 2 2
80 80
->
192.168.1.30:http
1 146 132
55822 29263
这两个数据是在开启了php缓存的状态下测试的,这个时候是轮训状态,请求的是index.php网页,两个web上main的网页内容不同,在浏览器上测试的时候,绝大多数网页是在一个上面停留的,在连接上面显示的只有一个,但是在测试的时候刷新了很多次浏览器,我们可以在INpkts可以看出数据是不断上涨的,这就是php的session的作用;造成了lvs的轮训没有启动作用
一下为停隔了一段时间,继续测试的
[[email protected] ~]# ipvsadm -L --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts
OutPkts InBytes OutBytes
->
RemoteAddress:Port
TCP
172.16.1.7:http
4 195 167
63912 62631
->
192.168.1.20:http
2 21 15
2494 26119
->
192.168.1.30:http
2 174 152
61418 36512
可以看出两个网页有循环,但是主要在一个网页上停留;而这个php上面的session已经开启
配置lvs-dr:direct routing
dr模型中,各主机上均需要配置VIP;解决地址冲突的方式有三种:
(1) 在前端网关做静态绑定;
(2) 在各RS使用arptables;
(3) 在各RS修改内核参数,来限制arp响应和通告的级别;
限制响应级别:arp_ignore
0:使用本地任意接口上配置的地址进行响应;
1:仅在请求的目标IP配置在本地主机的接收报文接口上时,才给予响应;
2-8:(不怎么使用,记住0和1即可)
限制通告级别:arp_announce
0:默认,把本机所有接口的信息向每个接口上的网络通告;
1:尽量改名向非本网络通告;(意思是有时可以通告)
2:必须避免向非本网络通告;
ipvs主机上面(设置好VIP为172.16.1.7,网关指向172.16.0.1),之后还要设置DIP(这里用一个网卡即可,别名)
[[email protected] network-scripts]# vim ifcfg-eno16777736
TYPE=Ethernet
BOOTPROTO=none
NAME=eno16777736
DEVICE=eno16777736
ONBOOT=yes
IPADDR=172.16.1.17 DIP地址,
PREFIX=16
GATEWAY=172.16.0.1
IPADDR0=172.16.1.7 设置该网卡上的面的第二个地址,
PREFIX0=32 掩码为32位,全为255,因为这里vip不用于对外通信,因此这样就好
BROADCAST=172.16.1.7 广播只能广播给自己
[[email protected] network-scripts]# systemctl restart network
[[email protected] network-scripts]# ip addr show eno16777736
inet 172.16.1.7/16
brd 172.16.255.255 scope global eno16777736
inet 172.16.1.17/32
brd 172.16.1.17 scope global eno16777736
其他两个Real server 上面的网络设置:
ip设置为172.16.1.1,网关执向172.16.0.1
ip设置为172.16.1.5,网关指向172.16.0.1
1.5设置限制响应级别和通告
[[email protected] ~]# echo 1 >
/proc/sys/net/ipv4/conf/all/arp_ignore
[[email protected] ~]# echo 1 >
/proc/sys/net/ipv4/conf/lo/arp_ignore
[[email protected] ~]# echo 2 >
/proc/sys/net/ipv4/conf/lo/arp_announce
[[email protected] ~]# echo 2 >
/proc/sys/net/ipv4/conf/all/arp_announce
在real server 上面配置VIP
[[email protected] ~]# ifconfig lo:0 172.16.1.7netmask 255.255.255.255 broadcast 172.16.1.7 up
1.1设置限制响应级别和通告(其实只设置all就可以了,但是为了保险起见,lo或者eno1677736选一个设置)
[[email protected] ~]# echo 1 >
/proc/sys/net/ipv4/conf/all/arp_ignore
[[email protected] ~]# echo 1 >
/proc/sys/net/ipv4/conf/lo/arp_ignore
[[email protected] ~]# echo 2 >
/proc/sys/net/ipv4/conf/lo/arp_announce
[[email protected] ~]# echo 2 >
/proc/sys/net/ipv4/conf/all/arp_announce
在real server 上面配置VIP (这个上面没有ifconfig,于是用了ip,跟上面起到的作用一样)
[[email protected] ~]# ip addr add 172.16.1.7/32 broadcast 172.16.1.7 dev lo label lo:0
在lvs服务器设置集群:
[[email protected] ~]# ipvsadm -A -t 172.16.1.7:80 -s rr
[[email protected] ~]# ipvsadm -a -t 172.16.1.7:80 -r
172.16.1.1:80 -g -w 1
[[email protected] ~]# ipvsadm -a -t 172.16.1.7:80 -r
172.16.1.5:80 -g -w 2
[[email protected] ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
->
RemoteAddress:Port Forward
Weight ActiveConn InActConn
TCP 172.16.1.7:80 rr
->
172.16.1.1:80 Route 1
0 0
->
172.16.1.5:80 Route 2
0 0
在其他主机客户端测试lvs的调度,
[[email protected] ~]# for i in {1..10}; do curl http://172.16.1.7/index.html; done
this is web from 172.16.1.5
this is web from 172.16.1.1
this is web from 172.16.1.5
this is web from 172.16.1.1
this is web from 172.16.1.5
.....
限制Real sever 每一个经由RS直接响应给客户端的的报文,应该经由本地回环接口(不做也可以)
[[email protected] www]# ip route add
172.16.1.7 dev lo:0
[[email protected] www]# route add
-host 172.16.1.7 dev lo:0
以上两条命令达到的效果是相同的,只不过使用的命令不同