什么是负载均衡

在日常的软件系统中,不管是 CS 架构还是 BS 架构,也无论是传统架构还是所谓的微服务架构,一般来说,正式的系统都不会是单台机器来承载业务。除去承压能力的问题,处于可用性的考虑也很可能会至少有两台机器负载,所以如何使用和分配这两台或者更多机器是一个很有挑战的话题。我对这些话题有一些兴趣,但是,必须说的是这是个无底洞,你总是不能找到一个吻合你所有需求的方案,同时还要满足架构的清晰性,所以,这里我就列举一些我知道或者用过的一个策略,希望能对有兴趣的同学做选择提供一些参考。

首先,在进入正题之前还是有必要解释一下,负载和均衡是两个话题。首先,所谓的负载,我认为是系统的承载能力,这里的系统可能是软件的也可能是硬件的,硬件的偏多。就以硬件来说,相同条件下,一个 1G 内存的机器的承载能力一般会比 32G 内存的机器要差;亦或者一个 cpu 利用率为 80% 的机器比 10% 的机器的承压能力要差。而均衡则是要保证业务在预期的时间内得到处理得到预期的结果,而使用最少的资源,这里就有个理想状态就是刚好后台使用的机器都以能承担的最高负载持续运行。

从这样来看,其实将负载均衡联系起来,其实就是找到一个较好的策略,将资源更充分得利用起来,同时,还能保证业务的稳定运行,这就是我的观点,而这些所谓的策略就是本文介绍的东西。

负载均衡算法

在负载均衡中,我的观点是可以大体上分为两类:粘性和非粘性的,所谓的粘性其实就是指对于同一个客户端(连接),每次用来接收处理它的后端都是同一个,通俗点来说,那就是可以在这种策略下使用最原始的 cookie/session;而非粘性则是一个客户端的每次连接都由同一个后端处理,这可能具有随机性,更常见于分布式场景,所以需要考虑的情况更多,下面就以这种分类方法进行介绍。

同时,将均衡的时候,经常都是和哈希算法关联的,所以如果对所谓的哈希算法以及平均表现比较好的一致性哈希不熟悉的同学可以先看一下我之前写过的一篇:一致性哈希及其 Python 实现

粘性算法

源地址(目标地址)哈希法

这是一种思路比较简单的负载策略,简单的实现其实就是根据客户端的源地址 IP 进行哈希映射,然后映射到某一台远程主机。在这种策略之下,只要系统的状态是稳定的,那么就可以保证负载的粘性;同时,如果使用的哈希算法恰当的话,及时后端的机器有些波动,那么对于客户端的影响也是比较少的,例如一致性哈希可以保证影响的范围在 (1/N) 内。

因为这种策略比较常见且简单,所以更多的就不阐述了。

Ring hash

这种所谓 Ring Hash 其实可以认为是 源地址哈希 的一个特例,也就是前面说的如果在 源地址哈希 中使用一致性哈希的话,那么就是所谓的 Ring Hash 了。好处也就是影响小,可以尽可能保证粘性。

非粘性算法

对于现实应用来说,可能粘性算法的实用性稍微差了一些,所以用的机会比较少,所以记住有这个东西就差不多了,万一哪天真的有需要用的呢?再来就是用得比较广的非粘性的策略的,因为不需要考虑粘性,所以发挥的空间也就大了很多。

随机法(Random)

如果说要最简单的恐怕随机是能排前的了,为什么不说是最简单的呢?因为如果我不搞均衡就找第一个能用的这种应该还更简单。随机法其实就是随便找一个,然后就交给你了,看上去是很简单,但是因为是无法确定的随机,所以可能会出现一个莫名其妙的现象,后面会说一些其他策略的问题,这些问题应该在随机法中都能找到。

轮询法(Round Robin)

另外一个简单的可能就是 Round Robin 了,也就是我们常说的轮询法,将一系列候选的后端放在一个循环队列中,然后每当一个请求就从上一次处理请求的下一个位置找一个主机来处理。其实你可以认为这是一种类似于 LRU 的另外一种说法。

在 Nginx 中,其实默认支持这种策略,可以很简单得通过这样的配置生效:

加权轮询法(Weight Robin)

很明显,使用 Round Robin 明显是具有不公平性,一个很简单的例子就是有可能我创业初期的时候用了两台 2 核的机器,突然有一天我的产品火了,我决定增加 8 台 16 核的机器,你说我如果在这 10 台机器上使用 Round Robin,不是要扼杀功臣嘛。

基于这样的考虑,也许是需要一种带权重的分发策略了,同样类似于 Round Robin, 如果对于一些性能比较强的机器我可以多给它几个席位,这样弱一些的机器占一个席位,强一些的机器占 4 个席位,当 Round Robin 转一圈的时候,弱机器只承担了一份,而强机器承担了 4 份,这样对于整体来说挺合适的,让负载均衡一些。

同样得,在 Nginx 中也是内置这种策略的,对 Round Robin 稍微修改即可:

加权随机法(Weight Random)

这种其实就是在类似于给 Round Robin 加权一样,在随机法中加权,不发散了。

最小连接法(Least Connections)

这种是稍微有那么点意思的策略,虽然 Weight Robin 在一定程度上考虑到了强弱的区别,但是这里的强弱只是静态的强弱,即后端机器的强弱。作为一个搬砖熟练工,我们知道,一粒沙子和一块砖明显是不一样重的,让一个工作一天搬 500 块砖和一天搬 500 个沙子这明显就不是一个量级。同样得,每个请求对于后端的负载也是不一样的,就我们平时的购物来说,可能获取一个商品的详情的复杂度会比下一个单的负载度低一些,对于后台的处理时间可能也低一些,因为展示商品的时候可能就做一些读取操作,对于数据一致性不用太过介意;但是对于下单就不一样的,数据不一致导致超卖和库存有余就不好了。

基于这些考虑,于是有了最小连接法,即下一个请求将分发给请求到来时,保持连接数最少的机器。虽然这在我描述的场景中保持了公平性,但是,又丢失了机器强弱的公平性,这也许可以也加一个 Weight, 但是这能真的正确么? Too many open file 怎么办?

在 Nginx 中,也是默认支持这种的:

总结

这是一些我的学习和思考的方式,如果你有一些我没提到的方式,不妨留言大家聊一下,我很乐意和其他同学,甭管是比我强还是比我强很多的同学,还是说比我年轻的同学,我都乐意听你们的建议,感谢。

Reference

  1. Maglev - Google Search
  2. Nginx Load Balancing Methods
  3. Envoy Supported Load Balancers