通过上一篇文章,我们了解了为什么分布式系统需要注册发现组件,也了解了从 cap 理论来说,注册发现是一个AP 模型。如果我们把一个单机系统改为分布式系统,注册发现是一个良好的开端。
为了进一步提升系统的高可用和高性能。之前的单机服务会被部署到多个实例上。 这样后端系统通过注册发现组件,获取服务的网络地址。 获取到服务地址列表后,调用方的请求应该打到哪个服务上呢? 这时候就需要 负载均衡。
负载均衡的关键点
负载均衡需要确保高效,正确的提供服务。让每个实例都能发挥它的能力,不要出现一些负载比较高,而另一些负载却非常低的情况,造成资源浪费。
因此 第一个关键点:公平性
即负载均衡需要关注被调用服务实例组之间的公平性,不要出现旱的旱死,涝的涝死的情况。
接着,我们来讨论一下“正确地提供服务”这个目的。如何正确地提供服务,我认为这是后端服务对外表现出的整体结果。负载均衡需要确保外部对后端服务的请求,一定能被路由到可以提供正确服务的实例上。如果后端实例是有状态的,比如需要利用本地缓存和存储来处理请求的,我们就需要考虑每个请求携带的状态,然后依据状态信息,将请求正确路由到后端的实例上。
第二个关键点:正确性
即对于有状态的服务来说,负载均衡需要关心请求的状态,将请求调度到能处理它的后端实例上,不要出现不能处理和错误处理的情况。
为了更好地实现负载均衡的公平性和正确性,针对各种不同的业务场景,出现了多种不同的策略。在这些不同的业务场景中,我认为对负载均衡策略的设计,影响最大的因素是后端实例是否存在状态,后端实例有状态,负载均衡就需要关心请求的状态。
无状态的负载均衡
无状态的负载均衡是我们日常工作中接触最多的负载均衡模型,它指的是参与负载均衡的后端实例是无状态的,所有的后端实例都是对等的,一个请求不论发向哪一个实例,都会得到相同的并且正确的处理结果,所以无状态的负载均衡策略不需要关心请求的状态。
到这里,你可能会有一个疑问,这些无状态实例难道不能处理像存储数据这样的状态吗?如果需要处理状态应该怎么办呢?这是一个很好的问题,答案也非常简单。
实例将这些状态信息的处理都交给一个中心存储来负责,比如 MySQL 数据库和 Redis 缓存等,实例不在本地机器的磁盘或者内存中,存储任何状态信息。这是一个非常好的设计原则,让专业的中心存储来处理状态信息,大大简化了系统的设计。
下面我们以轮询和权重轮询来举例,先讲一讲它们的负载均衡策略,再结合公平性和正确性这两个关键点,评价无状态的负载均衡策略的具体情况。
轮询
轮询的负载均衡策略非常简单,只需要将请求按顺序分配给多个实例,不用再做其他的处理。例如,轮询策略会将第一个请求分配给第一个实例,然后将下一个请求分配给第二个实例,这样依次分配下去,分配完一轮之后,再回到开头分配给第一个实例,再依次分配。
轮询在路由时,不利用请求的状态信息,属于无状态的负载均衡策略,所以它不能用于有状态实例的负载均衡器,否则正确性会出现问题。在公平性方面,因为轮询策略只是按顺序分配请求,所以适用于请求的工作负载和实例的处理能力差异都较小的情况。
权重轮询
权重轮询的负载均衡策略是将每一个后端实例分配一个权重,分配请求的数量和实例的权重成正比轮询。例如有两个实例 A,B,假设我们设置 A 的权重为 20,B 的权重为 80,那么负载均衡会将 20% 的请求数量分配给 A,80 % 的请求数量分配给 B。
权重轮询在路由时,不利用请求的状态信息,属于无状态的负载均衡策略,所以它也不能用于有状态实例的负载均衡器,否则正确性会出现问题。在公平性方面,因为权重策略会按实例的权重比例来分配请求数,所以,我们可以利用它解决实例的处理能力差异的问题,认为它的公平性比轮询策略要好。
无状态的负载均衡策略除了上面的两种外,还有 FAIR 、随机、权重随机和最少链接数等策略,你可以从两个关键点出发对这些负载均衡策略进行分析。
半状态的负载均衡
半状态负载均衡是介于有状态负载均衡和无状态负载均衡之间的一种策略。在半状态负载均衡中,负载均衡器会在一定程度上保持部分会话或状态信息。
与无状态负载均衡不同,半状态负载均衡会尝试将一组相关的请求发送到同一个节点上,以确保它们能够共享相同的会话或状态信息。这种方式可以解决一些应用程序需要在多个请求之间共享状态的问题。
半状态负载均衡可以通过不同的机制来实现。一种常见的方式是使用粘性会话(Sticky Session),即负载均衡器根据特定规则将客户端的请求路由到同一个节点上,并将客户端与该节点之间建立一个持久的关联。这样,后续的请求都会被发送到相同的节点上,从而使得相同会话的请求都能够被正确处理。
另一种方式是会话复制(Session Replication),即将会话或状态信息在不同的节点之间进行复制和同步。当一个节点接收到客户端的请求后,它会将相关的会话信息复制到其他节点上,以保持一致性。这样,无论后续请求被发送到哪个节点,都能够获取到相同的会话数据。
然而,半状态负载均衡也会带来一些挑战。由于需要维护会话或状态信息,会增加负载均衡器和节点之间的通信开销和复杂性。此外,当节点发生故障或动态扩展时,会话或状态信息的同步和一致性也需要被考虑。
综上所述,半状态负载均衡是在一定程度上保持会话或状态信息的负载均衡策略。它可以解决一些应用程序需要共享状态的问题,但需要权衡资源开销和一致性问题,选择适合的实现方式。
全状态复杂均衡
全状态负载均衡是一种负载均衡策略,其中负载均衡器完全维护会话或状态信息,并确保后续的请求都发送到处理该会话或状态的同一个节点。
在全状态负载均衡中,负载均衡器会跟踪每个会话或状态,并将相应的请求路由到负责该会话或状态的节点。这样可以确保在多个请求之间保持一致的会话或状态数据,并确保应用程序能够正常运行。
全状态负载均衡通常通过向后端节点发送Proxy Protocol或使用Layer 4或Layer 7的负载均衡器来实现。这些负载均衡器可以解析客户端请求中的会话或状态信息,并将请求路由到相应的节点,以便保持会话的连续性。
全状态负载均衡的一个重要优点是,在多个请求之间能够共享会话或状态数据,使得应用程序能够无缝地处理复杂的业务逻辑。它适用于那些需要在多个请求之间维护持久会话状态的应用程序,如电子商务购物车、网站登录状态等。
然而,全状态负载均衡也面临一些挑战。首先,由于负载均衡器需要维护会话或状态信息,会增加负载均衡器的存储和计算开销。其次,当节点发生故障或动态扩展时,需要确保会话或状态信息的同步和一致性。
综上所述,全状态负载均衡是一种在负载均衡器完全维护会话或状态信息的负载均衡策略。它可以实现复杂的应用程序逻辑和持久会话状态,但需要权衡资源开销和一致性问题,并根据具体需求来选择适合的实现方式。