这是why技术的第 91 篇原创文章
这篇文章其实并没有什么技术性的分享,从我的角度而言,更多是记录和思考。
把我对于源码和之前写的部分文章反哺给我的一些东西,带来的一点点思考分享给大家。
一行源码我很长时间没打开我的 Outlook 邮箱了。
前两天打开的时候发现我之前给 Dubbo 提交的 pr 居然已经被合并到 master 了:
这是第一次,我提交的 pr 被合并了。
这个 pr 是修复 LFU 缓存策略在 Dubbo 中即使配置了,也不起作用的 bug。
于是我也算是为开源项目贡献过源码的人了。
什么你问我贡献了多少代码?
一行,是的,就一行!
而且,说起来,这次提交真的是没有什么技术含量的事情。因为这是一个必现的 bug,只是很少有人用到这个功能而已。
你知道的,当一个 bug 能稳定复现的时候,其实它已经就不算是一个 bug 了。
但是我想聊聊这次提交背后的一些东西。
发现与解决从宿命论的角度来说,当我写下面这篇文章的第一个字的时候,这个 bug 就注定是等着我去发现并修复了:
其实吧,LRU也就那么回事。
而这篇文章我敲下第一个字的时间是 2020 年 12 月的下旬,这是我 2020 年的最后一篇技术原创文章。
当我写 LRU 的时候,我就知道 LFU 肯定也是需要专门写一篇的。
于是 2021 年的第一篇技术原创文章,我就选题了 LFU。
产生了这篇文章:
哎,这让人抠脑壳的 LFU。
写这篇文章的时候,我想起之前看 Dubbo 的版本,好像是提到了一下 LFU。
于是我翻到了 2.7.7 版本的发布内容:
果然是支持了 LFU 缓存策略,于是翻出了提交的代码记录:
虽然他的实现逻辑没有问题,Test 类也跑过去了。
但是毫不夸张的说,我看了一眼这个提交记录,就发现了这里势必是有问题的。
他仅仅是把 LFU 缓存策略集合到了 Dubbo 代码中,但是却没有给使用者提供使用的入口。
因为这里是基于 SPI 实现的,他没有在对应的配置文件中加入配置。
这个问题非常容易验证,我们可以看一下。
其源码的位置是:org.apache.dubbo.common.utils.LFUCache
源码里面告诉我这样配置一下就可以使用 LFU 的缓存策略:
但是,当我这样配置,发起调用之后,是这样的:
可以看到当前请求的缓存策略确实是 lfu。
但是会抛出一个错误:
No such extension org.apache.dubbo.cache.CacheFactory by name lfu
没有 lfu 这个策略。
这不是玩我吗?
再看一下具体的原因:
在 org.apache.dubbo.common.extension.ExtensionLoader#getExtensionClasses
处只获取到了 4 个缓存策略,并没有我们想要的 LFU。
所以,在这里抛出了异常:
为什么没有找到我们想要的 LFU 呢?
那就得看你熟不熟悉 SPI 了。
在 SPI 文件中,确实没有 LFU 的配置:
所以,这是个 Bug,而这个 Bug 的解决方案,就是在 SPI 文件里面加上一行 LFU 的配置即可。
经过上面的分析,其实你也发现了,这个并不是一个有什么技术含量的提交。
更多的是运气成分。
只是由于对于 Dubbo 框架有些许的了解,所以对于这个地方,我发现问题、定位问题、解决问题的速度非常的快。
这是运气带不给我的东西。
这需要日复一日的潜入到框架中去,去感受它的脉络,梳理它的结构,学习它的思想。
这是需要时间去沉淀和学习的东西。
注意,我说的是“潜入”,而非是流于表面的。
什么是流于表面的呢?
比如,如果你之前没有用过 Dubbo 框架,但你又想去了解,学习它。
于是你看到了我的这篇或者其他的和 Dubbo 相关的公众号文章,企图从这些文章中入手。
记住鲁迅先生的话:
亦或者是你在搜索框里面,输入 “Dubbo”,然后漫无目的的看了起来。
哪怕你买了一本 Dubbo 相关的书或者看了 Dubbo 相关的系列视频,进行系统的学习。
我觉得,只要没有自己亲手去做,都属于流于表面。
而自己动手的第一步,就是搭建 Demo,从 Demo 入手。
到后面高阶一点的就是你了解到了这个框架的前世今生,能在几个大版本之间进行横向对比,知道为什么升级、怎么升级、升级之后是怎么样的。
再之后,能细致到某一个大的模块的演变是怎样的,历史上出现过哪些 Bug,是怎么去修复的。在那个版本之后进行了修复,是稳定的。
再举个例子吧。
另外一个 bug回到最开始的地方,我为什么会在写 LFU 的时候联想到 Dubbo 呢?
因为在 2.7.7 这个版本发布的时候,我就关注到了它。
而当时关注到它的原因并不是 LFU ,而是新增了一种负载均衡策略:
于是我把之前的文章进行了汇总,写下了这篇文章:
吐血输出:2万字长文带你细细盘点五种负载均衡策略。
而其中一致性哈希负载均衡策略,我在实践的时候也发现了一个 bug。
其实这个 bug 也是一个必现的 bug,为什么没有被爆出来的原因,我想是因为当前的版本使用的人不多,而使用一致性哈希负载均衡策略的就更少了,甚至没有。
这个 bug 具体是这样的:
https://github.com/apache/dub...
我已经知道了在一致性哈希算法中的这行代码就是导致 bug 的原因:
System.identityHashCode(invokers)
甚至我也知道了,这行代码导致 bug 的原因是 invokers 这个集合的地址变了。
这个集合里面,放的就是服务提供者列表。
集合里面的服务者列表其实并没有变化,只是每次都用了一个新的 list 来装这些服务提供者。
而为什么每次都用一个新的 list 来装,我也找到了:
问题就出在 TagRouter 中:
org.apache.dubbo.rpc.cluster.router.tag.TagRouter#filterInvoker
基本上到这里,也就明确原因了。
但是我前面说了,更高一级的是了解这个框架的前世今生。
问题出在 TagRouter,那么这个 TagRouter 怎么来的呢?
如果了解 Dubbo 2.7.x 版本新特性的朋友可能知道,标签路由是 Dubbo2.7 引入的新功能。
巧就巧在我还真的清楚这个地方的来龙去脉。
因为我的第一篇技术文章就是写的 Dubbo 2.7 新特性,当时进行了一个了解。
没想到一年多以后,竟然还呼应上了。
而这个 bug,其实也是一行代码就能修复;
而我当时为什么没有去修复呢?
因为最开始找到这个 bug 的时候,我想到的解决方案是写个工具类。
思路也是只关心 List 里面的元素,而不关心 List 这个容器,但是实现方式比较复杂,改动点较多,还需要写一个工具类。
当时就没动手,想着先提个 issue 放着,有时间了再弄。
结果,没想到 issue 放上去的当天就有人回复并了一个我没有想到的解决方案:
看到这个回复的时候,我才一下回过神来,原来一行代码就能代替我写的工具类了啊。
而对于其中涉及到的知识点,我是知道的。
我反思了一下自己为什么没有想到这个方案。
其实就是对于已知道的知识点,掌握不够深刻导致的,没有达到融会贯通的地步。
知其然,也知其所以然,可惜在需要使用的场景稍稍一变的情况下,就想不起来了。
知道知识点,但是该用的时候却记不起来,这种情况其实挺常见的,那怎么解决呢?
于是我写下了这篇文章:
够强!一行代码就修复了我提的Dubbo的Bug。
这篇文章就是我的解决方案,记录下来嘛。
就像高中的时候人手一本的错题本,做错的题,不会的题都抄下来嘛。没事的时候翻一翻,总有下次碰到的时候。再次碰到时,就是“一雪前耻”的机会。
写过但没有发现的 bug我之前还写过一样的一篇文章:
Dubbo 2.7.5在线程模型上的优化
当时这个版本推出之后,我就赶紧去研究了一下对应部分的源码,然后写下这篇自称为全网第一篇解析 Dubbo 2.7.5 里程碑版本中的改进点之一:客户端线程模型优化的文章。
但是前两天我看提交记录的时候,发现了这样的一个提交:
并找到了对应的 issue:
https://github.com/apache/dubbo/issues/7054
根据这个 issue,我去看了一下对应的源码,确实是存在他描述的问题。
于是我就在想,我当时写文章的时候也是深入到源码里面了呀,为什么没有发现这样的问题呢?
我想原因还是在于自己当时思考的深度不够,仅仅是搭建了一个非常简陋的 Demo,而且把心思聚焦到了前后版本差异对比上。
只是摸到了一个大概的样子,被源码牵着走了,并没有跳出源码的包围,带着质疑的眼光去审视它。
所以,对于这种比较深层次的、一环扣一环的问题,自己还是流于表面了一些。
怎么看源码前面举了三个例子,一个是发现并解决了 bug,一个是仅发现未解决的 bug,一个是有 bug 但没有发现。
前两个 bug 都有一个共性,在简单的 Demo 下就是必现的,只要跑到了对应的地方,就会出现和预期不符的情况,比较容易发现。
最后一个 bug 隐藏的比较深入一点,也许你触发了,但是程序自愈了。
当有一天我能发现并解决这样的 bug 时,我就不会说这是运气了。
但是发现这些 bug 的前提是得动手搭建 Demo 呀。
你不看源码,只是看网上的文章,是永远发现不了问题的,也是潜入不进去的。
分享一下我看源码的方法吧。
我们知道开源框架的设计和理念大多是非常优秀的,但是源码里面的细枝末节特别的多,一不小心就容易在源码里面迷失,直接就是一波劝退。
所以,对于初读源码的同学,首先要做的就是把核心流程梳理出来,边梳理边画图,要多画图,别怕麻烦。
对于几处关键的源码,一定要写上自己的备注。因为你知道的,当时也许你对这个地方为什么这样写门清,但是隔段时间再回来看,就摸不着头脑了。这个时候,备注就显的非常重要了。
对于看不明白的地方,打断点,疯狂的调试,反复的调试。
等待主流程摸清楚之后,再去进入到源码的细节部分。
举个简单的例子,比如你看 Dubbo 源码,先摸清楚它一次请求大概的调用链路之后,再去细致了解其中负载均衡的部分。
然后,就是多复习,多巩固了。
你发现没有,我说的这些其实你也知道,或者其他人也是这样说的。
为什么你看的时候就老是看不进去呢?不得要领呢?
是的,我开始也是这样的。但是,无它,唯反复练习尔。
共勉之。
最后说一句才疏学浅,难免会有纰漏,如果你发现了错误的地方,可以在后台提出来,我对其加以修改。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。