长文预警,先看目录
不要因为技术而技术!技术服务于业务,必须是当前的技术不能满足业务的发展,才能产生新的技术来解决业务问题。为什么会产生负载平衡?这是因为业务需求。
中国人口14亿,移动网络用户7亿,固网用户4亿,保守估计独立用户为28.四亿,他们有消费能力。
假设你有一个idea,我一无是处,只有钱。当你的天使投资者给你第一笔投资时,你会做什么?毫无疑问,营销是谁?营销这2.假设2%的人(480万人)看到你的广告,20%的人(96W)有兴趣下载你的APP,光APP当然,96广告可以赚很多钱W人们使用你的服务,这必然会导致高并发,这表明高并发是真的!
高并发产生的日志有一个字段表示来源网站(例如,用户是通过百度点还是微博点),可以分析渠道流量,然后通过与订单表相关联,可以分析渠道流量转化率。这有什么用?这直接表明你下次应该去哪个渠道营销!这表明高并发性是有益的!
负载平衡是由高并发产生的!
LVS(Linux Virtual Server) Linux虚拟服务器
是简写,意思是,它是一个虚拟的服务器集群系统。该项目于1998年5月由张文松博士建立,是中国最早的自由软件项目之一。
- https://baike.baidu.com/item/LVS/17738
参考之前写的网络基础,很多基础内容需要先看这篇文章
学习手册
查看更多细节
- LVS手册man ipvsadm
- keepalived手册man keepalived
- keepalived配置文件手册man keepalived.conf
由于负载均衡,后端必须不止一台服务器。如何调度?LVS提供以下调度算法
- rr(Round Robin): 轮询
- wrr(weight):加权
- sh(source hashing): 源地址散列
- dh(Destination Hashing): 散列目标地址
- lc(Least-Connection): 最少连接
- wlc(Weighted Least-Connection): 至少加权连接
- sed(Shortest Expected Delay): 最短期望延迟
- nq(never queue): 永不排队
- LBLC(Locality-Based Least Connection): 至少基于本地连接
- LBLCR(Locality-Based Least Connections withReplication): 至少基于本地带复制功能的连接
名词定义:
- CIP: Client IP 客户端的IP
- VIP: Virtual IP LVS实例IP,一般暴露在公共网络中的地址;直接向外部用户请求,作为用户请求的目标IP地址
- DIP: Director IP,主要用于与内部主机通信IP地址
- RIP: Real IP 真实的后端服务器IP
- DS: Director Server 指前端负载均衡器节点
- RS: Real Server 后端真实工作服务器
NAT(Net Address Transition)即网络地址转换模式
图片上传失败…(image-2138b5-1615470382448)]
图中的黄色数据包从客户端到LVS集群的,绿色数据包是从LVS集群到客户端的一般过程如下:
- 数据包CIP->VIP,如果您直接将数据包塞RS,RS会丢弃,所以LVS修改目标地址VIP为RIP(D-NAT),此时数据包变成了CIP->RIP发给RS
- RS处理完成后,发送数据包RIP->CIP,这个数据包直接塞到客户端,客户端不会收到,因为客户端是发送的VIP是的,但回来就变成了RIP如果给客户端,客户端也会丢弃,所以LVS修改源地址为VIP(S-NAT),此时数据包变成了VIP->CIP发给客户端
整个过程保证一个原则:如何回去,按照Socket四元组IP反向发回去!比如客户端和LVS而且是CIP->VIP,回去就是VIP->CIP,LVS和RS两者也是如此
- RS使用私有地址,DIP和RIP必须在同一网段内,(因为最后给CIP将数据包扔回LVS)
- 要求和响应报文都需要通过Director Server,在高负载场景中,LVS很容易成为性能瓶颈
- 支持端口映射
- RS可使用任何操作系统
- 对LVS压力会更大,需要通过请求和响应LVS(一般来说,流量是不对称/倾斜的,请求小,响应大)
- 不断转换网络地址,消耗计算能力
既然响应报文太大,有没有办法直接返回响应报文?CIP,不经过LVS?当然,这是DR模式
DR(Direct Router)即直接路由模式
数据包的流向仍然是黄色的,绿色是回来的;RS有一个见,对外隐藏VIP
- 数据包CIP->VIP发送给LVS
- LVS通过修改MAC地址直接把数据包扔给RS,由于RS也有VIP,所以接收数据包
- RS处理完成后,直接密封数据包VIP->CIP,回到客户端,不经过LVS
- 确保前端路由以目标地址为准VIP统一发送报文LVS,而不是RS(因为RS的VIP是隐藏的)
- RS如果使用,可以使用私可以使用公网地址,这个时候可以通过互联网RIP直接访问
- RS跟LVS必须在同一个物理网络中(因为MAC地址欺骗/ARP欺骗就)
- 所有请求报文经理LVS,但必须响应报文LVS
- 不支持地址转换或端口映射(因为这是/2层处理不涉及IP)
- RS它可以是大多数常见的操作系统
- RS不允许指向网关DIP(因为我们不允许他经过LVS)
- RS上的lo接口配置VIP的IP地址(对外隐藏,对内可见)
参考【ARP】Linux内核参数之arp_ignore和arp_announce 内核参数(kernel parameter)定义了收到arp请求时的动作(arp_ignore)与主机通电时arp发起的动作(arp_announce),比如针对eth修改0网卡,文件路径如下:/proc/sys/net/ipv4/conf/eth0/arp_ignore
/proc/以下文件均为进程虚拟文件,Linux一切都是文件,内核运行时的变量arp_ignore映射成文件/proc/sys/net/ipv4/conf/eth0/arp_ignore,因此,修改文件是立即生效的!
定义接收到RP请求时的响应级别:
- 0:本地配置的有相应地址,就给予响应;(只要有ARP请求,就把本机所有网卡的MAC地址都给出去,主动型,不管你是否需要都给你)
- 1:在请求的目标(MAC)地址配置请求到达的接口上的时候,给予响应;(有ARP请求,并且ARP请求的IP是本机某块网卡的IP时,才响应,被动型,问哪个IP就给哪个IP,不全给);
定义将自己地址向外通告时的通告级别:
- 0:允许使用上的IP地址作为arp请求的源IP,通常就是使用数据包的源IP;(发ARP请求时,本机上多块网卡的IP都可以作为源IP,可混用)
- 1:使用不属于该发送网卡子网的本地地址作为发送arp请求的源IP地址;(发ARP请求时,尽量用自己的IP作为请求的源IP,注意尽量二字,万不得已还是可以借用别人的IP用一下,发生的情况可能是路由表的限制)
- 2:忽略IP数据包的源IP地址,选择该发送网卡上的本地地址作为arp请求的源IP地址;(发ARP请求时,哪块网卡发的ARP请求,就用该网卡的IP地址,不能混用)
那么作为RS的VIP要达到对内隐藏,对外可见,需要怎么配置?
arp_ignore无疑应该配置成1,要隐藏,不问就不说
arp_announce应该配置成2,要隐藏,其他网卡发ARP请求时,肯定不能借用我的VIP,你这一借,不就把VIP暴露了么
RS和LVS必须在同一机房中,很显然,ARP欺骗条件要求LVS和DS要在同一个物理局域网内,那有没有不需要再同一个局域网内的?但是是有,那就是隧道模式
TUN(Tunnel)即隧道模式
所谓隧道,最简单的理解就是数据包套数据包,一个数据包背上另一个数据包,到了目的地再放下来,整个流程如下:
- 数据包CIP->VIP发送给LVS
- LVS在源数据包外面套一层形成[DIP->RIP[CIP->VIP]],通过DIP->RIP将数据包发送给RS(DIP和RIP可以是不同的物理网络)
- RS拆包接收到CIP->VIP的请求处理完成之后直接封数据包VIP->CIP,返回给客户端,不经过LVS
- RIP、VIP、DIP全是公网地址
- RS的网关不会也不可能指向DIP
- 所有的请求报文经由LVS,但响应报文必须不能进过LVS
- 不支持端口映射
- RS的系统必须支持隧道
其实企业中最常用的是DR实现方式,而NAT配置上比较简单和方便,TUN模式则是综合了DR和NAT的优点
本次实验以最常用的DR模式作为目标
以下实验均基于CentOS7.5,docker 19.03.6,以下命令均在宿主机执行
为什么要在docker中实验?因为LVS涉及到的角色太多,需要很多台不同IP的服务器,而在docker中可以创建很多独立的容器来模拟,足够轻巧和方便,降低整个实验的成本!
-
宿主机CentOS开启ipvs内核模块ipvs虽然是Linux内核模块,但是默认是没有开启的,需要手动开启,因为docker和宿主机是公用内核,所以宿主机开启了docker里面自动开启,否则docker内即使装上ipvsadm也会报错:Can’t initialize ipvs: Protocol not available开启ipvs模块modprobe ip_vs modprobe ip_vs_wrr查看是否开启成功:lsmod | grep ip_vs,回显如下说明开启成功ip_vs_wrr 12697 0 ip_vs 145458 2 ip_vs_wrr…
-
准备docker容器node1到node4分别为LVS、RS1、RS2、LVS备用(为啥需要个备用?后面用来做主备高可用),注意要加上参数–privileged运行,因为我们的目的是把docker当虚拟机用,要在里面安装服务,并不是有现成的服务已经在虚拟机里面了,详情参考:搜索关键词docker使用centos的systemctldocker run -dit --name node1 --privileged centos /usr/sbin/init docker run -dit --name node2 --privileged centos /usr/sbin/initdocker run -dit --name node3 --privileged centos /usr/sbin/initdocker run -dit --name node4 --privileged centos /usr/sbin/init
-
准备IPnode1IP=
docker inspect -f '{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' node1
node2IP=docker inspect -f '{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' node2
node3IP=docker inspect -f '{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' node3
node4IP=docker inspect -f '{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' node4
nodeVIP=‘172.17.0.100’ -
给所有容器添加IP环境变量,后面的命令就可以直接用上面提到的几个IP了,免得每次都要去定义docker exec -it node1 bash -c “echo node1IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo node2IP=node2IP >> ~/.bashrc && echo node3IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo node4IP=node4IP >> ~/.bashrc && echo nodeVIP=KaTeX parse error: Expected 'EOF', got '&' at position 23: … >> ~/.bashrc" &̲& \ docker exec…node1IP >> ~/.bashrc && echo node2IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo node3IP=node3IP >> ~/.bashrc && echo node4IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo nodeVIP=nodeVIP >> ~/.bashrc” && docker exec -it node3 bash -c “echo node1IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo node2IP=node2IP >> ~/.bashrc && echo node3IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo node4IP=node4IP >> ~/.bashrc && echo nodeVIP=KaTeX parse error: Expected 'EOF', got '&' at position 23: … >> ~/.bashrc" &̲& \ docker exec…node1IP >> ~/.bashrc && echo node2IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo node3IP=node3IP >> ~/.bashrc && echo node4IP=KaTeX parse error: Expected 'EOF', got '&' at position 22: …P >> ~/.bashrc &̲& echo nodeVIP=nodeVIP >> ~/.bashrc”
-
测试结束后销毁所有容器docker stop node1 node2 node3 node4 && docker rm node1 node2 node3 node4
参考
分别登陆node1(LVS)、node2(RS1)、node3(RS2)
进入node2和node3配置
# 安装ifconfig nginx
yum install -y net-tools nginx
# 修改内核参数(实现对内可见、对外隐藏)
echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/eth0/arp_announce
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
# 添加VIP(对内可见、对外隐藏) 注意掩码必须是255.255.255.255
# 因为VIP是绑在环回网卡上(回环网卡离内核更近,数据包优先匹配)的,如果掩码不是4个255,则数据包在返回的时候匹配上了回环网卡,匹配不上真实网关,数据包发送不出去
ifconfig lo:vip ${nodeVIP} netmask 255.255.255.255
# 启动nginx
systemctl start nginx
# 给不同RS添加各自IP用于区分
echo `ifconfig $name | grep "inet.*broadcast.*" | cut -d' ' -f10` > /usr/share/nginx/html/index.html
进入node1配置
# 安装ifconfig ipvsadm命令
yum install -y net-tools ipvsadm
# 添加VIP 这里掩码是16还是24根据具体情况而定
ifconfig eth0:vip ${nodeVIP}/16
# 清空所有规则
ipvsadm -C
# -A添加流量进入规则
ipvsadm -A -t ${nodeVIP}:80 -s rr
# 在上面的进入规则下面添加流量出去(分发给RS)的规则
ipvsadm -a -t ${nodeVIP}:80 -r ${node2IP} -g -w 1
ipvsadm -a -t ${nodeVIP}:80 -r ${node3IP} -g -w 1
查看规则ipvsadm -ln,输出如下,说明成功负载到了node2和node3
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.17.0.100:80 rr
-> 172.17.0.4:80 Route 1 0 0
-> 172.17.0.5:80 Route 1 0 0
在宿主机下面执行循环访问
while true; do curl ${nodeVIP}; sleep 1 ;done
- node1中:netstat -natp看不到很多socket链接 ipvsadm -lnc查看偷窥小本本TCP 01:42 FIN_WAIT 172.17.0.1:55416 172.17.0.100:80 172.17.0.4:80 FIN_WAIT: 连接过,偷窥了所有的包 SYN_RECV: 基本上lvs都记录了,证明lvs没事,一定是后边网络层出问题
- node2和node3中:netstat -natp看到很多socket链接
LVS可以实现高并发,但是也存在的问题,LVS挂了就不能响应请求,要实现高可用【HA(High Available)高可用】怎么办,很简单,一变多,一变多分为
- 主备
- 主从
- 主主
此处肯定是用主备,主备健康监测又分为两种
- 每个备轮询主是否挂了,挂了就顶上去,备发给主,主没挂就返回,数据包一去一回总共跑两次
- 主主动给所有备广播健康包,当备一段时间没收到主发的健康包就认为主挂了,自己顶上去,这样数据包只用跑一次
很显然是第二种好,Keepalived也是采用的这种方式
进入node2和node3配置(和DR模式配置一样)
通过查看手册man keepalived.conf了解更多配置文件的细节
进入node1和node4配置
# 安装ifconfig ipvsadm keepalived命令
yum install -y net-tools ipvsadm keepalived
# 备份keepalived原配置文件
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
# 修改配置文件,本例而言如下,不需要的东西去掉
# 主机备机设置的选项不同,主要是state和priority不同
cat > /etc/keepalived/keepalived.conf <<EOF
vrrp_instance VI_1 {
state MASTER # 如果是备机的话使用 BACKUP
interface eth0
virtual_router_id 51
priority 100 # 优先级,主机优先级肯定要比备机高
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
${nodeVIP}/16 dev eth0 label eth0:vip # VIP的设置
}
}
# 虚拟主机设置
virtual_server ${nodeVIP} 80 {
delay_loop 6
lb_algo rr # 轮询
lb_kind DR # DR模式
# 持久化时间超时,避免三次握手打散在不同的RS上,根据具体情况设置,本例实验所以设置为0
persistence_timeout 0
protocol TCP
# RS1
real_server ${node2IP} 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200 # 健康检查状态码为200
}
connect_timeout 3
retry 3
delay_before_retry 3
}
}
# RS2
real_server ${node3IP} 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 3
retry 3
delay_before_retry 3
}
}
}
EOF
# 启动keepalived
systemctl start keepalived
查看VIP是否生效ifconfig,查看ipvs规则是否生效ipvsadm -ln
正常的情况应该是node1 VIP生效,node4没有生效,等node1挂掉之后自己顶上去!
- node1挂掉,检测node4是否生效(node4生效)
- node1上执行systemctl stop keepalived
- 在宿主机查看服务是否还能访问curl ${nodeVIP},发现能访问,说明node4顶上去了!
- 在node4上执行ifocnfig查看vip网卡是否生效,发现生效!
- 启动node1:systemctl start keepalived
- 继续访问,发现能访问,并且在node4上的vip网卡自动失效!
- node2挂掉,检测请求是否还继续打在node2上
- 在node2上执行systemctl stop nginx
- 在宿主机查看服务是否还能访问curl ${nodeVIP},发现能访问,且没有访问到node2上
- 启动node2:systemctl start nginx
- 继续访问多次,发现访问到了node2
keepalived在主机挂掉之后备机自动顶上去,在主机恢复之后备机自动下线,并且还具有对后端服务器的健康检查