L3/4负载平衡
另一种较为常见的负载平衡则是L3/4负载平衡。这里的L3/4实际上指的就是负载平衡服务器会根据OSI模型中的第三层网络层(Network Layer)和第四层传输层(Transport Layer)所包含的数据来进行负载平衡操作。在这种负载平衡服务器中,这些数据主要包含数据包的IP头和TCP、UDP等协议的协议头:
L3/4负载平衡服务器的工作原理非常简单:在数据到达时,负载平衡服务器将根据自身算法以及OSI模型三四层所包含的数据决定需要处理该数据的服务实例并将其转发。
整个负载平衡的运行包含三方面内容:负载平衡服务器需要知道当前有效的服务实例到底有哪些,并根据自身的分派算法来决定需要处理数据的服务实例,根据分派算法的计算结果将数据发送到目标服务实例上。
首先来看看负载平衡服务器是如何确定服务实例的有效性的。为了能够保证从负载平衡服务器所派发的数据包能被它后面的服务器集群正常处理,负载平衡服务器需要周期性地发送状态查询请求以探测到底哪些服务实例正在有效地工作。这种状态查询请求常常会超出很多人的认知:如果服务实例崩溃但是承载它的操作系统正常工作,那么该操作系统仍然会正常响应负载平衡服务器所发出的Ping命令,只是此时TCP连接会失败;如果服务实例并没有崩溃,而只是挂起,那么它仍然可以接受TCP连接,只是无法接收HTTP请求。
由于这种状态查询请求实际上是特定于服务实例的具体实现,因此很多负载平衡服务器都允许用户添加自定义脚本以执行特定于服务实例的查询。这些状态查询请求常常包含了很多测试,甚至会尝试从服务实例中返回数据。
一旦负载平衡服务器发现其所管理的某个服务实例不再有效,那么它就不会再将任何数据转发给该服务实例,直到该服务实例回归正常状态。在这种情况下,其它各个服务实例就需要分担失效服务器所原本承担的工作。
这里需要注意的一点是,在某个服务实例失效以后,整个系统仍应该具有足够的总容量以处理负载。举例来说,假如一个负载平衡服务器管理了三个具有相同能力的服务实例,而且这三个服务实例各自的负载都在80%左右。如果其中一个服务实例失效,那么所有的负载都需要由其它两个服务实例来完成。每个服务实例就需要承担120%的负载,远超过了它所具有的负载能力。这种情况的直接后果就是,服务显得非常不稳定,并常常有系统超时,应用无法正常工作的情况出现。
OK。 现在假设我们的负载平衡服务器有一个设计良好的状态查询请求,那么它就会根据其所使用的负载平衡算法来为工作的服务实例分配负载。对于初次接触到负载平衡功能的人来说,最常见的误区就是认为负载平衡服务器会根据各个服务实例的响应速度或负载状况来决定请求需要到达的服务实例。
通常情况下,Round Robin算法是最常用也是表现最好的负载平衡算法。如果各个服务实例的容量并不相同,那么负载平衡服务器会使用Weighted Round Robin算法,即根据各个服务实例的实际能力来安比例地分配负载。在某些商业负载平衡服务器中,其的确会根据当前服务实例的负载以及响应时间等因素对这些分配比例自动进行微小地调整,但是它们并不是决定性的因素。
如果单纯地使用Round Robin算法,那么具有关联关系的各个请求将可能被分配到不同的服务实例上。因此很多负载平衡服务器允许根据数据的特定特征对这些负载进行分配,如使用一种哈希算法来对用户所在的IP进行计算,并以计算结果决定需要分配到的服务实例。
同样地,我们也需要考虑某个服务器实例失效的情况。如果负载平衡系统中的某个服务器实例失效,那么哈希算法中的哈希值空间将发生变化,进而导致原本的服务实例分配结果将不再有效。在这种情况下,所有的请求将重新分配服务器实例。另外,在某些情况下,用户的IP也可能在各个请求之间发生变化,进而导致它所对应的服务实例发生更改。当然,不用担心,后面对L7负载平衡服务器的讲解会给您一个解决方案。
在确定了数据包的目标地址后,负载平衡服务器所需要做的事情就是将它们转发到目标实例了。负载平衡服务器所使用的转发方式主要分为三种:Direct routing,Tunnelling以及IP address translation。
在使用Direct routing方式的时候,负载平衡服务器以及各个服务实例必须在同一个网段上并使用同一个IP。在接收到数据的时候,负载平衡服务器将直接对这些数据包进行转发。而各个服务实例在处理完数据包之后可以将响应返回给负载平衡服务器,也可以选择将响应直接发送给用户,而不需要再经过负载平衡服务器。后一种返回方式被称为Direct Server Return。其运行方式如下所示:
在该过程中,负载平衡服务器和各个服务实例都不需要对IP(Internet Protocol)层数据进行任何更改就可以对其进行转发。使用这种转发方式的负载平衡服务器的吞吐量非常高。反过来,这种组织方式也要求集群的搭建人员对TCP/IP等协议拥有足够多的理解。
另一种转发方式Tunnelling实际上与Direct routing类似。唯一的一点不同则是在负载平衡服务器和各个服务之间建立了一系列通道。软件开发人员仍然可以选择使用Direct Server Return来减轻负载平衡服务器的负载。
IP Address Translation则与前两种方式非常不同。用户所连接的目标地址实际上是一个虚拟地址(VIP,Virtual IP)。而负载平衡服务器在接到该请求的时候将会将其目标地址转化为服务实例所在的实际地址(RIP,Real IP),并将源地址更改为Load Balancer所在的地址。这样在对请求处理完毕后,服务实例将会把响应发送到负载平衡服务器。此时负载平衡服务器再将响应的地址更改为VIP,并将该响应返回给用户。在这种转发方式下,其运行流程则如下所示:
有些细心的读者会问:在消息传递的过程中,用户所在的User IP已经不在消息中存在了,那负载平衡服务器在传回响应的时候应该如何恢复用户的IP地址呢?实际上在这种转发方式中,负载平衡服务器会维持一系列会话,以记录每个经由负载平衡服务器的正在处理的各个请求的相关信息。但是这些会话非常危险。如果将会话持续的时间设置得比较长,那么在一个高并发的负载平衡服务器上就需要维护过多的会话。反之如果将会话持续的时间设置得过短,那么就有可能导致ACK Storm发生。
先看会话持续时间较长的情况。假设当前负载平衡服务器每秒钟会接收到50000个请求,而且该负载平衡服务器的会话过期时间为2分钟,那么其就需要保持6000000个会话。这些会话会占用负载平衡服务器的很大部分资源。而且在负载高峰期,其所消耗的资源可能会成倍地增长,会向服务器施加更多的压力。
但是将会话持续时间设置得比较短则更为麻烦。这会导致用户和负载平衡服务器之间产生ACK Storm,占用用户和负载平衡服务器的大量带宽。在一个TCP连接中,客户端和服务端需要通过各自的Sequence Number来进行沟通。如果负载平衡服务器上的会话快速地失效,那么其它TCP连接就有可能重用该会话。被重用的会话中客户端和服务端的Sequence Number都会被重新生成。如果此时原有的用户再次发送消息,那么负载平衡服务器将通过一个ACK消息通知客户端其拥有的Sequence Number出错。而在客户端接受到该ACK消息之后,其将向负载平衡服务器发送另一个ACK消息通知服务端所拥有的Sequence Number出错。服务端接受到该ACK消息后,将再次发送ACK消息到客户端通知其所拥有的Sequence Number出错……这样客户端和服务端之间就将持续地发送这种无意义的ACK消息,直到某个ACK消息在网络传输过程中丢失为止。
因此乍一看来,使用IP Address Translation的方案是最容易的,但是相较于其它两种方案,它却是最危险也是维护成本最高的一种方案。