持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
之前有篇《网关设计与实现》提到了负载均衡的问题,这篇文章补充一些具体Java实现。当然另外一个要写此文的原因是看了一些其它的博客,发现有的基本没有考虑并发问题,有的考虑的并发问题,但没有考虑边界问题,所以有了此文。
负载均衡的实现我们只说3个基本切八股文常见的:
- 轮训
- 权重
- 一致性哈希
轮训
轮训很直白,就是我们3个机器A,B,C。加入此时有3个请求,先请求A,再请求B,再请求C。
方案一:数字加一法
不废话直接上代码。
需要注意有些博客没有处理自增数字溢出情况,这个溢出还得运行一段时间才能复现出来,上到生产就是一个bug。
那么下一步就是如何测试正确性呢,比如我请求2kw次,给5个节点,开启10个线程跑。第一个方法是可以在调用2kw次后,统计每个节点调用数量。但是这个方法没发计算中间过程每个节点调用次数。我采取了另外一个方式,开启了一个定时任务每间隔5秒打印每个节点的调用次数。
从测试结果看,首次执行后第一个节点的调用量少的有一点多,我个人感觉可能是在打印第一个节点调用次数时,其它节点调用量还是在增加?
但从最后结果上看(这次打印是调用2kw次结束后打印,不是定时任务),没有什么大问题。但又有个节点多了一次,有个节点少了一次。暂时不太清楚原因。但总的来说还是ok的。
2kw次50线程跑,耗时15762ms
优点:空
缺点:空
由于目前只有这一个方案,暂时无法评判优缺点。
方案二:随机数法
方案二主要是靠随机数的生成总体会呈现平均分布的原理达到轮询的效果。
2kw次50线程跑,耗时15011ms
优点:相比方案一,不需要循环+CAS,耗时有所下降。
缺点:节点的调用次数没有方案一均衡
方案三:ThreadLocalRandom随机数
使用ThreadLocalRandom生成随机数能够减少Random内的锁竞争,可以明显看到耗时更低了。
优点:相比方案二,耗时有所下降。
缺点:与方案二相同
总结: 方案三与方案二肯定选方案三,但是与方案一相比,在调用量少的情况下,方案三可能会出现负载不均衡的情况,但是对于网关场景来说,这一个问题可以忽略。所以选择方案三。
权重负载均衡
随机数法
随机数法的权重实现类似轮询。区别在于权重大的节点,随机数的范围更大一些。直接上代码了。
代码实现很简单,就是权重大的,区间就大,随机数落在区间的概率就大。测试用例中,5个节点权重=1,2,3,4,5。结果也是符合预期。
2kw次50线程跑,耗时18854ms
但是与轮询相比耗时有所增加。
一致性哈希
一致性哈希在分布式设计比较常用,因为对于有状态的节点,只需要迁移部署数据,不需要迁移所有的数据。
红黑树实现
在java中实现一致性哈希算法可以采用TreeMap作为数据结构,因为TreeMap有个ceilingEntry方法可以直接获取大于key的节点。一般情况都是使用key的hashcode作为散列函数,但是这个方式散列性并不是很好,可以选择使用FNV1_32_HASH算法。