这是 why 技术的第 28 篇原创文章

之前在《Dubbo 一致性哈希负载均衡的源码和 Bug,了解一下?》中写到了我发现了一个 Dubbo 一致性哈希负载均衡算法的 Bug。

对于解决方案我是这样写的:

特别简单,把获取identityHashCode的方法从System.identityHashCode(invokers)修改为invokers.hashCode()即可。此方案是我提的issue里面的评论,这里System.identityHashCode和 hashCode之间的联系和区别就不进行展开讲述了,不清楚的大家可以自行了解一下。

我说:这里 System.identityHashCode 和 hashCode 之间的联系和区别就不进行展开讲述了,不清楚的大家可以自行了解一下。

但是有读者在后台问我详细原因,我已经和他聊清楚了。

再加上这个BUG 已于近期修复了,且只用了一行代码就修复了,那我就写一下解决方案,以及背后的原理。

即是对之前文章的一个补充,也是一个独立的知识点。

所以本文主要是回答下面这三个问题:

1.什么是 System.identityHashCode?

2.什么是 hashCode?

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

注:本文 Dubbo 源码 2.7.4.1 版本。如果阅读过《Dubbo 一致性哈希负载均衡的源码和 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 下面的这个评论,

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

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

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

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

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

好了。

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

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

以上。

欢迎关注公众号【why 技术】,坚持输出原创。愿你我共同进步。

【原创】够强!一行代码就修复了我提的Dubbo的Bug。的更多相关文章

  1. PyTorch Hub发布!一行代码调用最潮模型,图灵奖得主强推

    为了调用各种经典机器学习模型,今后你不必重复造轮子了. 刚刚,Facebook宣布推出PyTorch Hub,一个包含计算机视觉.自然语言处理领域的诸多经典模型的聚合中心,让你调用起来更方便. 有多方 ...

  2. 推荐一款最强Python自动化神器!不用写一行代码!

    搞过自动化测试的小伙伴,相信都知道,在Web自动化测试中,有一款自动化测试神器工具: selenium.结合标准的WebDriver API来编写Python自动化脚本,可以实现解放双手,让脚本代替人 ...

  3. 深入探索Android热修复技术原理读书笔记 —— 代码热修复技术

    在前一篇文章 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍中,对热修复技术进行了介绍,下面将详细介绍其中的代码修复技术. 1 底层热替换原理 在各种 Android 热修复方案中 ...

  4. Android之ListView性能优化——一行代码绑定数据——万能适配器

    如下图,加入现在有一个这样的需求图,你会怎么做?作为一个初学者,之前我都是直接用SimpleAdapter结合一个Item的布局来实现的,感觉这样实现起来很方便(基本上一行代码就可以实现),而且也没有 ...

  5. 一行代码解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10

    行代码解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10 2012-04-25 16:29:04| 分类: 学习 |字号 订阅 在网站开发中不免因为各种兼容问题苦恼,针对兼容问题,其实IE ...

  6. 【原】iOS动态性(四):一行代码实现iOS序列化与反序列化(runtime)

    为取得更好的排版效果,本文同样发布在简书上,强烈建议跳转到[1]http://www.jianshu.com/p/fed1dcb1ac9f 一.变量声明 为便于下文讨论,提前创建父类Biology以及 ...

  7. 一行代码解决ie6,7,8,9,10兼容性问题

    "浏览器模式"."文档模式"选项的区别如下: 1."浏览器模式"用于切换IE针对该网页的默认文档模式.对不同版本浏览器的条件备注解析.发送给 ...

  8. 一行代码解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10 http://www.jb51.net/css/383986.html

    在网站开发中不免因为各种兼容问题苦恼,针对兼容问题,其实IE给出了解决方案Google也给出了解决方案百度也应用了这种方案去解决IE的兼容问题   百度源代码如下 复制代码 代码如下: <!Do ...

  9. 怎么用一行代码解决CSS各种IE各种兼容问题

    用一行代码来解决CSS在,IE6,IE7,IE8,IE9,IE10 中的各种兼容性问题. 在网站前端写代码的过程中,很多时间IE各个版本的兼容问题很难整.现在百度与谷歌都有了一行解决这种兼容性的代码了 ...

随机推荐

  1. es6 默认参数、rest参数、扩展运算符

    1.默认值 现在可以在定义函数的时候指定参数的默认值了,而不用像以前那样通过逻辑或操作符来达到目的了. function sayHello(name){ //传统的指定默认参数的方式 var name ...

  2. Python深入:02浅拷贝深拷贝

    对象赋值实际上是简单的对象引用.也就是说当你创建一个对象,然后把它赋给另一个变量的时候,Python并没有拷贝这个对象,而只是拷贝了这个对象的引用. 假设想创建一对小夫妻的通用档案,名为person. ...

  3. @codechef - SONATR@ Sonya and Tree

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定 p 为 0~N-1 的一个排列,并给定一棵 N 个点的树. ...

  4. LOJ 10239 有趣的数列

    LOJ 10239 有趣的数列 首先可以将奇数视作入栈,偶数视作出栈,那么它是卡特兰数,其实打表也能看出来,而且好像可以用dp? 不过这道题的难点不在这里,p不是素数,所以不能用求逆元来做,不过前50 ...

  5. 本地运行angularjs应用,提示出现跨域问题

    应用介绍: 使用angularjs创建一个简单的公司员工管理页面 使用Chrome打开后,提示存在跨域问题. 和别人交流后,得知使用像Angular,React,Vue框架,在Chrome地址栏中输入 ...

  6. pytorch 动态调整学习率 重点

    深度炼丹如同炖排骨一般,需要先大火全局加热,紧接着中火炖出营养,最后转小火收汁.本文给出炼丹中的 “火候控制器”-- 学习率的几种调节方法,框架基于 pytorch 1. 自定义根据 epoch 改变 ...

  7. 2018-8-10-git-使用-VisualStudio-比较分支更改

    title author date CreateTime categories git 使用 VisualStudio 比较分支更改 lindexi 2018-08-10 19:16:52 +0800 ...

  8. Mac MAMP 使用终端shell操作mysql数据库

    在MAMP中已经集成了phpMyAdmin,可以很方便的管理mysql数据库,但是有的情况是phpMyAdmin不能做到的.比如,导入sql文件,当sql文件非常大(大于20MB)的时候,apache ...

  9. zoj 3652 Maze

    Maze Time Limit: 2 Seconds      Memory Limit: 65536 KB Celica is a brave person and believer of a Go ...

  10. poj 2996

    提示:很烦很简单的国际象棋棋盘模拟,输入比较麻烦而已 输出时: 1.不论黑白,KQRBN P均是依次输出,强制大写,但不输出“P”,只输出其坐标 2.对白棋的位置,小行优先大行输出(行的数字越小则优先 ...