LVS简介
Linux virtual server,即Linux虚拟服务器,是一种基于Linux平台的高性能、高可用的服务器负载均衡技术。它主要工作在网络层、传输层(OSI参考模型的第三层、第四层),主要通过IP地址和端口号来转发网络流量。LVS可以在一台或多台服务器(称之为负载均衡器或调度器)上运行,将来自客户端的请求按照一定的策略分配给后端的服务器(称之为真实服务器或后端服务器),以达到分散负载、提高系统整体性能和可用性目的。
LVS的专业术语
-
VS:Virtual Server,虚拟服务器,负责调度
-
RS:Real Server,真实服务器,负责真正提供服务
-
CIP:Client IP,客户端IP
-
VIP:Vitrual server IP,负载均衡器虚拟IP
-
DIP:Director IP,负载均衡器IP
-
RIP:Real server IP,后端请求处理服务器IP
lvs集群体系架构
集群类型
-
lvs-nat:修改请求报文的目标IP,多目标IP的DNAT
-
lvs-dr:操纵封装新的MAC地址
-
lvs-tun:在原请求IP报文之外新加一个IP首部
-
lvs-fullnat: 修改请求报文的源和目标IP
NAT模式
将客户端发送的数据包的的目标VIP在LVS上转换为一台后台服务器RS的RIP,目标端口修改为RS的端口,并发送给RS处理,RS处理完成以后将数据包中的源IP和目标IP对换,转回给LVS,LVS再将数据包中的源IP地址修改为自己的VIP,将目标IP修改为客户端的CIP,转发回给客户端。
DR模式
直连路由模式,只有客户端发来的数据流量经过LVS,此模式为数据链路层(OSI参考模型第二层)负载均衡,修改的是目的MAC地址。
在DR模式中,所有的RS都需要配置RIP和VIP两个IP地址。LVS会通过arp获取到所有RS的IP地址以及对应的MAC地址,因此LVS和RS需处于同一二层网络中。
当客户端发送请求到LVS后,LVS保持源目IP地址、端口号不变,同时将源MAC地址转换为自己的MAC地址,目的地MAC地址转换为调度到的RS的MAC地址,进行转发。
RS在回复相应报文时,源地址为VIP地址,源端口为提供服务的端口,源MAC地址为自己的MAC地址,目的地址IP为客户端的地址CIP,目的端口为客户端发起连接请求时的随机端口,目的MAC地址为网关的MAC地址。
在DR模式中,由于要求DIP和RIP在同一网段,因此无法进行大规模或远距离部署 。
TUN模式
tun模式与nat模式的不同之处在于,在LVS和RS之间的传输不需要修改数据包,而是添加一个新的IP头部(把客户端的数据包封装在一个IP tunnel中),然后发送给RS,RS接收到数据包,解开IP tunnel后,再进行相应处理,并且将数据包通过外网直接发回给客户端。
FULLNAT模式
fullnat模式解决了LVS和RS之间跨vlan通信的问题。在传统的nat模式下,LVS和RS必须位于同一个vlan内,这限制了系统的灵活性和可扩展性。而fullnat模式通过在网络层进行额外的地址转换,使得LVS和RS可以跨VLAN通信。
在FULLNAT模式下,当客户端的请求到达LVS时,LVS会将请求报文的源地址从客户端IP替换为LVS的内网IP,并将目标地址改写为RS的IP。当RS处理完请求后,响应报文会返回给LVS的内网IP。LVS在收到响应报文后,会再次进行地址转换,将源地址从RS的IP改为LVS的内网IP,目标地址改回客户端的IP,然后将报文发送给客户端。
LVS的调度算法
静态算法
-
RR:轮询算法
-
调度器将收到的请求按照顺序轮流分配到集群中的RS。均匀地分配请求,该算法最大的特点就是实现简单,不考虑服务器连接数和负载情况
-
-
WRR:加权轮询算法
-
根据RS的处理能力给予不同的权重,为性能好的服务器分配更多的请求,性能一般的分配较少的请求。
-
-
SH:源地址哈希算法
-
根据请求的源IP地址作为哈希键从静态分配的哈希表中找出对应的服务器。同一个IP地址的请求始终发往第一次调度的RS,可实现会话绑定。
-
-
DH:目的地址哈希算法
-
根据请求的目标IP地址作为哈希键从静态分配的哈希表中找出对应的服务器。发往同一个目标地址的请求始终转发到第一次调度的RS,可用于实现正向代理缓存场景中的负载均衡。
-
动态算法
-
LC:最少连接算法
-
动态地将网络请求调度到连接数最少的RS上,当集群中的服务器性能相近时,这种算法可以进行较好的负载均衡。适用于长链接应用。其负载计算公式为:overhead=活动链接数*256+非活动连接数
-
-
WLC:加权最少连接算法
-
相对于LC,当集群中服务器性能有差异时,建议使用本算法,可以使性能较好的服务器承接较大比例的活动连接。此算法是LVS的默认调度算法,负载公式为:overhead=(活动连接数*256+非活动连接数)/权重值
-
-
SED:最短期望延迟算法
-
当使用WLC时,如果RS的连接数和权重比例正好一样时,下个请求将可能分配给任意一个RS,而不是性能最好的RS,SED可避免这种情况,保证了高权重优先。
-
-
NQ:最少队列算法
-
使用该算法时,第一轮请求会均匀的分配给所有的RS,后续再使用SED算法,这样可以避免某些服务器可能用于得不到请求的机会
-
-
LBLC:基于局部的最少连接算法
-
根据请求的目标IP找出该目标IP最近使用的服务器,若该服务器可用且没有超载,就将请求发给它;若服务器不存在或者超载,且有服务器处于一半的工作负载,则用最少连接原则将请求发给一个可用服务器。是针对目标IP地址的负载均衡算法,可以看做动态的DH算法。可根据负载情况实现正向代理、web缓存等。
-
-
LBLCR:带复制功能的LBLC
-
该算法维护从一个目标IP地址到一组服务器的映射(LBLC维护的是一个目标IP地址到一 台服务器的映射)。根据请求的目标IP找出该目标IP对应的服务器组,按最少连接原则从组内选出一台没有超载的服务器, 将请求发给它;若该服务器超载,则最少连接原则从该集群中选出一台服务器加入到服务器组中,将请求发给该服务器。如果服务器组在一段时间没有修改,将最忙的服务器从组内剔除,以降低复制程度。这个算法也是针对目标IP地址的负载均衡算法,解决LBLC负载不均衡的问题,从负载重的RS复制到负载轻的RS。可根据负载情况实现正向代理、web缓存等。
-
在4.15版本内核以后新增加的调度算法
-
FO(Weighted Fai Over)调度算法:常用作灰度发布
-
在此FO算法中,遍历虚拟服务所关联的真实服务器链表,找到还未过载(未设置IP_VS_DEST_FOVERLOAD标志)的且权重最高的真实服务器,进行调度当服务器承接大量链接,我们可以对此服务器进行过载标记(IP_VS_DEST_F OVERLOAD),那么vs调度器就不会把链接调度到有过载标记的主机中。
-
-
OVF(Overflow-connection)调度算法
-
基于真实服务器的活动连接数量和权重值实现。将新连接调度到权重值最高的真实服务器,直到其活动连接数量超过权重值,之后调度到下一个权重值最高的真实服务器,在此OVF算法中,遍历虚拟服务相关联的真实服务器链表,找到权重值最高的可用真实服务器。一个可用的真实服务器需要同时满足以下条件:
-
未过载(未设置IP_VS_DEST_F OVERLOAD标志)
-
真实服务器当前的活动连接数量小于其权重值
-
其权重值不为零
-
-
LVS命令ipvsadm的使用
在集群管理中的使用
参数 | 说明 |
-A | 增加新的集群 |
-E | 修改存在的集群 |
-t | 使用tcp协议 |
-u | 使用udp协议 |
-s | 指定调度算法,默认为wlc算法 |
-p | 设置持久连接超时,持久连接可以理解为在同一时间段同一来源的请求调度到同一台RS上 |
-f | firewalld mask,防火墙标记 |
#增加新的集群
[root@lvs ~]# ipvsadm -A -t 172.25.254.100:80 -s rr
#修改已存在的集群
[root@lvs ~]# ipvsadm -E -t 172.25.254.100:80 -s wrr
#删除集群
[root@lvs ~]# ipvsadm -D -t 172.25.254.100:80
管理集群中RS的增删改
参数 | 说明 |
-a | 增加新的RS |
-e | 更改已经存在的RS |
-t | 使用tcp协议 |
-u | 使用udp协议 |
-f | 防火墙标记 |
-r | 指向RS的IP地址 |
-g | 直连路由模式 |
-i | tunnel隧道模式 |
-m | nat模式 |
-w | 设定权重 |
-Z | 清空计数器 |
-C | 清空LVS策略 |
-L | 查看LVS策略 |
-n | 不做解析 |
--rate | 输出速率信息 |
-S | 保存策略到配置文件 |
-R | 从配置文件加载到策略表 |
#清空LVS策略
[root@lvs ~]# ipvsadm -C
#查看LVS策略表
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
#添加新的RS,nat模式
[root@lvs ~]# ipvsadm -a -t 172.25.254.100:80 -r 192.168.0.10 -m
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.25.254.100:80 rr
-> 192.168.0.10:80 Masq 1 0 0
#添加新的RS,dr模式
[root@lvs ~]# ipvsadm -a -t 172.25.254.100:80 -r 192.168.0.20 -g
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.25.254.100:80 rr
-> 192.168.0.10:80 Masq 1 0 0
-> 192.168.0.20:80 Route 1 0 0
#设定权重
#注意:设定权重在加权算法中生效
[root@lvs ~]# ipvsadm -e -t 172.25.254.100:80 -r 192.168.0.10 -w 2
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.25.254.100:80 rr
-> 192.168.0.10:80 Route 2 0 0
-> 192.168.0.20:80 Route 1 0 0
#查看速率信息
[root@lvs ~]# ipvsadm -Ln --rate
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port CPS InPPS OutPPS InBPS OutBPS
-> RemoteAddress:Port
TCP 172.25.254.100:80 0 0 0 0 0
-> 192.168.0.10:80 0 0 0 0 0
-> 192.168.0.20:80 0 0 0 0 0
#将策略保存到配置文件中
[root@lvs ~]# ipvsadm -S > /etc/sysconfig/ipvsadm
[root@lvs ~]# cat /etc/sysconfig/ipvsadm
-A -t lvs:http -s rr
-a -t lvs:http -r 192.168.0.10:http -g -w 2
-a -t lvs:http -r 192.168.0.20:http -g -w 1
#我们再将策略表清空
[root@lvs ~]# ipvsadm -C
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
#然后再从配置文件中导出策略
[root@lvs ~]# ipvsadm -R < /etc/sysconfig/ipvsadm
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.25.254.100:80 rr
-> 192.168.0.10:80 Route 2 0 0
-> 192.168.0.20:80 Route 1 0 0
LVS-NAT实验
实验环境
lvs 两个网卡,一个net,一个仅主机
net 172.25.254.100
仅主机 192.168.0.100
webserver1 仅主机
192.168.0.10
gateway 192.168.0.100
webserver2 仅主机
192.168.0.20
gateway 192.168.0.100
实验步骤
1、网络配置
#lvs
[root@lvs ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=172.25.254.100/24,172.25.254.2
method=manual
dns=114.114.114.114;
[root@lvs ~]# cat /etc/NetworkManager/system-connections/ens224.nmconnection
[connection]
id=ens224
type=ethernet
interface-name=ens224
[ipv4]
address1=192.168.0.100/24
method=manual
dns=114.114.114.114;
#webserver1
[root@webserver1 ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=192.168.0.10/24,192.18.0.100
method=manual
dns=114.114.114.114;
#webserver2
[root@webserver2 ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=192.168.0.20/24,192.168.0.100
method=manual
dns=114.114.114.114;
配置完成后,在lvs主机上ping两台webserver,能ping通即可
2、http服务配置
#webserver1
#下载httpd软件包
[root@webserver1 ~]# yum install httpd -y
#输入测试内容
[root@webserver1 ~]# echo webserver1 192.168.0.10 > /var/www/html/index.html
#启动httpd服务并设置开机自启
[root@webserver1 ~]# systemctl enable --now httpd
Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
#webserver2
#下载httpd软件包
[root@webserver2 ~]# yum install httpd -y
#输入测试内容
[root@webserver2 ~]# echo webserver2 192.168.0.20 > /var/www/html/index.html
#启动httpd服务并设置开机自启
[root@webserver2 ~]# systemctl enable --now httpd
Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
3、启动内核路由功能
[root@lvs ~]# sysctl -a | grep ip_forward
net.ipv4.ip_forward = 0
net.ipv4.ip_forward_update_priority = 1
net.ipv4.ip_forward_use_pmtu = 0
[root@lvs ~]# echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
[root@lvs ~]# sysctl -p
net.ipv4.ip_forward = 1
4、LVS配置
#下载ipvsadm软件包
[root@lvs ~]# yum install ipvsadm -y
#添加调度策略
#-A表示增加一个集群,-t表示使用TCP协议,-s rr表示使用的调度算法为rr
[root@lvs ~]# ipvsadm -A -t 172.25.254.100:80 -s rr
#-a表示添加RS,-r指向RS的地址,-m表示使用nat模式
[root@lvs ~]# ipvsadm -a -t 172.25.254.100:80 -r 192.168.0.10:80 -m
[root@lvs ~]# ipvsadm -a -t 172.25.254.100:80 -r 192.168.0.20:80 -m
#查看lvs策略表
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.25.254.100:80 rr
-> 192.168.0.10:80 Masq 1 0 0
-> 192.168.0.20:80 Masq 1 0 0
5、测试访问
LVS-DR实验
实验环境
测试主机client
NAT 172.25.254.10
路由器router
NAT 172.25.254.100
仅主机 192.168.0.100
调度器LVS
仅主机 192.168.0.200
webserver1
仅主机 192.168.0.10
webserver2
仅主机 192.168.0.20
实验步骤
1.网络配置
#client,nat模式网卡
[root@client ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=172.25.254.10/24,172.25.254.100
method=manual
#router,双网卡,ens160为nat网卡,ens224为仅主机网卡
[root@router ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=172.25.254.100/24
method=manual
[root@router ~]# cat /etc/NetworkManager/system-connections/ens224.nmconnection
[connection]
id=ens224
type=ethernet
interface-name=ens224
[ipv4]
address1=192.168.0.50/24
method=manual
#lvs调度器,仅主机模式网卡
[root@lvs ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=192.168.0.200/24,192.168.0.50
method=manual
#webserver1,仅主机模式网卡
[root@webserver1 ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=192.168.0.10/24,192.168.0.50
method=manual
#webserver2 仅主机模式网卡
[root@webserver2 ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
type=ethernet
interface-name=ens160
[ipv4]
address1=192.168.0.20/24,192.168.0.50
method=manual
2、http服务配置
与LVS-NAT实验相同,参考LVS-NAT实验httpd服务配置即可。
3、VIP响应问题
由于在LVS和RS主机上都要配置相同的VIP,所以会产生地址冲突,解决地址冲突的方法主要有以下三种:
①在前端网关做静态绑定
②在各RS上使用arptables
③在各RS上修改内核参数,来限制arp响应和通告级别
我们采用的是第三种方法。
内核参数 | 参数值说明 |
限制响应级别:arp_ignore | 0:默认值,表示可使用本地任意接口上配置的任意地址进行响应 |
1:仅在请求的目标IP配置在本地主机的接收到请求报文的接口上时,才给予相应 | |
限制通告级别: arp_announce | 0:默认值,把本机所有接口的所有信息向每个接口的网络进行通告 |
1:尽量避免将接口信息向非直连连接网络进行通告 | |
2:必须避免将接口信息向非直连连接网络进行通告 |
#在LVS和RS中配置相同的VIP,为192.168.0.100
[root@lvs ~]# ip a a dev lo 192.168.0.100/32
[root@webserver1 ~]# ip a a dev lo 192.168.0.100/32
[root@webserver2 ~]# ip a a dev lo 192.168.0.100/32
#在RS中解决响应问题
[root@webserver1 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
[root@webserver1 ~]# echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
[root@webserver1 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
[root@webserver1 ~]# echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
[root@webserver2 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
[root@webserver2 ~]# echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
[root@webserver2 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
[root@webserver2 ~]# echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
4、LVS配置
#在LVS中配置策略
[root@lvs ~]# ipvsadm -A -t 192.168.0.100:80 -s rr
[root@lvs ~]# ipvsadm -a -t 192.168.0.100:80 -r 192.168.0.10 -g
[root@lvs ~]# ipvsadm -a -t 192.168.0.100:80 -r 192.168.0.20 -g
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn I nActConn
TCP 192.168.0.100:80 rr
-> 192.168.0.10:80 Route 1 0 0
-> 192.168.0.20:80 Route 1 0 0
5、启动内核路由功能
[root@router ~]# sysctl -a | grep ip_forward
net.ipv4.ip_forward = 0
net.ipv4.ip_forward_update_priority = 1
net.ipv4.ip_forward_use_pmtu = 0
[root@router ~]# echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
[root@router ~]# sysctl -p
net.ipv4.ip_forward = 1
6、测试访问
防火墙标签解决轮询错误
轮询规则中可能会遇到的一些错误
以http和https为例,当我们在RS中同时开放80和443端口,那么默认控制是分开轮询的,这样我们就会出现轮询错乱的问题。
当我们第一次访问80被轮询到webserver1后,下次访问443仍可能会被再次轮询到webserver1上。
#在两台webserver上安装mod_ssl,并重启httpd
[root@webserver1 ~]# yum install mod_ssl -y
[root@webserver2 ~]# yum install mod_ssl -y
[root@webserver1 ~]# systemctl restart httpd
[root@webserver2 ~]# systemctl restart httpd
#在LVS中,设置80端口和443端口两组策略
[root@lvs ~]# ipvsadm -A -t 192.168.0.100:80 -s rr
[root@lvs ~]# ipvsadm -A -t 192.168.0.100:443 -s rr
[root@lvs ~]# ipvsadm -a -t 192.168.0.100:80 -r 192.168.0.10 -g
[root@lvs ~]# ipvsadm -a -t 192.168.0.100:80 -r 192.168.0.20 -g
[root@lvs ~]# ipvsadm -a -t 192.168.0.100:443 -r 192.168.0.10 -g
[root@lvs ~]# ipvsadm -a -t 192.168.0.100:443 -r 192.168.0.20 -g
[root@lvs ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.0.100:80 rr
-> 192.168.0.10:80 Route 1 0 0
-> 192.168.0.20:80 Route 1 0 0
TCP 192.168.0.100:443 rr
-> 192.168.0.10:443 Route 1 0 0
-> 192.168.0.20:443 Route 1 0 0
[root@client ~]# curl http://192.168.0.100 && curl -k https://192.168.0.100
webserver2 192.168.0.20
webserver1 192.168.0.20
防火墙标记解决轮询调度问题
FWM:firewall mark
mark target可用于给特定的报文打标记
--set-mark value
其中:value可为0xffff格式,表示16进制数字借助于防火墙标记来分类报文,而后基于标记定义集群服务,可将多个不同的应用使用同一个集群服务进行调度。
#在LVS中, 设定端口标签,将80和443端口视为一个整体
[root@lvs ~]# iptables -t mangle -A PREROUTING -d 192.168.0.100 -p tcp -m multiport --dports 80,443 -j MARK --set-mark 66
[root@lvs ~]# ipvsadm -A -f 66 -s rr
[root@lvs ~]# ipvsadm -a -f 66 -r 192.168.0.10 -g
[root@lvs ~]# ipvsadm -a -f 66 -r 192.168.0.20 -g
[root@client ~]# curl http://192.168.0.100 && curl -k https://192.168.0.100
webserver2 192.168.0.20
webserver1 192.168.0.10
LVS持久连接
在我们客户上网过程中,有很多情况下需要和服务器进行交互,客户需要提交响应信息给服务器,如果单纯的进行调度会导致客户填写的表单丢失,为了解决这个问题,我们可以使用sh算法,但是sh算法比较简单粗暴,可能会导致调度失衡。
解决方案
在进行调度时,不管用什么算法,只要相同源过来的数据包我们就把他的访问记录在内存中,也就是把这个源的主机调度到了另一个RS上。
如果在短期(默认360s)内同源再来访问我们仍然按照内存中所记录的调度信息,把这个源的访问还调度到同一台RS上。
如果过了比较长的时间(默认最长时间360s)同源访问再次来访,那么就会被调度的其他的RS上。
标签:LVS,lvs,技术细节,IP,RS,192.168,内核,80,root From: https://blog.csdn.net/weixin_71169037/article/details/141000449