一行代码就修复了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。如果你有闲暇时间,可以做个知识拓展。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++入门到精通】智能指针 shared_ptr 简介及C++模拟实现 [ C++入门 ]

阅读导航 引言一、简介二、成员函数三、使用示例四、C模拟实现五、std::shared_ptr的线程安全问题六、总结温馨提示 引言 在 C 动态内存管理中,除了 auto_ptr 和 unique_ptr 之外,还有一种智能指针 shared_ptr,它可以让多个指针共享同一个动…

MT36291替代MT3608 FP6291 低成本 用于移动电源,蓝牙音箱,便携式设备等

航天民芯原装MT36291 SOT23-6 PIN对PIN替代FP6291LR-G1 MT3608等,低成本,用于移动电源,蓝牙音箱,便携式设备等领域。 TEL:18028786817 专注于电源管理IC 一级代理 技术支持 欢迎试样! 描述 MT36291是一个恒定频…

初始linux:多用户信息共享

提示:以下指令均在Xshell 7 中进行 共享文件的创建: 在创造共享文件之前,我们首先要知道,目录的权限。 目录的权限 分别是 r w x ,r表示对可以在目录中查看目录的文件信息,w表示可以在目录中进行文件的…

MyBatis框架基础到进阶

1、为什么要学习MyBatis 如果没有MyBatis框架,我们依靠JDBC和连接池已经能够很好的和数据库进行交互了,而学习MyBatis框架最核心的原因是为了减少SQL语句对代码的侵入性。 因为在过往不管是使用连接池还是JDBC Templete,所有的SQL语句都写在代…

大创项目推荐 深度学习花卉识别 - python 机器视觉 opencv

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 &a…

【网络安全】常见的网络威胁有哪些?

随着互联网的快速发展,网络安全问题日益凸显。常见的网络威胁包括病毒、木马、恶意软件等。这些威胁不仅会影响计算机的安全运行,还会窃取用户的个人信息,造成巨大的损失。因此,我们需要采取一些措施来保护自己的网络安全。 常见的…

Elasticsearch 入门向使用

文章目录 ElasticSearch简介倒排索引安装(单节点)分词器kibana与Mysql概念上的对比索引库CRUD文档CRUDDSL查询相关性算分Function Score Query自定义算分Boolean Query 搜索结果处理排序分页高亮 数据聚合 aggregations自动补全数据同步集群 ElasticSearch 简介 Elasticsearc…

【2023我的编程之旅】七次不同的计算机二级考试经历分享

目录 我报考过的科目 第一次报考MS Office 第二次报考Web语言,C语言,C语言 第三次报考C语言,C语言,Java语言 分享一些备考二级的方法 一些需要注意的细节 结语 2023年的CSDN征文活动已经进入了尾声,在这最后我…

全志D1-H芯片Tengine支持

简介 ​ Tengine 是 OPEN AI LAB 推出的边缘 AI 计算框架,致力于解决 AIoT 产业链碎片化问题,加速 AI 产业化落地。Tengine 为了解决 AIoT 应用落地问题,重点关注嵌入式设备上的边缘 AI 计算推理,为海量 AIoT 应用和设备提供高性…

学习Spring的第九天

Spring Bean的生命周期 Bean的实例化阶段 : 看配置文件里Bean标签的信息 , 来判断进行实例化(如是否有lazy-init , 或者是否是FactoryBean等等) (实际就是Bean标签表面的信息 , 即BeanDefinition) Bean的初始化阶段 : 对Bean的属性(重要 : BeanPostProcessor方法 , 及如下 , pr…

用VSCode玩STM32的烧录工具 CooCox Cortex Flash Programmer

一、下载软件 经热心兄弟推荐的版本,不知道有没有版权,如有版权问题,请通知删除。 CSDN - 0积分下载:https://download.csdn.net/download/qq_49053936/88744187 二、生成bin文件 插件不同,方法有所不同,各…

【日常聊聊】自然语言处理的发展

🍎个人博客:个人主页 🏆个人专栏: 日常聊聊 ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 技术进步 应用场景 挑战与前景 伦理和社会影响 实践经验 结语 我的其他博客 前言 自然语言处理(NLP&#xf…

关于ElasticSearch,你应该知道的

一、集群规划优化实践 1、基于目标数据量规划集群 在业务初期,经常被问到的问题,要几个节点的集群,内存、CPU要多大,要不要SSD? 最主要的考虑点是:你的目标存储数据量是多大?可以针对目标数据…

【C++ 记忆站】内联函数

文章目录 一、概念二、特性1、inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段,会用函数体替换函数调用2、inline对于编译器而言只是一个建议若一个函数代码很长则编译器不会将它变成内联3、一般来说,函数代码在10行及以内时这时编译器会将它优化为…

学习【Git项目管理工具】这一篇就够了

目录 1. Git概述2. Git代码托管服务3. Git常用命令3-1. Git全局配置设置用户信息查看配置信息 3-2. 获取Git仓库本地初始化仓库克隆远程仓库 3-3. 基本概念工作区文件状态 3-4. 本地仓库操作git reset 操作 3-5. 远程仓库操作查看远程仓库添加远程仓库推送远程仓库拉取远程仓库…

Gorm 应用开发时区问题与unique唯一索引字段数据冲突问题

文章目录 一、定义表模型时区问题1.1 time.Time 与int641.2 优势 二、unique唯一索引字段数据冲突问题 一、定义表模型时区问题 1.1 time.Time 与int64 一般情况下,我们在定义表模型的时候,会使用time.Time,但是会根据当前时间存储。返回给…

ARM多核调度器DSU

1. 背景 从A75开始,ARM提出了一个新的多核心管理系统单元,叫做DSU(DynamIQ Shared Unit)。DSU的核心功能是控制CPU内核,使其成簇Cluster使用,簇内每一个核心可以单独开关、调整频率/电压,能效表现更佳,甚至…

二、VS2019编译的VTK9.0.0 + Qt 5.14.2 环境测试

1. 使用CMake VS2019 编译vtk 9.0.0 时,需要启用支持Qt开关、如下图 如果不会编译的可以参见我的这篇文章: 一、VTK 9.0.0 编译安装步骤 VS2019 CMake3.26.0-CSDN博客 打开Qt5.14.2 ,创建Qt Widget 项目: 构建设置选择 MSVC2017 64bit pro 项目文件加入两行配置: …

SOCKET编程和TCP通信案例三次握手四次挥手

文章目录 一、SOCKET1、网络套接字SOCKET2、网络字节序2.1、小端法2.2、大端法2.3、字节序转换3、IP地址转换函数3.1、本地字节序转网络字节序3.1.1、函数原型:3.1.2、返回值3.2、网络字节序转本地字节序3.2.1、函数原型3.2.2、返回值4、sockaddr地址结构&#xff0…

菜鸟关于做前、后端的整理(html、js),以及疑问

涉及到后端的接口py&#xff0c;前端html和js 这三部分就按照如下格式放到server项目主路径下&#xff0c;这样后端机可以作为一个前端server main.pystaticmain.jsmain.htmlhtml 首先是html要设定网页的显示 <!DOCTYPE html> <html> <head><title>…