吐血输出:2万字长文带你细细盘点五种负载均衡策略。 (5)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
日志服务 SLS,月写入数据量 50GB 1个月
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 吐血输出:2万字长文带你细细盘点五种负载均衡策略。 (5)

我又发现了一个BUG


前面我介绍了Dubbo 2.6.5版本之前,最小活跃数算法的两个 bug。


很不幸,这次我又发现了Dubbo 2.7.4.1版本,一致性哈希负载均衡策略的一个bug,我提交了issue 地址如下:


https://github.com/apache/dubbo/issues/5429


我在这里详细说一下这个Bug现象、原因和我的解决方案。


现象如下,我们调用三次服务端:


输出日志如下(有部分删减):


可以看到,在三次调用的过程中并没有发生服务的上下线操作,但是每一次调用都重新进行了哈希环的映射。而我们预期的结果是应该只有在第一次调用的时候进行哈希环的映射,如果没有服务上下线的操作,后续请求根据已经映射好的哈希环进行处理。


上面输出的原因是由于每次调用的invokers的identityHashCode发生了变化:


我们看一下三次调用invokers的情况:


经过debug我们可以看出因为每次调用的invokers地址值不是同一个,所以System.identityHashCode(invokers)方法返回的值都不一样。


接下来的问题就是为什么每次调用的invokers地址值都不一样呢?


经过Debug之后,可以找到这个地方:


org.apache.dubbo.rpc.cluster.RouterChain#route


问题就出在这个TagRouter中:

org.apache.dubbo.rpc.cluster.router.tag.TagRouter#filterInvoker


所以,在TagRouter中的stream操作,改变了invokers,导致每次调用时其

System.identityHashCode(invokers)返回的值不一样。所以每次调用都会进行哈希环的映射操作,在服务节点多,虚拟节点多的情况下会有一定的性能问题。


到这一步,问题又发生了变化。这个TagRouter怎么来的呢


如果了解Dubbo 2.7.x版本新特性的朋友可能知道,标签路由是Dubbo2.7引入的新功能。


通过加载下面的配置加载了RouterFactrory:

META-INF\dubbo\internal\org.apache.dubbo.rpc.cluster.RouterFactory(Dubbo 2.7.0版本之前)


META-INF\dubbo\internal\com.alibaba.dubbo.rpc.cluster.RouterFactory(Dubbo 2.7.0之前)


下面是Dubbo 2.6.7(2.6.x的最后一个版本)和Dubbo 2.7.0版本该文件的对比:


可以看到确实是在 Dubbo 2.7.0 之后引入了 TagRouter。


至此,Dubbo 2.7.0 版本之后,一致性哈希负载均衡算法的 Bug 的来龙去脉也介绍清楚了。


解决方案是什么呢?特别简单,把获取 identityHashCode 的方法从 System.identityHashCode(invokers) 修改为 invokers.hashCode() 即可。


此方案是我提的 issue 里面的评论,这里 System.identityHashCode 和 hashCode 之间的联系和区别就不进行展开讲述了,不清楚的大家可以自行了解一下。


(我的另外一篇文章:够强!一行代码就修复了我提的Dubbo的Bug。)


改完之后,我们再看看运行效果:


可以看到第二次调用的时候并没有进行哈希环的映射操作,而是直接取到了值,进行调用。


加入节点,画图分析


最后,我再分析一种情况。在A、B、C三个服务器(20881、20882、20883端口)都在正常运行,哈希映射已经完成的情况下,我们再启动一个D节点(20884端口),这时的日志输出和对应的哈希环变化情况如下:


根据日志作图如下:


根据输出日志和上图再加上源码,你再细细回味一下。我个人觉得还是讲的非常详细了。


一致性哈希的应用场景


当大家谈到一致性哈希算法的时候,首先的第一印象应该是在缓存场景下的使用,因为在一个优秀的哈希算法加持下,其上下线节点对整体数据的影响(迁移)都是比较友好的。

但是想一下为什么 Dubbo 在负载均衡策略里面提供了基于一致性哈希的负载均衡策略?它的实际使用场景是什么?


我最开始也想不明白。我想的是在 Dubbo 的场景下,假设需求是想要一个用户的请求一直让一台服务器处理,那我们可以采用一致性哈希负载均衡策略,把用户号进行哈希计算,可以实现这样的需求。但是这样的需求未免有点太牵强了,适用场景略小。


直到有天晚上,我睡觉之前,电光火石之间突然想到了一个稍微适用的场景了。


如果需求是需要保证某一类请求必须顺序处理呢?


如果你用其他负载均衡策略,请求分发到了不同的机器上去,就很难保证请求的顺序处理了。比如A,B请求要求顺序处理,现在A请求先发送,被负载到了A服务器上,B请求后发送,被负载到了B服务器上。而B服务器由于性能好或者当前没有其他请求或者其他原因极有可能在A服务器还在处理A请求之前就把B请求处理完成了。这样不符合我们的要求。


这时,一致性哈希负载均衡策略就上场了,它帮我们保证了某一类请求都发送到固定的机器上去执行。比如把同一个用户的请求发送到同一台机器上去执行,就意味着把某一类请求发送到同一台机器上去执行。所以我们只需要在该机器上运行的程序中保证顺序执行就行了,比如你加一个队列。


一致性哈希算法+队列,可以实现顺序处理的需求。


好了,一致性哈希负载均衡算法就写到这里。


继续进入下一个议题。


加权轮询负载均衡


这一小节是对于Dubbo负载均衡策略之一的加权随机算法的详细分析。


从 2.6.4 版本聊起,该版本在某些情况下存在着比较严重的性能问题。由问题入手,层层深入,了解该算法在 Dubbo 中的演变过程,读懂它的前世今生。


什么是轮询?


在描述加权轮询之前,先解释一下什么是轮询算法,如下图所示:


假设我们有A、B、C三台服务器,共计处理6个请求,服务处理请求的情况如下:


  1. 第一个请求发送给了A服务器
  2. 第二个请求发送给了B服务器
  3. 第三个请求发送给了C服务器
  4. 第四个请求发送给了A服务器
  5. 第五个请求发送给了B服务器
  6. 第六个请求发送给了C服务器
  7. ......

上面这个例子演示的过程就叫做轮询。可以看出,所谓轮询就是将请求轮流分配给每台服务器


轮询的优点是无需记录当前所有服务器的链接状态,所以它一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。


轮询的缺点也是显而易见的,它的应用场景要求所有服务器的性能都相同,非常的局限。

大多数实际情况下,服务器性能是各有差异,针对性能好的服务器,我们需要让它承担更多的请求,即需要给它配上更高的权重。


所以加权轮询,应运而生。


什么是加权轮询?


为了解决轮询算法应用场景的局限性。当遇到每台服务器的性能不一致的情况,我们需要对轮询过程进行加权,以调控每台服务器的负载。



经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:3:2。那么在10次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的3次请求,服务器 C 则收到其中的2次请求。


这里要和加权随机算法做区分哦。直接把前面介绍的加权随机算法画的图拿过来:


上面这图是按照比例画的,可以直观的看到,对于某一个请求,区间(权重)越大的服务器,就越可能会承担这个请求。所以,当请求足够多的时候,各个服务器承担的请求数,应该就是区间,即权重的比值。


假设有A、B、C三台服务器,权重之比为5:3:2,一共处理10个请求。


那么负载均衡采用加权随机算法时,很有可能A、B服务就处理完了这10个请求,因为它是随机调用。


采用负载均衡采用轮询加权算法时,A、B、C服务一定是分别承担5、3、2个请求。


Dubbo2.6.4版本的实现


对于Dubbo2.6.4版本的实现分析,可以看下图,我加了很多注释,其中的输出语句都是我加的:


示例代码还是沿用之前文章中的Demo,这里分别在 20881、20882、20883 端口启动三个服务,各自的权重分别为 1,2,3。


客户端调用 8 次:


输出结果如下:


可以看到第七次调用后mod=0,回到了第一次调用的状态。形成了一个闭环。


再看看判断的条件是什么:


其中mod在代码中扮演了极其重要的角色,mod根据一个方法的调用次数不同而不同,取值范围是[0,weightSum)。

因为weightSum=6,所以列举mod不同值时,最终的选择结果和权重变化:


可以看到20881,20882,20883承担的请求数量比值为1:2:3。同时我们可以看出,当 mod >= 1 后,20881端口的服务就不会被选中了,因为它的权重被减为0了。当 mod >= 4 后,20882端口的服务就不会被选中了,因为它的权重被减为0了。


结合判断条件和输出结果,我们详细分析一下(下面内容稍微有点绕,如果看不懂,多结合上面的图片看几次):

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
8天前
|
负载均衡 Java Nacos
常见的Ribbon/Spring LoadBalancer的负载均衡策略
自SpringCloud 2020版起,Ribbon被弃用,转而使用Spring Cloud LoadBalancer。Ribbon支持轮询、随机、加权响应时间和重试等负载均衡策略;而Spring Cloud LoadBalancer则提供轮询、随机及Nacos负载均衡策略,基于Reactor实现,更高效灵活。
19 0
|
2月前
|
负载均衡 应用服务中间件 nginx
Nginx的6大负载均衡策略及权重轮询手写配置
【10月更文挑战第9天】 Nginx是一款高性能的HTTP服务器和反向代理服务器,它在处理大量并发请求时表现出色。Nginx的负载均衡功能可以将请求分发到多个服务器,提高网站的吞吐量和可靠性。以下是Nginx支持的6大负载均衡策略:
208 7
|
2月前
|
负载均衡 算法 Java
腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?
尼恩,一位资深架构师,分享了关于负载均衡及其策略的深入解析,特别是基于权重的负载均衡策略。文章不仅介绍了Nginx的五大负载均衡策略,如轮询、加权轮询、IP哈希、最少连接数等,还提供了手写加权轮询算法的Java实现示例。通过这些内容,尼恩帮助读者系统化理解负载均衡技术,提升面试竞争力,实现技术上的“肌肉展示”。此外,他还提供了丰富的技术资料和面试指导,助力求职者在大厂面试中脱颖而出。
腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?
|
3月前
|
负载均衡 Java 对象存储
负载均衡策略:Spring Cloud与Netflix OSS的最佳实践
负载均衡策略:Spring Cloud与Netflix OSS的最佳实践
53 2
|
4月前
|
负载均衡 应用服务中间件 Linux
在Linux中,Nginx如何实现负载均衡分发策略?
在Linux中,Nginx如何实现负载均衡分发策略?
|
4月前
|
缓存 负载均衡 算法
在Linux中, LVS负载均衡有哪些策略?
在Linux中, LVS负载均衡有哪些策略?
|
5月前
|
消息中间件 负载均衡 算法
【RocketMQ系列十二】RocketMQ集群核心概念之主从复制&生产者负载均衡策略&消费者负载均衡策略
【RocketMQ系列十二】RocketMQ集群核心概念之主从复制&生产者负载均衡策略&消费者负载均衡策略
142 2
|
7月前
|
域名解析 缓存 运维
【域名解析DNS专栏】DNS解析策略:如何实现负载均衡与故障转移
【5月更文挑战第23天】DNS在互联网中扮演关键角色,将域名转换为IP地址。本文探讨DNS的负载均衡和故障转移技术,以增强服务可用性和性能。负载均衡包括轮询(简单分配流量)和加权轮询(按服务器处理能力分配)。故障转移通过主备策略和TTL值实现快速切换,确保服务连续性。实践案例展示了在电商网站如何应用这些策略。DNS策略优化可提升网站速度和稳定性,借助云服务和智能工具,DNS管理更加高效。
642 1
【域名解析DNS专栏】DNS解析策略:如何实现负载均衡与故障转移
|
7月前
|
负载均衡
【SpringCloud】Ribbon负载均衡原理、负载均衡策略、饥饿加载
【SpringCloud】Ribbon负载均衡原理、负载均衡策略、饥饿加载
89 0
|
7月前
|
负载均衡 关系型数据库 分布式数据库
【PolarDB开源】PolarDB读写分离实践:优化读取性能与负载均衡策略
【5月更文挑战第26天】PolarDB是云原生关系型数据库,通过读写分离优化性能和扩展性。它设置主节点处理写操作,从节点处理读操作,异步复制保证数据一致性。优化读取性能的策略包括增加从节点数量、使用只读实例和智能分配读请求。负载均衡策略涉及基于权重、连接数和地理位置的分配。实践示例中,电商网站通过主从架构、只读实例和负载均衡策略提升商品查询效率。PolarDB的读写分离与负载均衡为企业应对大数据和高并发提供了有效解决方案。
256 0