全网最权威:LVS高性能原因揭秘

如果你看过我的一些文章,你应该知道,我一般不会把知识点给你直接列出来;
这样的文章网上有一大把,你大可不必来我这看;
如果你要看我的文章,那么,就请你做好思考的准备,跟着我的思路,去一点一点,把这么一个知识的历程,把它研究透彻,你会受益匪浅。

OSI网络分层

我们都应该知道,有 OSI 网络分层模型这么一个概念。此外,由于我们学的是软件工程学,为什么有一个工程这俩字在里面,那就说明了不是纯学术上的一个研究,是有具体的工程去做的。
那么就涉及到分层解耦这么一个思想,任何一个层,只需要关心它这一层的事,而不用去管其它层具体是怎么做的。那么,对于每一层,就只需要专注于这一层的实现、细节,那么与之带来的,就是效率和安全上的提高,以及可插拔式的方便。

就比如开发人员就只需要去关注应用层的事,物理层就只要去管物理层的事,不论物理层的传输如何,光纤也好、WiFi 也好、4G、5G,这些对于其它层来说都是透明的,不论如何改动,升级,只要接口不变,那么其它层级,就无须改动。
七层模型

当然,基于方便,业内也将七层模型简化成了五层模型,也就是将绘画层、表示层、应用层合并,共同组成了应用层。

这时候,如果基于这五层模型,那么客户端所发送一个消息给服务端,那么首先要肯定的是,不是我们的应用程序,或者一个浏览器直接请求服务器,而是一个应用程序,如浏览器,在应用层给出一个请求,递交给下一层,也就是传输层,然后传输层按照传输层的协议,做自己应该做的事,然后交给下一层,网络层,然后再到数据链路层,最后通过物理层的 WiFi、光纤、4G 等等物理介质,将数据送达服务端,
然后,服务端的物理层,数据链路层,一直往上到达应用层,服务器应用程序处理完请求,然后返回响应,同样将响应数据丢到下一层,一直到物理层传输到客户端然后到应用层。
五层模型
不要觉得我文不对题,我为什么要先说这个一定有原因,如果你学过网络,那你一定知道,对于每一层,都有自己对应的协议;
那什么是协议?可以看做一种规范,就是必须去遵守的一种东西。

假设你和一个外国人要对话,那么你说中文,他却说英语,日语,韩语,拉丁语,法语……一堆听不懂的,那你们怎么对话?怎么交流?
所以,必须统一一种语言去交流。

那么对于计算机的网络世界,每一层应该怎么去收发数据,都有协议必须去遵守,那么数据传输,每一层才能理解,另一台计算机发过来的数据,代表了什么含义。

应用层

那我们现在就要开始探讨,既然我们平时的访问,都是基于应用层,然后往服务端去访问,然后服务端的应用层程序来进行处理,
那么,应用层都是使用什么协议?
对于我们生活中每天都用的,比如你拿浏览器去访问网站,那用的是 HTTP 协议,你连接你的虚拟机,那用的是 SSH 协议,你要文件传输,那走的是 FTP 协议,你要发邮件,那就是 SMTP 协议。
就比如我们 web 应用的应用层协议 HTTP,我简单举个例子:

现在,假设我要把百度的主页给请求到:
我先在 /proc/$$/fd 当前进程打开一个文件描述符,
然后 ll,我们可以看到,任何一个进程都会有 0、1、2,输入,输出,还有报错:
fd
然后,我给出了一个路径 /dev/tcp/www.baidu.com/80,这在 Linux 当中就是一个磁盘路径对吧;
而且指定了文件描述符 8,<> 两个方向表示了输入输出两个方向,既可以输入,也可以输出;
回车之后我们就可以看到,其实这个 8,实际上就是指向了一个 socket 套接字;
这就是 Linux 的一切接文件:
看似打开了一个文件,实际上是创建了一个套接字;
这时候,就意味着,和百度的连接就建立好了。
TCP
现在,这个连接是传输控制层的事对不对,也就是操作系统在传输控制层已经用 TCP 和百度建立起了一个连接;
这时候,我们需要模仿的是什么,就是应用层的协议对不对?
这时,访问百度,那就是 HTTP 协议对不对?
所以我们要做的,就是发送,符合 HTTP 协议规范的数据,这样,百度才能处理接收我们的请求。

我们都知道在 Linux 当中,有命令重定向这么一个概念,此时,我们输入:
echo -e ‘GET / HTTP/1.1\n’ >& 8
就意味着,我们把 GET / HTTP/1.1\n 这么一行请求,重定向到文件描述符 8,
此时,文件描述符 8,正代表着这一个 socket 套接字,
而 echo,代表着打印的意思,就相当于我们的 System.out.print() 这么一个方法,
那就意味着,我们打印的话,重定向到了我们的百度:
百度
这时回车,就意味着这行请求已经发出去了对不对,那么百度接收到我们请求,就会发送回响应,到我们的文件描述符 8 里面;
这时,我们只要去读我们的文件描述符 8,就可以看到百度对我们的响应了。
百度响应
刚才我们看到的,就是基于 HTTP 的应用层协议,说白了就是一堆字符串对不对。
不过刚刚我们也说了,HTTP 是基于七层模型的应用层协议,也就是最顶层的;
我们的发送的数据,不过是基于字符串的,要有一种规范的格式,不管是乱输错了什么内容,那么请求、响应的解析就都不会成功。

那遵循了 HTTP 协议之后,那么接下来,才是进入操作系统内核态的传输控制层协议,去进一步,来把我们的请求,也就是一堆字符,发送给服务端,
以及,接收服务端返回的响应。

传输控制层

所以对于 HTTP 来说,我们只需要发送遵守协议的数据即可,然后,数据就会被丢入传输控制层,就会经历耳熟能详的 TCP 三次握手。
对于 TCP 三次握手大家应该都明白吧,如果不明白的话我简单描述一下;
(这里只是简单描述,如果要了解详细些,可以看我之前的博客)
TCP三次握手四次挥手详解

就是,我们的客户端,首先会想服务端发送一次 TCP 建立连接的请求,然后服务端响应应答,然后客户端确认应答,TCP 连接就建立成功了。
当然,这么描述比较抽象,我们可以先想一想,为什么 TCP 建立连接要三次握手,两次不行吗?
那么我们就得先明白,这三次握手的目的是什么!

首先,一端(通常是客户端)先发起一次连接请求,意思是高速服务端,我要和你建立连接啦;
然后,服务端要响应客户端,我收到你的请求啦;
不过,由于连接肯定是双向互通的,所以肯定也得有服务端也说,我也要和你建立连接才行;
然后客户端再响应给服务端,我收到你的请求啦。

这样,一次完整的 TCP 连接才能建立。
要是只有两次,就无法完成双方同时都能发出请求和应答。
但是乍一看,怎么有 4 步,不是说好三次握手吗?

你要是仔细看看,就会发现,第二步和第三步,实际上是可以合二为一的;
服务端响应客户端的同时,不也可以同时告诉客户端,我也要和你建立连接吗?

就相当于你要去拜访一户人家:
你先说:在吗?我要去你家啦;
然后他回应:在啊,你来吧(这句话就既回应了在吗,又同时告诉了你请来吧);
然后,你就可以回复说,那我来了哦。

这样,传输控制层,就只需要关心,数据之间要以如何的方式去发送和接收。

所以实际上,对应于 TCP 三次握手,传输控制层要做的只是:
让自己下一层发送一个握手的数据包;
然后就等着下一层,把握手的包一点点发到服务端,等应答包再传回来,丢回给传输层自己;
然后再让下一层再发送第三次握手的包,就建立起了 TCP 三次握手。

也就是,我们的传输控制层,对于 TCP 连接,只管着不断给下一层丢数据包,
以及接收下面一层传上来的数据包,
来判断,是否是连接建立,是否已经分手,是否网络拥塞,是否需要重传等等等等……
也就是只负责数据的发放与接收,
而数据在网络中的传输过程,则不闻不问,全由下面的层级来做。

四层负载

不过说到传输控制层,那就得顺便提一下,从传输控制层开始,就都是属于内核空间的了。
也就是说,我们的应用程序,是跑在我们的应用层用户空间的;
而往下的传输控制层,一直到物理层,则都是在内核空间的。
所以,这也就涉及到了用户态内核态的切换。

所以,我们的应用程序,实际上也是不能够参与和管理数据的传输控制的;
应用层所能做的,就是让我们的操作系统内核,去发送和接收数据,至于数据到底怎么发送传输,要怎么处理,什么时候响应,应用程序完全做不了主,全部由操作系统来决定。

所以实际上,应用程序要发送数据,也都是往操作系统指定的一块内存空间,将数据写入,然后操作系统驱动硬件,将数据代为发送;
而数据接收的时候,我们的应用程序,也是无法直接接收的,数据只有通过网卡,被传入计算机指定的一处数据缓冲区存着,然后由应用程序来把它取走。

那么,要理解为什么会出现四层的负载均衡,那么就得明白,为什么其它负载会慢?

我们都知道,Nginx 也是常见的负载均衡软件,而且性能很高,当初设计出来就是为了解决 c10k 问题。
然而,Nginx 与 四层负载一比,其负载效率就又差了一个台阶。

首先,我们都知道的一点,那就是 Nginx 是一个软件,是一个基于应用层跑在用户态的一个软件;
那么就对应上了之前我说的两点:

  • 首先,由于是应用层的负载均衡软件,它每次接收到请求,都需要读出用户请求,分析请求,然后按照配置的逻辑进行负载;
  • 其次,由于是用户态的应用软件,那么就涉及到了用户态和内核态的切换,这是一个大的开销;
  • 再有,由于是处于应用层,所以必须完成传输层,那么就必须建立起三次握手,这又是一个大的开销。

既然知道了 Nginx 为什么慢,那么我们就可以想到办法,去解决这些问题。

  • 既然 Nginx 在应用层,需要分析用户请求进行负载,
    那么,就采取降层级的方式,不考虑用户发的是什么,直接去往后端服务器负载;
  • 这样,降了层级之后,就不会处于用户态,又节省了用户态和内核态切换的开销;
  • 此外,由于 Nginx 要建立起三次握手,数据包首先就要来回传递 3 次,
    那么,就选择,不在负载处握手,而是直接将请求丢到后端真实服务器,让真实服务器与用户三次握手。

快速已经可以由此体现出来了,
它在收到用户的数据包的时候,什么都没做,只是看一下端口号,确定是不是要转发给后端服务器的,然后就“哗”地一下直接丢给后面,
所以是到 4 层,却没握手,所以又不到 4 层的这么一个负载均衡。
四层负载均衡
不过这也同时带来了一点注意点,那就是后端的真实服务器,它必须是镜像。
就是对于用户来说,不管访问了哪一个服务器,它都必须是无感知的,看起来必须是一样的。
(你访问网站的时候,总不会刷新一下就感觉变了一个网站吧)

它不能像 Nginx 那样,可以根据请求头,去判断应该负载给哪一个服务器,去定向负载,
基于四层的负载均衡看不到请求头,所以它只能无视用户的请求,直接往后边仍。
镜像

网络层/IP层

那么虽然理清了数据是如何被发送的,但是,基于应用层和传输控制层,还无法决定数据要如何在网络中传输,
传输控制层,只是负责,按照协议,不断将数据,丢给下一层发送。

那么,数据如何一步一步,从我们的客户端,一直跳啊跳啊跳,到服务端那里,就得看我们的网络层。

我们可以先看一下机器上的网络配置:
cat /etc/sysconfig/network-scripts/ifcfg-ens32
后面的 if,就是 interface,cfg 就是 config,就是接口配置,然后就会显示如下信息:
我们一般配置网络,都有 4 个维度:

  • 一个是计算机的 IP 地址,比如这里的 192.168.177.204
  • 一个是掩码,255.255.255.0
  • 还有网关:192.168.177.2
  • 以及 DNS 域名解析的地址
    在这里插入图片描述
    想来这 4 个维度,大部分都是知道的,不过它们又分别代表了什么意思:
  • 首先我们要通信对不对,那么我们的网络中就得有一个来描述自己的东西,别人才能通过这个名字找到它对不对,这就是 ip 地址。
  • 还有 NDS,大家都知道,是一个用来解释域名的这么一个地址
  • 那么,什么又是掩码,我们经常看到的 3 个 255 一个 0 又是什么?
  • 以及,网关又是什么?
    我来详细解释

首先要明白,这个世界有网络没错,但是有一个很重要的概念叫互联网。

我先来做一个假设,假如你要给一个张三送一封信,你在信封上,什么也不写,就写一个张三收,那么假如有一个快递员,他胆敢帮你送这封信,
那么他有可能穷其一生,都没法帮你把这封信送到张三手里,
因为他不知道张三这个人,在哪,哪个省,哪个市,哪个村等等,那他就得一个一个地方去遍历,每个人都去问一遍,才有肯能找到这个人。

ip 地址就是这么一个道理。
ip 地址,它又叫点分字节,我们平常看到的,就是 192.168.1.103 等等,每个 ip,都有 3 个点,分割成了 4 段数字,每个数字,都是一个字节;
一个字节,不就是有 8 个二进制位,也就都是 0 ~ 255 的大小。

而子网掩码,就是拿来,和 ip 地址,按位做与运算,最后得到的,就是网络号。

所以 ip 地址,就相当于前面所说的,那封信的 国家.省市.街道.然后就是门牌号,
通过 ip 地址,就能够从网络中,找到对应的计算机。

就拿我上面的 ip 来说,192.168.177.204,和 255.255.255.0,按位做与运算,
255 代表的 8 位二进制就是全 1 对不对,那么不管谁和它做与运算,那么结果就都是自身,
而 0 代表的 8 进制,就是 8 个 0,那么无论谁和它做与运算,得到的结果就都是 0。

从而,我的虚拟机的网络号,就是 192.168.177.0,
204,就是主机位,这台机器,就是这个网络的第 204 号。

了解了这个之后,我们就还得知道,计算机中还存着一张表,叫路由表。
我们可以看到,这张表有目标、网关、掩码,还有 Iface网卡:
路由表
那么,这代表什么意思?
所以还得从 TCP/IP 开始讲起。

假设,我们现在,要找一个唯一的 ip 标识的主机,那么最先想到的方法就是:
把所有的 ip 的结点位置,全部记录下来;
然后,根据这么一张互联网 ip 结点的图,去计算出它的最短路径;
然后,跟着这条路径,去传输我们的数据。

那么,这就意味着,这个世界上的每一台机器,都需要记录整个世界的全量的结点信息;
而且,假设任意一个结点出现了变化,全世界所有的机器都得跟着改动自己的数据。

不仅如此,全世界的结点数量有多少,那肯定是浩瀚星海,假设要在这么大的数据之下,做一个图的路径计算,那消耗的时间,一定是巨大的。
也就是计算路径慢,虽然传输速度很快,但还是会造成一定的延时。

所以,最终是没有采取这种方案的,
而采取的叫:下一跳机制

假设现在,一台路由器,接入到一个网段中,然后,这个网段,又有其他的路由器,其他的路由器,又分别接入了其他不同的网段中。
在整个互联网当中,就被分割成了到处一小块一小块,
然后,当有一段数据包到达,路由器,只需要去判断,这个数据包,要发送给这个网段中的谁,也就是下一跳;
然后下一个路由器,再接着判断,要扔给哪一个路由器去继续发送。

这样,每个路由器,就不用存储全量的数据,它只需要知道,自己这个网段,有哪些其它的路由器,我应该把这个包扔给这个网段里的谁,让它去接着扔。
这样,每个路由器所需要的计算就非常小,由于它只要负责一个小范围的数据包发送,所以设计也会相比而言更加简单。

现在明白了这个机遇下一跳的概念,我们就能理解,这个路由表,它到底是有什么用途了;
它就记录着,要访问的那个 ip,它的下一跳,在哪里。

上面的路由表,具体的字段就意味着:
我们在访问目标 ip 的时候,拿着目标 ip,和 Genmask 掩码做一次与运算,就能得到 Destination 目标网段;
如果和目标网段匹配,就走 Gateway 网关,就能去访问目标主机。

不过,这里面有两行记录,假设我们要访问统一网段的另一台主机,
比如:192.168.177.203
我们就会先拿 192.168.177.203 ip地址,去和 255.255.255.0 做与运算,得到 192.168.177.0 这个目标网段,然后就能走 Gateway 网关 0.0.0.0,在本地局域网内部,找到目标主机。

这个 0.0.0.0,表示的就是局域网,因为只有网与网之间的通信,才需要下一跳这么一个机制,
要是都在局域网之内了,还要什么下一跳,直接找到那个地址去访问就可以了。

但是,假设,我们访问外网,6.6.6.6
那么,先和掩码 255.255.255.0 做与运算,得到 6.6.6.0 这个网段;
但是一比,和 192.168.177.0 这个网段不一样,说明走不通;
然后走上面一行,6.6.6.6 和掩码 0.0.0.0 做与运算,得到 0.0.0.0 这个网段,
然后和前面 0.0.0.0 这个目标网段一比较,发现相等;
这时候,就走网关,192.168.177.2,就代表通过我们路由器的网关,跑到外网去访问了。

其实我们认真点观察的话,也是可以看出,实际上,任何的 ip,和 0.0.0.0 去做与运算,得到的结果都是 0.0.0.0,也就是一定和目标网段 0.0.0.0 匹配,那么,网关就会是 192.167.177.2,
也就是,除了 192.168.177.0 这个自身所在的网段,其他所有的 ip,都会从这个网关出去,也就是我们的默认网关 192.168.177.2,就会从路由器提供的这个网关出去,访问外网。

所以,这样的所有的数据包都会被扔给我们的路由器,然后我们的路由器,再根据自己的路由表,去找到要发送的下一个路由器,然后再传递给下一个路由器……

在这里,由于我用的是虚拟机,所以网关,就是我的 VM 虚拟出来的一个连接我的 Windows 主机的一个网关。

现在了解了下一跳这么一个概念,我就能抛出一个问题,怎么才能访问到下一跳,也就是我们的目标 ip 写什么才对?
现在,假设我们访问百度:
我们可以发现,百度的 ip 是 112.80.248.76
ping百度
那么,根据上一个路由条目规则,我们的数据包就会匹配到 0.0.0.0 这一条,
所以,数据就要往我们的网关,192.168.177.2 去发送。

数据链路层

那么,问题来了,要发送给网关,我们的目标 ip 写什么?

不急着回答,先做个假设

  • 假设目标 ip 写网关 ip,那么网关接收到数据包之后一看:好的,是发送给我的。
    那么,它还会去给百度继续发送吗?
    明显不会了,因为目标 ip 就是这个网关,而不是百度。
  • 那么,假设,目标 ip 要是不改怎么办?
    那么它要怎样才能把这个数据发送给网关?目前局域网里面又没有百度这个 ip 地址。
    所以因为目标 ip 还是百度啊,怎么才能转发给网关呢?网关即使收到,又怎么知道,是转发给它的呢?

我们输入 arp -a
可以看到,两条 ip 地址,都指向了另一个地址,就是 mac 地址。
arp
我们知道 DNS 是域名到 ip 的解析,而 arp 就是在一个网段内,ip 到 mac 地址的解析。

所以,接下来,解决的就是链路层。
七层网络模型,总不可能在网络层,或者说 IP层,就全部解决了吧,不然还要下面两层干什么。

所以,我们的 ip,就是负责,在茫茫人海中,找到传输路径,找到下一跳;
而我们的数据链路层,负责的就是,定位下一跳的准确位置。
所以,在传输给下一跳的时候,不修改 ip 地址,而是通过具体的 mac 地址,将数据传输给下一跳。

数据,通过两个 ip 端,不断在网络中跳啊跳,传过去;
ip 就是负责,不断找到传输的路径;
而 mac 地址,则是定位具体的机器是哪一个。

ARP协议

既然了解了,数据是如何,通过七层模型,一层层传递发送出去的,那么接下来,就给出一个实际的例子。

假设,现在有这么两个网段,192.168.1.0 和 192.168.2.0;
它们内部都有交换机,以及分别连接在了路由器的一端。
现在假设,192.168.1.6 这个机器,要给 192.168.2.9 这台机器发送消息。
在这里插入图片描述
首先我们已经知道,先是应用层定好数据的格式,然后交给传输控制层,去发送数据包,然后,就是交给网络层,也就是 ip 层,去寻找下一跳的路线,然后由数据链路层封装 mac 地址从物理层发送。

不过,它要具体怎么一步一步找到那个 ip 对应的主机呢?

之前,我提到过 arp 协议,在我们的机器上,会记录着在同一个局域网内其他机器的 ip 对应的 mac 地址。
不过,我不知道你有没有去想,这些地址是如何被机器记录下来的?
当一台机器新进入局域网之后,如何发现其它的机器?又如何被其它机器发现?

首先,一台机器新启动的时候,由于它在这个局域网还没有身份,别人不知道它的存在,它也不知道别人的存在,
那么,它要进入这个局域网,和它人建立通信应该要怎么做?

这就涉及到 arp 协议了。
一个机器,当它接入局域网,就会发一个数据包,基于 arp 协议的;
但是,这就回到了那个先有鸡还是先有蛋的问题:
要发送一个数据包,那就得有数据包的格式,就要有目标 mac 地址。

那这该怎么办?
实际上,arp 协议所指定的目标地址很特殊:
就是目标mac 地址:FFFFFFF(全F)
目标 IP:192.168.1.1

然后,数据包会被发送到交换机。
不过交换机有一个机制,就是如果看到 mac 地址全 f,就会把这个数据包广播。
广播给所有除了发送方自己外的其它所有机器。
arp广播
这个时候,192.168.1.8 就会收到一份数据,然后路由器也会收到一份广播的数据。
然后,如果是局域网的另一台机器收到了,就会比对目标 IP 地址,发现不是给自己的,就会丢弃数据包。
路由器收到了,发现这是一个 arp 数据包,就会响应这个数据包。

然后路由器此时返回的数据包,会封装准确的目标 mac 地址,就是之前发送给它的那台机器的 mac 地址,
然后发送给交换机;
不过此时,由于交换机之前已经收到过那台机器发来的一个数据包,它可以记住哪台机器对应着哪一个 mac 地址,所以此时路由器发出的数据包不会被广播,而是直接准确地发送给之前的计算机。
然后,那台计算机就习得了 IP 到 mac 地址的记录。
路由器响应

所以,一台计算机要和另一台计算机通信,那么数据包一定要封装这三条信息:

  • 原端口号和目标端口号
  • 原 IP 地址和目标 IP 地址
  • 原 mac 地址和下一跳 mac 地址

现在,这台机器已经习得了路由器的 mac 地址,所以,它在发送给 192.168.2.9 这个 IP 的机器时,
由于通过掩码与运算,发现并不匹配自己的局域网段;
所以最终会匹配到 0.0.0.0,走它自己的网关,将自己的数据包发送出去;
于是,封装自己 IP,目标 IP,以及下一跳的 mac 地址,也就是网关的 mac 地址。

数据包被发送到网关,然后网关根据自己的路由条目,原IP 和 目标IP 都不变,仅仅修改下一跳的 mac 地址,然后将数据包发送给 192.168.2.9 这台机器。
在这里插入图片描述

NAT模型

现在,假设你在 192.168.1.6 这台机器上,假设你要访问百度。
那么,根据上面学过的知识,你就会知道,首先会根据本机的路由条目,匹配不到局域网内的主机地址,
于是,最终就会匹配到 0.0.0.0,走默认网关:192.168.1.1,然后前往 Internet。

那么,就涉及到了一个概念,私有IP,和公网IP。
首先,我们的私有IP肯定不会出现在互联网上,
你想一想,我们每个人回到家,家里的那一片局域网都是 192.168.1.xxx,放到公网,那就是一大片一大片的相同IP地址,那互联网就乱套了。

所以,如果私有IP出现在互联网上,就会被直接丢弃。
而在互联网上,每一个IP都是唯一公开的。
在这里插入图片描述
现在假设,你发送了一个请求给百度,那么就会走默认网关,也就是走到你的路由器(家用路由器都是集交换机路由器功能为一体的)。
你的IP是192.168.1.6,端口是 33333。百度假设是 112.80.242.76,端口是 80。

然后,路由器能把你的请求的原IP直接发送到互联网上吗?
肯定是不行的。

所以,这里,路由器做了一次替换,将你的IP地址从 192.168.1.6 替换成了路由器接入公网的IP;
然后,你的请求就被路由器代为发送,进入 Internet;
然后,一路到达百度,百度处理完请求,然后发送响应,原IP就是百度IP,目标IP就是路由器的 6.6.6.6,端口还是 33333;
然后,路由器接收到百度的响应,这时一看,是我刚刚发出去的请求;
于是,就把这个响应的目标IP再改回 192.168.1.6,然后再发给你。
发送百度
百度回复

这很简单,但是,现在有这么一个问题:
由于你目前的互联网有不止一台主机,那么,你们有没有这么一种可能,同时去访问百度,
去百度一下,什么不可告人的小秘密。

现在,假设有一件很碰巧的事,假如,你访问时,使用的端口号是 33333;
另一个人,访问时,使用的端口号,也是 33333;
那么,路由器,替换你们的IP之后,就会产生两个发送方一模一样的数据包;
等到百度返回响应的时候,路由器该怎么区分,哪个数据包是谁的?
在这里插入图片描述
所以,在路由器去替换原地址的时候,会把IP和端口一块替换掉:
比如你的端口被替换成了 11111,他的端口被替换成了 22222,这样,就保证了发送到公网,虽然IP相同,但是由于端口不同,也是可以区分出哪个包是谁发出来的,
这样,等到百度返回响应,也可以将响应准确的归还给你,不会出错。
端口替换
端口替换

明白了这个,那下面我们就能继续去思考,负载均衡器,如何将请求去分发给后面的真实服务器。

首先,客户端(Client)有一个 IP,就叫 CIP;
负载均衡器有一个IP,就是虚拟IP,就叫做 VIP;
然后,真实服务器也有IP,就叫做 Real IP,RIP;
负载均衡器有一个分发的 IP,就叫做 DIP;
在这里插入图片描述

然后,客户端给服务端发送请求,肯定是往这个 VIP 上去发,
因为肯定,百度就算要加服务器,也不可能给所有的客户去说:我们加服务器啦,现在我们有 … 一堆IP,请去访问它们。
这是不合理的,所以,暴露给用户的那一个IP就是固定的,后边成百上千的服务器不断去加,都要通过那一个入口,去抵达后边的服务器。

所以,用户在访问的时候,数据包就是从 CIP -> VIP;
然后,负载均衡器,就能做一个转换,让数据包从 DIP -> RIP,然后就能发送到真实的服务器上。
NAT负载

DR模型

但是,你觉得这样就已经可以了吗?
是否还有什么问题?

假设就是这样,那么当请求,经过 4 层负载均衡器,然后抵达真实服务端之后;
服务端返回的响应,应该怎样再发回给我们的用户?

刚才经过我们的学习,应该能想到,直接把响应再打回给我们的 4 层负载,
然后通过它,再把所有的响应,发送给用户。

但是,这样会不会有什么问题?
首先,我们可以确保的是,这么做一定不会出错。
但是,我们既然要用到这样的四层负载,那就意味着,服务端的并发已经极高,连 Nginx 都已经无法应对,所以才会应用到基于四层的负载均衡器。

但是,就拿我最上面访问百度的例子,
我输入的请求,只有一行,
然后百度,返回的响应,有我一整个页面都显示不下的内容。

也就是说,往往,服务端返回的响应,要占据更大部分的带宽,
所以,如果响应仍然走负载均衡器返回,
那么势必会极大增加负载均衡的网络带宽开销,同时,对负载均衡器的性能影响,也是很大的。

那么,我们要想,能不能,不让响应再走负载均衡器返回,而是直接真实服务器直接将响应回复给用户呢?
直接返回
现在开始,回想我上面说的知识:
你可以发现,曾经,客户是 CIP 发送给服务端的 VIP,
而现在呢,是 RIP 返回给客户 CIP。

那么,当客户收到这个数据包,就会发现:
这玩意哪来的?我什么时候给它发过东西了!
所以数据包就会被丢弃。。

所以,看来直接返回时不行的,但是,为了追求效率,我们直接走负载均衡器返回,会降低性能,好像进入了一个进退两难的境地。

那么,现在假设我们要解决这个问题,那么第一步要做的,就是让返回的 IP 是 VIP 对不对,
因为这样,客户端才会认这个数据包,他才会收下对不对。

那么,既然如此,我们就必须给真实的服务器,也配一个 IP,叫 VIP,
这样才能返回 VIP -> CIP 的数据包对不对。
VIP
但是,在一个局域网内,如果有多个相同的 IP,就会混乱对不对,
我们就得解决这个问题。

现在,我们假设,每个真实服务器都有一个 VIP,然后,它也用这个 VIP 给用户返回响应。
但是,局域网内,其他机器都知道它有一个 RIP,但是都不知道它有一个 VIP,
也就是只有它自己知道自己有一个 VIP。

这样,由于只有负载均衡器暴露了一个 VIP,所以,客户的所有请求,跳啊跳,最终都只会跳到这个负载上,
而不会乱跑,跑到其他机器上,就不会产生混乱;
然后负载,把请求给到后边服务器,由于它有一个 VIP,所以能直接收下对不对;
然后因为有这个 VIP,所以能直接返回个用户,对不对!
VIP
要暴露一个地址很容易,但是难的是,让它有了这个地址,但是还不能让别人知道。

现在,假设,我们的负载,要直接把请求丢给后边的真实服务器,
如果,它不把IP改成RIP,根据我们之前学习的理论知识,那么请求是不是就会一直卡在这个负载这里,一直丢不出去对不对?
从网络层,就是这样的。

但是,我们还记得,还有一个数据链路层对不对?
就是负责一跳与一跳之间的具体位置的请求的转发,那就是有个 mac 地址对吧。

那么,假设,我们的IP不变,但是,把数据链路层的 mac 地址,偷偷改成下一个真实服务器的 mac 地址,
那么,这个数据包是不是最终也可以发送到我们的真实服务端!
然后服务端一看,mac 是自己的地址,然后 VIP,自己也有,那就会收下这个数据包。
在这里插入图片描述
然后,如果是这样,那就能发现,这样是不是更快?
因为 mac 地址是二层的,是数据链路层,不像 NAT,要在 3-4 层。
它只需要修改一下 mac 地址,只在二层做了一下手脚,所以速度是极快的,数据包刷得一下就过去了。

这就是:mac 地址欺骗。

上面探讨了这么多,
现在我们就要尝试实现这么一个技术,将 CIP -> VIP 原封不动,丢到后端的服务器上。

现在,假设你想要车站;
然后,由于你还不在车站,你又不认识去车站的路,你碰巧遇到了一辆滴滴;
你告诉司机,把我送去车站;
然后,司机就一股脑儿随便把你载到一个车站,把你往那一丢,你就到了。

现在,你看完这个例子,不知道你能不能想出来我讲这个例子可以说明一个什么问题。

首先,你知道要去车站;
其次,你不知道怎么去车站;
第三,你不用关心怎么去车站。

这其实很像刚才的 DR 模型,
CIP 要去 VIP,但是到了负载还不是真正的服务器,所以要去另一个隐藏的 VIP。
但是,由于那个 VIP 不知道怎么去,所以,就需要一辆滴滴,帮忙载着这个数据包,把它送过去。
而具体怎么过去,原数据包不用管。

所以,实际上,想要实现,
就只需要,在原有的 CIP -> VIP 的数据包,外面再包上一层 DIP -> RIP 的数据包;
这样,数据包就会被完好送达,然后真实服务器只要把外面那一层包一拆,就能看到里面的 CIP -> VIP,然后自己就会收下。
DR
既然解决了这个问题,下面我们继续思考:

我们既然需要每一台虚拟主机都配置一个 VIP,但是又不能让它暴露,
那怎么才能让它不暴露呢?
因为我们知道,协议是死的,只要这个网卡一接通,那它就一定会把自己暴露出去。

那看起来好像又没有办法了,
不过,下面带出一个小知识,就是我们的计算机的网卡上,是可以配置多个 IP 地址的。

光这样还不行,假设我们往物理网卡上配多个 IP 地址,那么这个 IP 还是会被暴露,
不过还有一个点,我们平时也都在用,
就是:localhost

你永远无法通过这个 localhost 访问别人的主机,但是可以用来访问自己的主机;
也就是,我们的计算机自带一个虚拟网卡,这个网卡除了自己,没有任何人可以访问到。
localhost
那这样,我们的每一个真实的物理主机,就只需要,在这块虚拟网卡上配上 VIP,
那么就可以做到,对外隐藏,对内可见。

然而实际上,我之前还隐藏了一个问题,不知道你有没有发现,
就是当一个请求到达的时候,负载均衡器是直接把这个请求丢到后边的真实服务器上,由真实的服务器,去和用户建立三次握手。
但是,假设第一次,用户的数据包丢给了服务器 1,然后服务器 1 给用户返回了第二个握手包;
然后,用户发来第三个握手包,负载均衡服务器要是把这个握手包丢给了其他服务器怎么办?
那这样,用户连三次握手建立连接都做不到。

所以,这台负载均衡服务器就得具备一个能力,就是它必须得去偷窥这个IP,
什么叫偷窥?
就是,只看,不动手。

所以,当第一个握手包发过来之后,负载均衡服务器就会去偷窥一下客户的IP,然后偷偷拿小本本记录下来,
然后,当这个用户再一次发送数据过来时,它就能知道,该把这个包丢给谁,而不是不小心给了另一台服务器。

你会发现我写到这里,整片文章没有出现过三个字母,就是你想看的:
这个负载均衡,就叫 LVS

发布了21 篇原创文章 · 获赞 187 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_44051223/article/details/105508919