知其然,先知其所以然。
疫情终于渐渐得到稳定,可是最近却很不太平。到处的天灾人祸,让我们失去了很多英雄,很痛惜也很难过。
哪有什么岁月静好,不过是有人替你负重前行。
四月四清明节,愿所有的英雄,“归”来仍是少年。
也向所有的逆行者们致敬!
分享区
本周没有问题分享,但是要分享给各位同学一个宝藏网站(正经的那种)
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
基本上所有的数据结构都有 AVL、二叉树、链表、红黑树、BTrees、B+Trees,可以随便增删改查来看底层数据结构动态变化的这么一个好东西。
随便举个例子,mysql的索引数据结构,B+Tree,依次插入,最终呈现就是这样子的。
前言
“负载均衡”这个词已经不“新鲜”了,基本上有点数据量或者时间段热点较多的业务都要考虑这个问题。那对于高并发更不用说了,没做负载均衡,服务器是顶不住压力的。
因此,也是面试中常提及的问题之一。
当然,很多同学在平时的项目中也接触过负载均衡,像nginx,以及SpringCloud五大件之Ribbon还有Dubbo,它们都有自己的负载均衡策略。
但是,有想过它们的底层是如何实现负载均衡的嘛?
它是如何给服务器均匀的分配请求?又是如何控制每台服务器的请求量的呢?
耐心看完,相信一定会有些收获哒。
负载均衡的算法
首先本篇主要会说负载均衡的常用的几种算法,随机、轮询、平滑加权轮询算法、哈希。
1.随机算法
故名思议,就是没有规律,从一堆元素中随机选择。就像大家的年会抽奖,忽略掉筛选条件,从一堆人里随机抽取中奖人就是这种算法。
第一种:
举个例子,定义了10个服务器的ip地址,通过随机算法,循环10次,每次获取的ip地址
打印结果值:
这种随机算法呢,用当然是可以用的,但是!!!
假设以后购买了性能更好的机器,那肯定是希望这些机器能够处理更多的请求。能力越大责任越大嘛,不然我花大价钱买你干啥??
那这种场景再使用上面的随机算法肯定就不行了,你没办法控制啊~~~
第二种:
用过nginx的同学肯定也知道权重的概念,,上面的场景我们就可以结合权重的方式来做随机算法,使我们指定的一些性能高的服务器能够处理更多的用户请求。
看下代码:
其实很好理解,循环每个key的权重,权重越大,那添加到list集合中该权重的key肯定也就越多,再次随机算法的时候出现的概率也就越大。这样也就达到了我们的目的。
对应上面的初始化定义的ip,这些随机出来的都是权重比较大的。
那到这,这种方式是不是就是随机算法中最优的了呢?思考一下🤔
其实并不是,我们这里只是做了简单的权重分配,并且很小数值。在实际开发中配置权重肯定不可能这么小,并发量大的情况下最少也是百分位的配置。那我们这种方式的缺点其实就暴露了。权重越大,循环效率就越低,List集合元素越多,占用内存肯定也就越大。
第三种:范围式比较(这可能不是专业的名词,是我自我理解的)
何为范围式比较?
假设一个数组中有
[5,3,2....]
我们以坐标轴的方式展示:
0_____5___3__2
从总权重中随机计算一个值,去和数组中的值进行一一比较,如果随机值小于数组中的元素,那说明随机出来的值落在了第一区域,如果是大于的话那就要那这个随机值减去数组中的元素,依次再去循环比较。
这个假设一个随机值 7
第一次循环
7和5比较 7 > 5
7 - 5 = 2
第二次循环
2 < 3
满足上面所说的,随机出来的值落在了第二区域。
这里应该会有些同学不太明白为什么要减去小于随机数的值?
你可以跟着坐标理解一下,当第一区域不在条件范围内,就应该被“淘汰”了,
0_____5___3__2 7 > 5 把0到5的区域干掉,那坐标是不是就从0到3了
0___3__2 再次循环的时候才能比较到啊。
看下代码实现:
看下返回值:
当然这里还有一个场景我觉得还是提一下,就是说如果每一个ip对应的权重都是一样的,那就没有必要再去走这里的代码了,使用最开始的第一种就可以了。
2.轮询算法
本文由“壹伴编辑器”提供技术支持
这个也很好理解,轮询就是一个接着一个来呗。
同样也来看这种算法的几种方式。
第一种:从List集合中依次的轮询出ip地址
看下返回值:
这种方式其实和随机算法的第一种方式类似,所以同样也就存在相同的缺点,就是权重的问题。
第二种:
看下返回值:
这种方式和随机算法的第二种也是类似的,先循环map集合的权重把所有的元素放到一个新的集合中,然后采用轮询算法计算。
第三种:
上图有这么一个方法:RequsetId.getRequsetId();
这里只是模拟像nginx负载均衡中,有一个RequestId的变量,这种以序列号或者流水号的自增的标识。但是像我们的权重最大值是68,而序列号或者流水号可能是00001,100000等等,所以在下面取余为了避免值过大。
看下返回值:
其实按道理来说,返回值是没问题的,按照权重大小轮询,就应该是这样。
但是从合理性上来说,是存在缺点的。
现在就相当于连续的5个请求都压在第一台服务器上,如果第一台配置的权重再大一点,那最后可能其它几台机器并没有处理什么请求,所有的请求压力全部在第一台,这样肯定是不好的。
通过轮询(10次)的方式,肯定会得到:
AAAAABCAAA
那同样的调用次数,如果我们能够得到这样的结果:
AABACAA
那对于权重较大的服务器请求压力肯定就会减少很多。
所以!!!接下来说的就是轮询算法的升级算法——平滑加权轮询算法
轮询算法——平滑加权轮询算法
本文由“壹伴编辑器”提供技术支持
上面说了ip地址加权重,在这种算法里有一个动态权重的概念,也可以把它看成一个全局变量的,初始值都是0,无需人工配置的,它的作用就是和静态权重进行逻辑运算从而计算出我们上面想要的结果。
结合这种逻辑,我们再来看代码实现:
先定一个实体对象,
打印结果值:
是我们想要的轮询效果。这种方式相对于说,是比较好的一种方式。当然可能还有更好的方式,可以在下方评论,我也学习学习。
随机和轮询是最常用的两种负载均衡的算法,但是有的场景中在实际使用中还是存在缺点的,像seesion共享,先看个图:
第一次nginx通过随机或者轮询的方式可能会把请求交给A服务器,用户登录实现,并且在A服务器上存放一个session。
当第二次客户端再次发起第二次请求,同样nginx通过随机或者轮询的方式可能会把请求交给B服务器,这时发现这台服务器并没有session,导致用户可能又需要重新登录。
这就是session共享存在的问题,当然解决这种问题的方式也会有很多,而一致性哈希算法就是一种方式。
3.一致性哈希算法
本文由“壹伴编辑器”提供技术支持
通过客户端的ip进行哈希算法得到一个hashcode,再根据hashcode把请求交到某台服务器,当下次相同的ip请求过来,nginx根据hashcode把请求交到相同的服务器上。
就像双十一的时候,某宝上有一批人登录可以正常获取数据,而有的会特别慢甚至直接提示网络异常,差不多就是这个原理。每个账号请求的可能都是一台服务器,所以有的其它服务器宕机可能短时间内并不会让请求在其它服务器上的用户察觉到。
ClientIp ——> 通过哈希算法 ——> 得到一个 ——> hashcode ——> 对应唯一的ServerIp
通过映射,形成hashcode和IP地址一一对应关系。
但是要知道一点,客户端发起的请求是无穷无尽的,那对于哈希值也是无穷无尽的,所以我们不可能把所有的哈希值都进行映射到服务器ip上,所以这里需要用到哈希环。
这个圆圈上存放的都是hashcode值,当hashcode值在ip1和ip2之间,则选择ip2作为结果,以此类推,形成一个环状,更好的平摊服务器的请求。
但是,假如服务器4宕机了,就会出现这种情况:
IP1占了哈希环的1/2,那它就要处理更多的请求,做不到平摊的效果了怎么办?
这里又有一个新概念,叫做虚拟节点。
这里我觉得大家不是死记硬背,理解就行,
这里的ip2-1和ip3-1就是虚拟节点,就等同于对于ip2和ip3服务器,目的就是让每个服务器接收的请求更均衡一点,当服务器4宕机,通过临时添加虚拟节点的方式,来控制请求均衡,但是这种方式在正式环境肯定是不行的。
记住一点,所有临时的解决办法都只是临时的,如果没有彻底解决,千万记住要中午再出门,因为,早晚得出事。
哈哈哈哈哈,什么烂梗......冷死个人
最优的解决方式,提前就布置好这些节点,,第一请求量肯定会均衡一点,第二就算4服务器挂掉量,对于其它服务器来说影响也不大,请求量还是比较稳定的。
“讲的什么乱七八糟的,这还那节点的,听不懂记不住,直接来实现代码,我看代码贼6”
“好的,直接来,哈希环使用TreeMap的特性,第一它是有序集合,第二它的数据结构也是红黑树。“
计算hash的方法我随便拷贝的,你可以自己写,主要是实现逻辑。
至此,这几种负载均衡的算法已结束啦。有不足之处,欢迎指出。
代码已上传Github:https://github.com/Leexiaobei/spring-demo