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(反向服务器), upstreamserver(上游服务器)
CIP: Client IP (客户端IP) VIP: Virtual Server IP (lvs前端的ip,它是负责往里面转的,同时也是用户的目标地址) DIP: Director IP (跟realserver交互) 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 | :WeightedRR,加权轮询;针对不同的服务器本来负载能力就不同,负载能力强的权重大,负载能力小的权重小 | SH | SourceHashing,源地址哈希,(在指定的时间内)将来自于同一个IP地址的请求始终发往第一次挑中的RS,从而实现了会话绑定; 会话不会丢失,但是就怕服务器宕机;还有就是现在上网都是通过nat实现内网上网,很多用户访问一个网页会形成一个ip,这样大量的请求就指向了同一台服务器,绑定力度太粗糙(有人会说绑定cookie,问题就在这,ipvs基于iso第四层网络传输层,不属于应用层,所有无法实现 | DH | DestinationHashing,目标地址哈希,将发往同一个目标地址的请求,始终转发至第一次挑中的RS;不管是谁访问的,只要访问的资源为同一个资源,都发往同一个server,常用于正向代理当中 |
动态方法:主要根据每RS当前的负载状态进行调度,注重结果公平; 负载计算方式: Overhead = activeconns*256+inactiveconns 活动数*256+非活动数 LC | least connections,最少连接,谁的小就让请求谁 (如果一开始一样就自上而下以此调度) Overhead= activeconns*256+inactiveconns | WLC | WeightedLC, 加权最少连接,默认算法 Overhead = (activeconns*256+inactiveconns)/weight ;一般权重大的,值比较小,因此会先会被调度;会有一个问题:当响应比较少的时候,最好是让权重比较大的相应,但是这个算法不太好控制 | SED | ShortestExpection Delay,最短期望延迟;让系统最好的服务器来响应 Overhead=(activeconns+1)*256/weight 这样权重越大,所得的值越小,因此越先被调度,但是又出了新的问题:权重大的要先响应,而权重小的先不响应,造成了先没事儿干的情况 | NQ | Never Queue 永不排队,不让一个服务器有队列,而另一个服务器没有请求等情况发生,就是先一人来一个,之后按照SED的算法继续最短期望延迟算法进行后续调度,这个是上一个改进版 | LBLC | Locality-BasedLeast connections,基于本地的最少连接;动态的DH算法; 主要节约的问题吧DH换成动态的,能让同一个网页再访问量比较大的时候,让新的同一网页的请求调度到其他服务器上 | LBLCR | LBLCwith 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) [iyunv@haizei ~]# yum install mariadb-server [iyunv@haizei ~]# vim /etc/my.cnf [mysqld] socket=/var/lib/mysql/mysql.sock datadir = /data/mariadb innodb_file_per_table = ON skip_name_resolve = ON [iyunv@haizei ~]# mkdir /data/mariadb [iyunv@haizei /]# chown mysql.mysql /data/mariadb [iyunv@haizei ~]# 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) [iyunv@localhost wordpress]# cp wp-config-sample.phpwp-config.php [iyunv@localhost wordpress]# vim wp-config.php define('DB_NAME', 'wordpress'); define('DB_USER', 'wpuser'); define('DB_PASSWORD', 'wppasswd'); define('DB_HOST', '192.168.1.123');
创建集群 [iyunv@localhost ~]# ipvsadm -A -t 172.16.1.7:80 -s rr [iyunv@localhost ~]# ipvsadm -a -t 172.16.1.7:80 -r192.168.1.20:80 -m -w 2 [iyunv@localhost ~]# ipvsadm -a -t 172.16.1.7:80 -r192.168.1.30:80 -m -w 1 [iyunv@localhost ~]# ipvsadm -L IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags ->RemoteAddress:Port ForwardWeight 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 开启路由转发: [iyunv@localhost ~]# echo 1 > /proc/sys/net/ipv4/ip_forward
在浏览器输入172.16.1.7/wordpress 即可进入安装界面
之后点击浏览网页的各个资源,并做先关测试 [iyunv@localhost ~]# 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 [iyunv@localhost ~]# 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 从上面的数据我们可以看出,这个服务器均有响应, 好了我们清空一下计数,准备一下,保持会话,会有什么变化 [iyunv@localhost ~]# ipvsadm -Z 修改为保持会话 [iyunv@localhost ~]# ipvsadm -E -t 172.16.1.7:80 -s sh 查看状态: [iyunv@localhost ~]# 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 [iyunv@localhost ~]# 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,轮训,不管权重,同时清空计数 [iyunv@localhost ~]# ipvsadm -Z [iyunv@localhost ~]# ipvsadm -E -t 172.16.1.7:80 -s rr
之后我们去设置php的的会话保持,修改php.ini配置文件 这里在(192.168.1.20 和192.168.1.30的两台php服务器上设置) [iyunv@luffy ~]# 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(会话)。 [iyunv@localhost ~]# 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 [iyunv@localhost ~]# 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的轮训没有启动作用 一下为停隔了一段时间,继续测试的 [iyunv@localhost ~]# 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(这里用一个网卡即可,别名) [iyunv@localhost 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 广播只能广播给自己 [iyunv@localhost network-scripts]# systemctl restart network [iyunv@localhost network-scripts]# ip addr show eno16777736 inet 172.16.1.7/16brd 172.16.255.255 scope global eno16777736 inet 172.16.1.17/32brd 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设置限制响应级别和通告 [iyunv@zhi ~]# echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore [iyunv@zhi ~]# echo 1 >/proc/sys/net/ipv4/conf/lo/arp_ignore [iyunv@zhi ~]# echo 2 >/proc/sys/net/ipv4/conf/lo/arp_announce [iyunv@zhi ~]# echo 2 >/proc/sys/net/ipv4/conf/all/arp_announce 在real server 上面配置VIP [iyunv@zhi ~]# ifconfig lo:0 172.16.1.7netmask 255.255.255.255 broadcast 172.16.1.7 up
1.1设置限制响应级别和通告(其实只设置all就可以了,但是为了保险起见,lo或者eno1677736选一个设置) [iyunv@luffy ~]# echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore [iyunv@luffy ~]# echo 1 >/proc/sys/net/ipv4/conf/lo/arp_ignore [iyunv@luffy ~]# echo 2 >/proc/sys/net/ipv4/conf/lo/arp_announce [iyunv@luffy ~]# echo 2 >/proc/sys/net/ipv4/conf/all/arp_announce 在real server 上面配置VIP (这个上面没有ifconfig,于是用了ip,跟上面起到的作用一样) [iyunv@luffy ~]# ip addr add 172.16.1.7/32 broadcast 172.16.1.7 dev lo label lo:0
在lvs服务器设置集群: [iyunv@localhost ~]# ipvsadm -A -t 172.16.1.7:80 -s rr [iyunv@localhost ~]# ipvsadm -a -t 172.16.1.7:80 -r172.16.1.1:80 -g -w 1 [iyunv@localhost ~]# ipvsadm -a -t 172.16.1.7:80 -r172.16.1.5:80 -g -w 2 [iyunv@localhost ~]# ipvsadm -Ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags ->RemoteAddress:Port ForwardWeight 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的调度, 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直接响应给客户端的的报文,应该经由本地回环接口(不做也可以) [iyunv@luffy www]# ip route add172.16.1.7 dev lo:0 [iyunv@zhi www]# route add-host 172.16.1.7 dev lo:0 以上两条命令达到的效果是相同的,只不过使用的命令不同
|