一行代码就修复了Dubbo的Bug

1.什么是 System.identityHashCode?

2.什么是 hashCode?

3.为什么一行代码就修复了这个 BUG?

前情回顾

先通过一个前情回顾,引出本文所要分享的内容。

Dubbo 一致性哈希负载均衡算法的设计初衷应该是如果没有服务上下线的操作,后续请求根据已经映射好的哈希环进行处理,不需要重新映射。

然而我在研究其源码时,我发现实际情况是即使在服务端没有上下线操作的时候,一致性哈希负载均衡算法每次都需要重新进行 hash 环的映射。

实际情况与设计初衷不符。

于是给 Dubbo 提了一个 issue,地址如下:

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

以下内容是对该 issue 的详细说明:

在 Dubbo 对应的源码中,只需要一行代码。就可以判断是否有服务上下线的操作:

就是下面这一行代码:

int identityHashCode = System.identityHashCode(invokers);

通过判断 invokers(服务提供方 List 集合)的 identityHashCode 是否发生了变化,从而判断是否有服务上下线的操作。

但是这行代码,在Dubbo2.7.0 版本之后就失效了。

问题出在 Dubbo2.7.0 版本引入的新特性之一:标签路由。

其对应的源码如下:

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

通过源码可以看出:在 TagRouter 中的 stream 操作,改变了 invokers,导致每次调用时其 System.identityHashCode(invokers)返回的值不一样。

所以每次调用都会进行哈希环的映射操作,在服务节点多,虚拟节点多的情况下一定会有性能问题。

该问题对应的 PR 链接如下:

https://github.com/apache/dubbo/pull/5440

修复方法也是特别简单:把获取 identityHashCode 的方法从 System.identityHashCode(invokers)修改为 invokers.hashCode()即可。如下图所示:

为什么一行代码就能修复?

为什么把获取 identityHashCode 的方法从 System.identityHashCode(invokers)修改为 invokers.hashCode()就可以了呢?

要回答这个问题,我们首先得明白什么是 identityHashCode?什么是 hashCode?

**什么是 identityHashCode?**我们看看 API 里面的注释:

返回与默认方法 hashCode()返回的给定对象相同的哈希码,无论给定对象的类是否覆盖了 hashCode()。空引用的哈希码为零。

另外关于 identityHashCode 还有下面的三条规则:

1.所以如果两个对象 A == B,那么 A、B 的 System.identityHashCode() 必定相等;

2.如果两个对象的 System.identityHashCode() 不相等,那他们必定不是同一个对象;

3.但是如果两个对象的 System.identityHashCode()相等,并不保证 A==B,因为 identityHashCode 的底层实现是基于一个伪随机数实现的。

什么是 hashCode? 大家应该都比较熟了,还是看 API 上的注释:

再结合下面两个示例代码,深入理解。

示例一:WhyHashCodeDto没有重写 hashCode()方法,所以 identityHashCode 和 hashCode 的值是一样的:

示例二:如下所示,String 是重写了 hashCode()的方法,所以在下面的例子中 identityHashCode 不等于 hashCode:

带入场景

有了前面的知识铺垫,我们就可以回到 Dubbo 的一致性哈希算法的场景中去了。

在 PR 中有一行注释是这样写的:

using the hashcode of list to compute the hash only pay attention to the elements in the list

我们应该只注意 list 里面的元素就可以了。 而这个 list 里面的元素,就是一个个的服务提供方。

所以,在 Dubbo 的一致性哈希算法的场景中,我们只需要关心 List 里面的服务提供方是否有上下线的操作,而不关心这个 List 是否每次都是新的。

我们再回到源码中,结合源码,然后简化源码:

把上面的源码抽离一下,简化一下,如下:

filterInvoker 方法是根据条件过滤 invokers,并返回一个 List。而我传入的条件是,过滤出 invokers 中 invoker 大于 0 的数据:

filterInvoker(invokers, invoker -> invoker > 0);

执行结果如下:

可以看到经过 filterInvoker 方法后,由于集合中所有的元素都满足条件,所以过滤前后,集合中的元素并没有发生变化,导致 hashCode 没有变化。但是由于装元素的容器(集合)已经不是原来的容器了,所以 identityHashCode 发生了变化。

"因为集合中的元素没有发生变化,导致 hashCode 没有变化。"这句话的理由是什么?

因为 List 重写了 hashCode()方法,其算出的 hashCode 只和 list 中的元素相关:

经过 filterInvoker 方法后元素还是【1,2,3】,与过滤之前一样,所以 hashCode 没有变。

"由于装元素的容器(集合)已经不是原来的容器了,所以 identityHashCode 发生了变化。"这句话的理由又是什么?

可以看到在源码中,Collectors.toList()方法会 new List。所以都是新的,那么每次的 identityHashCode 必不相同。

上面的示例代码,模拟的是没有服务上下线的操作。

接下来,我们模拟一下服务下线的场景:

这次传入的过滤条件为,过滤出 invokers 中 invoker 大于 1 的数据:

filterInvoker(invokers, invoker -> invoker > 1);

输出结果如下:

可以看到,过滤后的集合中只有【2,3】了,所以 hashCode 发生了变化。

上面的示例在 Dubbo 的一致性哈希算法的场景中相当于 1 号服务器下线了,服务列表发生了变化,需要重新进行哈希环的映射。

对应源码如下(PR 提交的源码):

因为在标号为 ① 处得到的 invokersHashCode 和之前的不一样了,所以在标号为 ② 处判断条件为真,进入标号为 ③ 的代码处,重新进行 Hash 环的映射,并选择某个虚拟节点执行该请求。

通过上面模拟的两个示例,再结合下面的源码:

也就回答了为什么把上图中编号为 ① 处的代码替换为标号为 ② 的代码,这一行代码就能修复这个 Bug,核心思想就是只关心 List 集合里面的元素变化,而不关心 List 集合容器是否发生变化。

最后说一句

最开始找到这个 BUG 的时候,我自己也是有一套解决方案的。思路也是只关心 List 里面的元素,而不关心 List 这个容器,但是实现方式比较复杂,改动点较多,还需要写一个工具类。

但是看到 issue 下面的这个评论,

我才一下回过神来,原来一行代码就能代替我写的工具类了啊。而对于这个知识点,我之前其实是知道的。

我反思了一下自己为什么没有想到这个方案。

其实就是对于已知道的知识点,掌握不够深刻导致的,没有达到融会贯通的地步。知其然,也知其所以然,可惜在需要使用的场景稍稍一变的情况下,就想不起来了。

知道知识点,但是该用的时候却记不起来,这种情况其实挺常见的,那怎么解决呢?

这篇文章就是我的解决方案,记录下来嘛。就像高中的时候人手一本的错题本,做错的题,不会的题都抄下来嘛。没事的时候翻一翻,总有下次碰到的时候。再次碰到时,就是"一雪前耻"的机会。

好了。

才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。

技术前沿拓展

前端开发,你的认知不能仅局限于技术内,需要发散思维了解技术圈的前沿知识。细心的人会发现,开发内部工具的过程中,大量的页面、场景、组件等在不断重复,这种重复造轮子的工作,浪费工程师的大量时间。

介绍一款程序员都应该知道的软件JNPF快速开发平台,很多人都尝试用过它,它是功能的集大成者,任何信息化系统都可以基于它开发出来。

这是一个基于 Java Boot/.Net Core 构建的简单、跨平台快速开发框架。前后端封装了上千个常用类,方便扩展;集成了代码生成器,支持前后端业务代码生成,实现快速开发,提升工作效率;框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3。如果你有闲暇时间,可以做个知识拓展。

看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

感谢您的阅读,感谢您的关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/418125.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内网穿透Neutrino-Proxy, 中微子代理

中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。技术栈:Solon、MybatisPlus、Netty遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。官网地址1:https://neutrino-p…

Unity3D学习之Unity基础——3D数学

文章目录 1. 前言2 Mathf和Math基础2.1 一般用于只计算一次的函数2.1.1 PI Π PI2.1.2 取绝对值 Abs2.1.3 向上取整 CeilToInt2.1.4 向下取整 FloorToInt2.1.5 钳制函数 Clamp2.1.6 获取最大值 Max2.1.7 获取最小值 Min2.1.8 一个数的n次幂 Pow2.1.9 四舍五入 RoundToInt2.1.10…

OpenAI CEO称“AGI时代”即将来临,下一个风口或为能源领域

原创 | 文 BFT机器人 在最近的达沃斯论坛上,Sam Altman以其深邃的见解和前瞻性的思考,再次成为了全场关注的焦点。他以一场激情四溢的演讲,深入剖析了人工智能技术的未来发展趋势,以及它可能对社会和工作领域产生的深远影响。 Al…

计算机毕业设计 | springboot 图书商城(附源码)

1,项目背景 1.1 研究背景 随着网络时代的兴起,各个行业发生了巨大的变革,纷纷加入线上购物服务的行列,书店行业也不例外。传统的图书购买方式不仅需要花费时间去实体店,而且图书价格不透明,顾 大都被动购…

Python爬虫IP池

目录 一、介绍 1.1 为什么需要IP池? 1.2 IP池与代理池的区别 二、构建一个简单的IP池 三、注意事项 一、介绍 在网络爬虫的世界中,IP池是一个关键的概念。它允许爬虫程序在请求网页时使用多个IP地址,从而降低被封禁的风险,提高…

JUC并发编程知识点总结

JMM Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在…

2024最新Jmeter接口测试教程以及接口测试流程详解

一、Jmeter简介 Jmeter是由Apache公司开发的一个纯Java的开源项目,即可以用于做接口测试也可以用于做性能测试。 Jmeter具备高移植性,可以实现跨平台运行。 Jmeter可以实现分布式负载。 Jmeter采用多线程,允许通过多个线程并发取样或通过独…

5外包功能测试做完,人废了一半····

先说一下自己的情况。大专生,18年通过校招进入湖南某软件公司,干了接近5年的点点点,今年年上旬,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了五年的功能测试…

Architecture Lab:预备知识2【汇编call/leave/ret指令、CS:APP练习4.4】

chap4的练习4.4(page.255)让用Y86-64实现rsum(递归求数组元素之和),提示为:先得到x86-64汇编代码,然后转换成Y86-64的 这是rsum的c实现: long rsum(long *start, long count) {if …

ISA Server2006部署RuoYi无法登录的问题

ISA Server2006部署RuoYi无法正常登录。每次登录都会报错如下: 无效的会话,或者会话已过期,请重新登录。 原因分析 在nginx中部署没有问题,在ISA Server就会报这个错。根据登录的原理,我猜测可能是headr中的Author…

【Python机器学习】多分类问题的不确定度

decision_function和predict_proba也适用于多分类问题。还是以鸢尾花数据集为例: from sklearn.ensemble import GradientBoostingClassifier from sklearn.datasets import make_circles,load_iris import numpy as np from sklearn.model_selection import train_…

元宇宙:智慧城市建设的未来引擎与价值之源

在21世纪的技术大潮中,元宇宙的出现无疑是一场革命,其独特的概念与价值已经引发了全球范围内的关注。 作为新兴科技的前沿,元宇宙为智慧城市建设带来了无限的可能性和价值,有望成为未来城市发展的核心动力。 元宇宙,这…