在计算机世界中,这是常见的负载平衡(负载平衡),即所谓的负载平衡,即如果一组计算机节点(或一组进程)使用相同的(类似的)服务,那么对服务的请求应该在节点上均匀地分配。负载均衡的前提必须是“从多个服务器提供单一的Internet服务”,提供这些服务的节点称为服务器场、服务器池或后端服务器。
这里的服务是一般化的,可以是简单的计算,也可以是读取或存储数据。负载平衡并不是新事物,这种思想的多核cpu的时代,只有在分布式系统中,负载平衡是无处不在的,它是分布式系统的自然特点,分布式节点是使用大量的计算机来完成一台计算机无法完成计算,存储服务,因为大量的计算机节点,然后平衡调度是非常重要的。
负载均衡的含义是,让所有节点具有最低价格、最佳服务、这样的系统吞吐量,最大的性能更高,用户请求的时间更短。此外,负载均衡提高了系统的可靠性,最大限度地提高了单节点重载甚至崩溃的概率。不难想象,如果一个系统大多数请求会落在同一个节点上,那么请求响应时间就会很慢,1000个节点会退化或崩溃,所以所有请求都会移动到下一个节点,导致雪崩。
事实上,互联网上有很多文章介绍了负载平衡算法,其中大部分都是相似的。本文主要是对这些算法的总结和思考。
一分钟了解负载均衡的一切
本章节的标题和内容都来自一分钟了解负载均衡的一切这一篇文章。当然,原文的标题是夸张了点,不过文中列出了在一个大型web网站中各层是如何用到负载均衡的,一目了然。
常见互联网分布式架构如上,分为客户端层、反向代理nginx层、站点层、服务层、数据层。可以看到,每一个下游都有多个上游调用,只需要做到,每一个上游都均匀访问每一个下游,就能实现“将请求/数据【均匀】分摊到多个操作单元上执行”。
(1)【客户端层】到【反向代理层】的负载均衡,是通过“DNS轮询”实现的;
(2)【反向代理层】到【站点层】的负载均衡,是通过“nginx”实现的;
(3)【站点层】到【服务层】的负载均衡,是通过“服务连接池”实现的;
(4)【数据层】的负载均衡,要考虑“数据的均衡”与“请求的均衡”两个点,常见的方式有“按照范围水平切分”与“hash水平切分”。
数据层的负载均衡,在我之前的《带着问题学习分布式系统之数据分片》中有详细介绍。
算法衡量
在我看来,当我们提到一个负载均衡算法,或者具体的应用场景时,应该考虑以下问题:
第一,是否意识到不同节点的服务能力是不一样的,比如CPU、内存、网络、地理位置;
第二,是否意识到节点的服务能力是动态变化的,高配的机器也有可能由于一些突发原因导致处理速度变得很慢;
第三,是否考虑将同一个客户端,或者说同样的请求分发到同一个处理节点,这对于“有状态”的服务非常重要,比如session,比如分布式存储;
第四,谁来负责负载均衡,即谁充当负载均衡器(load balancer),balancer本身是否会成为瓶颈。
下面会结合具体的算法来考虑这些问题。
负载均衡算法
轮询算法(round-robin)
思想很简单,就是提供同质服务的节点逐个对外提供服务,这样能做到绝对的均衡。Python示例代码如下:
可以看到,所有的节点都是以同样的概率提供服务,即没有考虑到节点的差异,也许同样数目的请求,高配的机器CPU才20%,低配的机器CPU已经80%了。
加权轮询算法(weight round-robin)
加权轮训算法就是在轮训算法的基础上,考虑到机器的差异性,分配给机器不同的权重,能者多劳。注意,这个权重的分配依赖于请求的类型,比如计算密集型,那就考虑CPU、内存;如果是IO密集型,那就考虑磁盘性能。Python示例代码如下
随机算法(random)
这个就更好理解了,随机选择一个节点服务,按照概率,只要请求数量足够多,那么也能达到绝对均衡的效果。而且实现简单很多
加权随机算法(random)
如同加权轮训算法至于轮训算法一样,也是在随机的时候引入不同节点的权重,实现也很类似。
当然,如果节点列表以及权重变化不大,那么也可以对所有节点归一化,然后按概率区间选择:
哈希法(hash)
根据客户端的IP,或者请求的“Key”,计算出一个hash值,然后对节点数目取模。好处就是,同一个请求能够分配到同样的服务节点,这对于“有状态”的服务很有必要:
只要hash结果足够分散,也是能做到绝对均衡的。
一致性哈希
散列算法的缺陷也很明显。当节点数量发生变化时,可以将请求分配给具有较大概率的其他节点,从而引发一系列问题,如粘性会话。在某些情况下,例如分布式存储,是绝对不允许的。
为了解决哈希算法的问题,在简单的条件下,引入了一个一致的哈希算法,在哈希的时候,一个物理节点和多个虚拟节点映射,使用的是虚拟节点的数量,而不是物理节点。当物理节点发生变化时,虚拟节点的数量不需要改变,只需要重新分配虚拟节点。此外,调整对应于每个物理节点的虚拟节点的数量相当于每个物理节点的不同权重。
最少连接算法(least connection)
在许多算法之上,或者不考虑节点之间的差异(旋转、随机、散列),或者节点之间的权重是静态分配的(旋转的加权训练,加权随机,一致性,哈希)。
考虑这样的情况,机器故障,无法及时处理请求,但是新的请求将会持续地在一定的概率分布到这个节点,请求的积压。因此,根据节点的实际负载动态调整节点的权重是非常重要的。当然,要得到节点的真正负载,以及如何定义负载,无论负载是否及时收集,都是需要考虑的问题。
每个节点的当前连接数是一个很容易收集的目标,所以租赁连接是最常用的算法。也有不同或更复杂的客观指标,如最小响应时间(最少响应时间)、最不活跃(最不活跃)等。
一点思考
有状态的请求
首先让我们看看第三个问题中提到的“算法”:如果同一个服务节点的请求相同,相同的请求指向相同的用户或相同的唯一标识。相同的请求(必须)何时被分配到相同的服务节点?这是有状态的——请求依赖于存在于内存或磁盘中的数据,比如web请求的会话,比如分布式存储。如何实现它,有几种方法:
(1)请求分发的时候,保证同一个请求分发到同样的服务节点
这依赖于负载平衡算法,比如简单的训练,这些都不是随机提供的,当添加或删除节点时,哈希方法会失败。可能的是一致性哈希,以及分布式存储中的范围碎片(即记录哪些请求是由哪个服务节点提供的),代价是维护负载均衡器中的额外数据。
(2)状态数据在backend servers之间共享
确保相同的请求被分发到相同的服务节点,这只是请求使用相应的状态数据的一种方法。如果可以在服务节点之间共享状态数据,也可以实现这一点。例如,一个服务节点连接到一个共享数据库,或者一个内存数据库,如memcached。
(3)状态数据维护在客户端
这个在web请求中也有使用,即cookie,不过要考虑安全性,需要加密。
关于load balancer
下一个答案是第四个问题:关于负载平衡器,也就是说,在哪里进行负载平衡,无论是客户机还是服务器,请求的发起者还是请求3。具体地,在客户端,选择服务节点上的信息,然后将请求直接发送到所选的服务节点;在服务节点集群之前放置一个集中式代理,代理负责请求分发。不管怎样,至少您需要知道关于当前服务节点列表的基本信息。
如果客户端负载均衡,客户端必须首先知道服务器列表,或者静态配置,或者有一个简单的查询接口,但是后台服务器加载信息的细节,并不适用于客户机的查询。因此,无论是在客户端负载均衡算法上还是相对简单的,如旋转训练(旋转加权训练),随机(加权随机),这几个散列算法,只要每个客户端足够随机,根据大数定理,服务节点负载均衡。想要在客户端使用更复杂的算法,例如,根据后端的实际负载,您需要在GRPC中使用额外的负载平衡服务(外部负载平衡服务)查询到信息,即使用此方法。
可以看到,负载均衡器与GRPC服务器通信,得到GRPC服务器的负载,然后GRPC客户端从负载均衡器获取此信息,最后GRPC客户端直接连接到所选的GRPC服务器。
代理方法更常见,比如在第7层的Nginx,第四层的F5和LVS,硬件路由和软件分配。中心特性易于控制,并且易于实现一些更复杂和复杂的算法。但是,缺点是显而易见的,负载平衡器本身可能会成为性能瓶颈。其次,可能会引入额外的延迟,并且请求必须先被发送到负载均衡器,然后再发送到实际的服务节点。
负载平衡代理要么是对请求(响应)的响应,要么是由代理(如LVS)的响应;或代理,比如Nginx。下图是lv的示意图。
如果响应也是负载均衡器代理,那么整个服务过程对客户端来说是完全透明的,也会阻止客户端尝试连接后台服务器,提供一层安全!
值得注意的是负载均衡器代理不能是一个单点故障,因此它通常被设计为一个高度可用的主从结构。
其他
在这篇文章中几个常见的负载平衡算法方案(链接:http://www.cnblogs.com/data2value/p/6107653.html),负载平衡是一个推模型,将选择一个服务节点,然后将请求推迟。在另一种方式中,使用消息队列成为拉模型:空闲服务节点采取主动来拉动处理请求,并且每个节点的负载也都是平衡的。消息队列对负载均衡的好处是,服务节点不会被大量请求所淹没,而且更容易增加服务节点。缺点也很明显,请求不是由事实来处理的。
考虑另一个例子,比如在pre -叉gunicorn模型、主(gunicorn仲裁者)将支付指定数量的工作进程,工作进程在同一端口监控,谁听网络连接请求,谁将提供服务,这是工作进程之间的负载平衡。