负载均衡算法(转)

负载均衡算法

参考:简单的老王 https://blog.csdn.net/zgwangbo/article/details/51533657

什么是负载均衡?

load-balance

最常见的一个 case 就是 nginx:比如我们的web逻辑服务器是 jetty 或者 tomcat,一般会有多台,nginx 就需要配置这多台机器:

upstream simplemain.com {
	server 192.168.1.100:8080;
	server 192.168.1.101:8080;
}

那这些机器是怎么样选取的呢?实际就是负载均衡算法。

注意:负载和均衡是两个不同的概念

负载:服务器的承载能力

均衡:保证后端服务器的平衡

负载均衡的算法实际上就是解决跨系统调用的时候,在考虑后端机器承载情况的前提下,保证请求分配的平衡和合理。

为什么要有负载均衡呢?

1、很明显,如果我们不去考虑后端的承载情况,有可能直接就把某台机器压垮了(比如 cpu 利用率已经 80% 了,再给大量的请求直接就干死了),更严重的会直接造成雪崩(一台压死了,对应的请求又压倒其他某台机器上,又干死一台……),从而致使服务瘫痪。

2、如果我们均衡算法选的不好,就会导致后端资源浪费。比如:如果选择一致Hash算法,可以很好利用 cache 的容量。而如果用随机,有可能就会让cache效果大打折扣(每台机器上都要缓存几乎相同的内容)。

怎样实现负载均衡呢?

负载部分:

  1. 手工配置

大家是不是觉得这个听起来很山寨呢?其实不是。这种方式对于中小系统来讲是最有效最稳定的。

因为后端机器的性能配置、上面部署了哪些服务、还能有多大的承载能力等等,我们是最清楚的,那我们在配置的时候,就可以明确的告诉调用者,你只能分配多大的压力到某台服务器上,多了不行!

upstream simplemain.com {
	server 192.168.1.100:8080 weight=30;
	server 192.168.1.101:8080 weight=70;
}

这种方式配置简单,而且很稳定,基本不会产生分配的抖动。不过,带来的问题就是分配很固定,不能动态调整。如果你的后端服务器有一段时间出现性能抖动(比如有其他服务扰动了机器的稳定运行,造成 cpu 利用率一段时间升高),前端调用者就很难根据实际的情况重新分配请求压力。所以,引入了第二种方法。

  1. 动态调整

根据服务器响应速度判断是否达到平衡。如果反应较快则多分配一些请求,如果请求过多导致响应减慢,应该减少对他请求的分配,将压力转移一部分到其他机器。直到所有机器达到一个整体的平衡

这种方案是不是看起来很高级呢?他的好处在于可以动态的来平衡后面服务器的处理能力。不过,任何事物都有两面性。这种方案如果遇到极端情况,可能会造成系统雪崩!当某台机器出现短暂网络抖动的时候,他的响应就可能变慢,这个时候,前端服务就会将他的请求分配给其他的机器。如果分配的很多,就有可能造成某些机器响应也变慢。然后又将这些机器的请求分配给另外的……如此这般,那些勤勤恳恳的机器终将被这些请求压死。

所以,更好的方案,将两者结合一方面静态配置好承载负荷的一个范围,超过最大的就扔掉;另一方面动态的监控后端机器的响应情况,做小范围的请求调整。

均衡算法:

请求如何发送给后端服务,四种算法:

  1. 随机 random
  2. 轮询 round-robin
  3. 一致哈希 consistent-hash
  4. 主备 master-slave

1、随机算法

我们有两台机器,分别需要承载30%和70%的压力,那么我们算法就可以这样来写(伪代码):

bool res = abs(rand()) % 100 < 30

这句话是什么意思呢?

1、我们先产生一个伪随机数:rand()

2、将这个伪随机数的转化为非负数: abs(rand())

3、将这个数取模100,将值转化到[0,100)的半开半闭区间:abs(rand()) % 100

4、看这个数是否落入了前30个数的区间[0,30):abs(rand()) % 100 < 30

如果随机是均匀的话,他们落到[0,100)这个区间里一定是均匀的,所以只要在[0,30)这个区间里,我们就分给第一台机器,否则就分给第二台机器。

随机算法是我们最最最最最最常用的算法,绝大多数情况都使用他。首先,从概率上讲,它能保证我们的请求基本是分散的,从而达到我们想要的均衡效果;其次,他又是无状态的,不需要维持上一次的选择状态,也不需要均衡因子等等。总体上,方便实惠又好用,我们一直用他!

2、轮询算法

(为了方便,我们把第一台机器叫做A,第二台叫做B):

1、我们先给两台机器做个排序的数组:array = [ABBABBABBB]

2、我们用一个计数指针来标明现在数组的位置:idx = 3

3、当一个请求来的时候,我们就把指针对应的机器选取出来,并且指针加一,挪到下一个位置。

这样,十个请求,我们就可以保证有3个一定是A,7个一定是B。

轮训算法在实际中也有使用,但是因为要维护 idx 指针,所以是有状态的。我们经常会用随机算法取代。

3、一致哈希算法

https://www.cnblogs.com/lpfuture/p/5796398.html

一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似。一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用。

  但现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性hash时采用如下步骤:

  1. 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
  2. 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
  3. 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
img

从上图的状态中添加一台 memcached 服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在园(continuum)上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响,如下图所示:

img

一致哈希的性质:

  • 平衡性、单调性、分散性、负载、平滑性

原理....

为什么要这样做呢?

过了很久,老王有了一些体会。实际上,一致Hash要解决的是两个问题:

1、散列的不变性:就是同一个请求(比如:同一个用户id)尽量的落入到一台机器,不要因为时间等其他原因,落入到不同的机器上了;

2、异常以后的分散性:当某些机器坏掉(或者增加机器),原来落到同一台机器的请求(比如:用户id为1,101,201),尽量分散到其他机器,不要都落入其他某一台机器。这样对于系统的冲击和影响最小。

一致Hash算法用的最多的场景,就是分配cache服务。将某一个用户的数据缓存在固定的某台服务器上,那么我们基本上就不用多台机器都缓存同样的数据,这样对我们提高缓存利用率有极大的帮助。

不过硬币都是有两面的,一致Hash也不例外。当某台机器出问题以后,这台机器上的cache失效,原先压倒这台机器上的请求,就会压到其他机器上。由于其他机器原先没有这些请求的缓存,就有可能直接将请求压到数据库上,造成数据库瞬间压力增大。如果压力很大的话,有可能直接把数据库压垮。

4、主备算法

这个算法核心的思想是将请求尽量的放到某个固定机器的服务上(注意这里是尽量),而其他机器的服务则用来做备份,如果出现问题就切换到另外的某台机器的服务上。

这个算法用的相对不是很多,只是在一些特殊情况下会使用这个算法。比如,我有多台Message Queue的服务,为了保证提交数据的时序性,我就想把所有的请求都尽量放到某台固定的服务上,当这台服务出现问题,再用其他的服务。

posted @ 2021-08-09 00:06  egu0o  阅读(214)  评论(0编辑  收藏  举报