這是why技術的第 91 篇原創文章
這篇文章其實並沒有什麼技術性的分享,從我的角度而言,更多是記錄和思考。
把我對於原始碼和之前寫的部分文章反哺給我的一些東西,帶來的一點點思考分享給大家。
一行原始碼
我很長時間沒開啟我的 Outlook 郵箱了。
前兩天開啟的時候發現我之前給 Dubbo 提交的 pr 居然已經被合併到 master 了:
這是第一次,我提交的 pr 被合併了。
這個 pr 是修復 LFU 快取策略在 Dubbo 中即使配置了,也不起作用的 bug。
於是我也算是為開源專案貢獻過原始碼的人了。
什麼你問我貢獻了多少程式碼?
一行,是的,就一行!
而且,說起來,這次提交真的是沒有什麼技術含量的事情。因為這是一個必現的 bug,只是很少有人用到這個功能而已。
你知道的,當一個 bug 能穩定復現的時候,其實它已經就不算是一個 bug 了。
但是我想聊聊這次提交背後的一些東西。
發現與解決
從宿命論的角度來說,當我寫下面這篇文章的第一個字的時候,這個 bug 就註定是等著我去發現並修復了:
而這篇文章我敲下第一個字的時間是 2020 年 12 月的下旬,這是我 2020 年的最後一篇技術原創文章。
當我寫 LRU 的時候,我就知道 LFU 肯定也是需要專門寫一篇的。
於是 2021 年的第一篇技術原創文章,我就選題了 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 ,而是新增了一種負載均衡策略:
於是我把之前的文章進行了彙總,寫下了這篇文章:
而其中一致性雜湊負載均衡策略,我在實踐的時候也發現了一個 bug。
其實這個 bug 也是一個必現的 bug,為什麼沒有被爆出來的原因,我想是因為當前的版本使用的人不多,而使用一致性雜湊負載均衡策略的就更少了,甚至沒有。
這個 bug 具體是這樣的:
https://github.com/apache/dubbo/issues/5429
我已經知道了在一致性雜湊演算法中的這行程式碼就是導致 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 放上去的當天就有人回覆並了一個我沒有想到的解決方案:
看到這個回覆的時候,我才一下回過神來,原來一行程式碼就能代替我寫的工具類了啊。
而對於其中涉及到的知識點,我是知道的。
我反思了一下自己為什麼沒有想到這個方案。
其實就是對於已知道的知識點,掌握不夠深刻導致的,沒有達到融會貫通的地步。
知其然,也知其所以然,可惜在需要使用的場景稍稍一變的情況下,就想不起來了。
知道知識點,但是該用的時候卻記不起來,這種情況其實挺常見的,那怎麼解決呢?
於是我寫下了這篇文章:
這篇文章就是我的解決方案,記錄下來嘛。
就像高中的時候人手一本的錯題本,做錯的題,不會的題都抄下來嘛。沒事的時候翻一翻,總有下次碰到的時候。再次碰到時,就是“一雪前恥”的機會。
寫過但沒有發現的 bug
我之前還寫過一樣的一篇文章:
當時這個版本推出之後,我就趕緊去研究了一下對應部分的原始碼,然後寫下這篇自稱為全網第一篇解析 Dubbo 2.7.5 里程碑版本中的改進點之一:客戶端執行緒模型優化的文章。
但是前兩天我看提交記錄的時候,發現了這樣的一個提交:
並找到了對應的 issue:
https://github.com/apache/dubbo/issues/7054
根據這個 issue,我去看了一下對應的原始碼,確實是存在他描述的問題。
於是我就在想,我當時寫文章的時候也是深入到原始碼裡面了呀,為什麼沒有發現這樣的問題呢?
我想原因還是在於自己當時思考的深度不夠,僅僅是搭建了一個非常簡陋的 Demo,而且把心思聚焦到了前後版本差異對比上。
只是摸到了一個大概的樣子,被原始碼牽著走了,並沒有跳出原始碼的包圍,帶著質疑的眼光去審視它。
所以,對於這種比較深層次的、一環扣一環的問題,自己還是流於表面了一些。
怎麼看原始碼
前面舉了三個例子,一個是發現並解決了 bug,一個是僅發現未解決的 bug,一個是有 bug 但沒有發現。
前兩個 bug 都有一個共性,在簡單的 Demo 下就是必現的,只要跑到了對應的地方,就會出現和預期不符的情況,比較容易發現。
最後一個 bug 隱藏的比較深入一點,也許你觸發了,但是程式自愈了。
當有一天我能發現並解決這樣的 bug 時,我就不會說這是運氣了。
但是發現這些 bug 的前提是得動手搭建 Demo 呀。
你不看原始碼,只是看網上的文章,是永遠發現不了問題的,也是潛入不進去的。
分享一下我看原始碼的方法吧。
我們知道開源框架的設計和理念大多是非常優秀的,但是原始碼裡面的細枝末節特別的多,一不小心就容易在原始碼裡面迷失,直接就是一波勸退。
所以,對於初讀原始碼的同學,首先要做的就是把核心流程梳理出來,邊梳理邊畫圖,要多畫圖,別怕麻煩。
對於幾處關鍵的原始碼,一定要寫上自己的備註。因為你知道的,當時也許你對這個地方為什麼這樣寫門清,但是隔段時間再回來看,就摸不著頭腦了。這個時候,備註就顯的非常重要了。
對於看不明白的地方,打斷點,瘋狂的除錯,反覆的除錯。
等待主流程摸清楚之後,再去進入到原始碼的細節部分。
舉個簡單的例子,比如你看 Dubbo 原始碼,先摸清楚它一次請求大概的呼叫鏈路之後,再去細緻瞭解其中負載均衡的部分。
然後,就是多複習,多鞏固了。
你發現沒有,我說的這些其實你也知道,或者其他人也是這樣說的。
為什麼你看的時候就老是看不進去呢?不得要領呢?
是的,我開始也是這樣的。但是,無它,唯反覆練習爾。
共勉之。
最後說一句
才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,可以在後臺提出來,我對其加以修改。
感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。