閱讀 Nacos 源碼一時(shí)爽,一直閱讀一直爽

大家好,我是躍哥。躍哥是某個(gè)社群的 Java 文章推薦官,有很長一段時(shí)間,躍哥都在推薦一個(gè)閱讀源碼的連載,覺得很棒。
那么有同學(xué)就會(huì)問了,躍哥,源碼閱讀難嗎?今天躍哥就借鑒好友的文章,來和你聊聊如何閱讀 Nacos 源碼,沖鴨。
前言
最近寫了一些列的Nacos源碼相關(guān)文章,很多朋友都感興趣的在問:你最近在閱讀什么源碼,如何閱讀源碼?今天這篇文章就以Nacos源碼閱讀來展開聊聊。對(duì)閱讀源碼或技術(shù)感興趣的朋友,也可以添加微信交流(微信號(hào):zhuan2quan)。
在讀這篇文章的時(shí)候呢,要看你想獲得什么了。因?yàn)檫@篇文章亦是在寫如何閱讀Nacos源碼,也是在寫如何閱讀源碼。不要被技術(shù)棧所束縛,要提煉屬于自己的方法。看你所欲,取你所需。
閱讀源碼的目的
不清楚大家為什么要閱讀源碼,就聊聊個(gè)人閱讀源碼的目的,或許可以拿來借鑒。
學(xué)習(xí)底層原理與實(shí)現(xiàn)
閱讀某一個(gè)框架的源碼,最重要的目的就是更深入的學(xué)習(xí)它的底層實(shí)現(xiàn)及原理。這里的底層實(shí)現(xiàn)和原理相對(duì)來說要宏觀一些,比如閱讀Nacos源碼我就是想知道,它是如何實(shí)現(xiàn)服務(wù)注冊(cè)、服務(wù)發(fā)現(xiàn)以及那些服務(wù)實(shí)例是如何存儲(chǔ)的。
像文章《微服務(wù)的靈魂擺渡者——Nacos,來一篇原理全攻略》便是來源于此類閱讀。你也可以像我一樣,閱讀之后繪制成流程圖、架構(gòu)圖、數(shù)據(jù)結(jié)構(gòu)圖,甚至整理成文章等幫助自己學(xué)習(xí)和理解。
學(xué)習(xí)優(yōu)秀的代碼設(shè)計(jì)
這一項(xiàng)包含的點(diǎn)就太多了,比如架構(gòu)設(shè)計(jì)、功能實(shí)現(xiàn)理念、優(yōu)秀代碼示范、設(shè)計(jì)模式、算法等等。凡是能看到的,比較優(yōu)秀的實(shí)踐,都可以學(xué)習(xí)。
以Nacos為例,簡單的一個(gè)實(shí)例注銷的入口方法,你能看到多少值得學(xué)習(xí)的內(nèi)容?

上圖是我一眼看過去,代碼給我最直觀的感受。然后就可以對(duì)照自己項(xiàng)目中的代碼,思考一下是否能夠達(dá)到這么高的標(biāo)準(zhǔn)?是否能進(jìn)行改造?
再看一個(gè)Nacos Client中的例子,在Client中調(diào)用Server的API時(shí),會(huì)涉及到重試機(jī)制和多個(gè)Server選一個(gè)進(jìn)行注冊(cè)的邏輯。看看Nacos是如何實(shí)現(xiàn)的。

暫且不說算法的優(yōu)劣,看到這里是不是感覺又學(xué)到了一種實(shí)現(xiàn)?而且你也知道了Nacos Client在調(diào)用Server時(shí)到底是怎么處理請(qǐng)求重試和異常的。有意思吧。
當(dāng)然,這個(gè)層面還有一些更深入的,比如一致性算法等很多解決方案的內(nèi)容。
學(xué)習(xí)知識(shí)點(diǎn)的運(yùn)用
這一項(xiàng)就更細(xì)碎更多了。像前面寫的《Nacos中已經(jīng)有Optional使用案例了,是時(shí)候慎重對(duì)待這一語法了》和 《Nacos源碼中為什么使用了String.intern方法?》都是在閱讀源碼時(shí)發(fā)掘的知識(shí)點(diǎn)。
這個(gè)層面有一個(gè)很好的點(diǎn)大家一定要把握住。那就是你可能看過很多文章在寫某個(gè)知識(shí)點(diǎn),而且也寫了一些簡單的實(shí)例。但如果你沒有實(shí)踐的機(jī)會(huì),或者沒在大型項(xiàng)目中運(yùn)用,看源碼中的實(shí)現(xiàn)和思考就非常有意思了。
比如Nacos中對(duì)String.intern方法的使用,就沒你想象中的那么簡單。而且深入思考一下,還會(huì)發(fā)現(xiàn)并不是每個(gè)場景都適合,只有在字符串變化不大的情況下才適合緩存到常量池中。
這里再舉一個(gè)Nacos中對(duì)常見知識(shí)點(diǎn)的運(yùn)用,看看咱們思考的維度是否一樣。ServiceManager中有下圖這么幾個(gè)成員變量:

上面的知識(shí)點(diǎn)可能你已經(jīng)背的滾瓜爛熟了,但你見過怎么用么?你見過怎么結(jié)合起來使用嗎?怎么支持分布式、高并發(fā)的場景嗎?
只要你仔細(xì)分析一下,閱讀一下源碼的實(shí)現(xiàn),你就能得到答案,也算是一次實(shí)踐。
從源碼中可學(xué)的內(nèi)容太多了,我這里就不逐一講解了,后面會(huì)逐步形成系列文章的形式把我看到的源碼中的技術(shù)和思想分享給大家。
如何閱讀源碼
有了閱讀源碼的目標(biāo),下一步就是執(zhí)行了。這里就分享一些我閱讀源碼的方法,不一定適合你,但可以參考和改進(jìn)。這里全部以Nacos為例,后續(xù)不再做特殊說明。
代碼的下載
開源項(xiàng)目可以直接拉取源代碼,Nacos的源代碼有兩個(gè)平臺(tái)可以獲取:GitHub和碼云。碼云庫作為同步,定時(shí)更新。這里采用GitHub作為源碼來源,說不定啥時(shí)候還可以貢獻(xiàn)一些代碼。
可以直接執(zhí)行g(shù)it命令拉取開源庫代碼:
git clone [email protected]:alibaba/nacos.git
但個(gè)人并不建議這樣直接拉取代碼,可以從nacos的倉庫fork到自己的GitHub賬號(hào)下。這樣既可以保持與主干的同步,又可以方便的修改一些內(nèi)容。
項(xiàng)目結(jié)構(gòu)
下載完成之后,直接通過IDEA打開項(xiàng)目,待依賴類庫引入完畢,可看到如下項(xiàng)目結(jié)構(gòu):

此時(shí)可以大概了解一下目錄結(jié)構(gòu),基本都能見名知意。
address:地址服務(wù)相關(guān);
api:naming和config的api抽取;
auth:權(quán)限控制相關(guān);
cmdb:支持對(duì)接第三方CMDB獲取CMDB數(shù)據(jù)等;
client:為客戶端代碼;
common:共用工具類;
config:Nacos配置中心的實(shí)現(xiàn);
consistency:一致性實(shí)現(xiàn);
console:Nacos控制臺(tái)相關(guān)實(shí)現(xiàn);
console-uri:控制臺(tái)UI部分實(shí)現(xiàn);
core:屬性加載,初始化,監(jiān)聽器相關(guān);
distribution:發(fā)布相關(guān);
example:示例;
istio:對(duì)istio的支持,如k8s等;
naming:Nacos的核心功能,動(dòng)態(tài)服務(wù)發(fā)現(xiàn);
項(xiàng)目的啟動(dòng)與測試
要閱讀源碼跟蹤流程,肯定要先把項(xiàng)目啟動(dòng)起來。由于Nacos是基于Spring Boot來構(gòu)建的,只需執(zhí)行對(duì)應(yīng)入口類的main方法即可。
在console項(xiàng)目中,執(zhí)行Nacos的main方法即可啟動(dòng)項(xiàng)目。Nacos默認(rèn)啟動(dòng)的是集群模式,研究代碼先啟動(dòng)單機(jī)模式即可。這里通過在main方法中添加參數(shù)指定單機(jī)模式:
public class Nacos {
public static void main(String[] args) {
// 通過環(huán)境變量的形式設(shè)置單機(jī)啟動(dòng)
System.setProperty(Constants.STANDALONE_MODE_PROPERTY_NAME, "true");
// 通過環(huán)境變量的形式設(shè)置關(guān)閉權(quán)限校驗(yàn)
System.setProperty("nacos.core.auth.enabled", "false");
SpringApplication.run(Nacos.class, args);
}
}
程序啟動(dòng)之后,在client項(xiàng)目中的單元測試(test)中可以看到NamingTest和ConfigTest類,直接執(zhí)行單元測試中的方法即可連接剛啟動(dòng)的Server進(jìn)行代碼跟蹤調(diào)試了。
此時(shí),關(guān)于不同API的操作,也可參看官方文檔的說明進(jìn)行調(diào)用驗(yàn)證了,這就不做演示了。
項(xiàng)目流程的梳理
完成了代碼的下載和啟動(dòng)之后,那么如何來梳理源代碼的業(yè)務(wù)邏輯呢?很多朋友可能會(huì)遇到一些困惑,比如看不懂,或看了就忘了,每次都得重新梳理一遍。
這個(gè)種狀況在初期階段很容易出現(xiàn),也算是正常情況。我的處理方式是,先將fork的代碼打一個(gè)分支(branch),比如我會(huì)打一個(gè)comment的分支。在這個(gè)分支上,每看到一行代碼有點(diǎn)難度的代碼或者需要備注的代碼,就會(huì)在上面添加注釋。
關(guān)于Nacos本人fork的代碼地址https://github.com/secbr/nacos,其中comment分支中在逐步添加注釋,對(duì)Nacos源碼感興趣的朋友可以看一下。

比如上圖是client請(qǐng)求服務(wù)器進(jìn)行注冊(cè)的部分代碼邏輯,我會(huì)將梳理過的路徑通過注釋的形式進(jìn)行描述。這樣每次看到就不用再次梳理了。
當(dāng)對(duì)上述邏輯看得多了,也就不會(huì)忘記了。就這樣逐個(gè)邏輯,逐個(gè)類的添加注釋,當(dāng)享受梳理流程圖或架構(gòu)圖時(shí),只用將注釋內(nèi)容進(jìn)行提煉和概況即可。
再看一下client注冊(cè)時(shí),server對(duì)應(yīng)的代碼注釋:

至此,關(guān)于閱讀源碼的核心部分已經(jīng)完事。剩下的就是逐個(gè)業(yè)務(wù)流程的梳理,逐個(gè)技術(shù)點(diǎn)的學(xué)習(xí)。如果遇到無法理解的部分,最好通過debug模式跟蹤一下,看看執(zhí)行到此處時(shí),具體的數(shù)據(jù)都是什么。
小結(jié)
個(gè)人覺得閱讀源碼是每個(gè)程序員必備的技能,也是站在巨人肩膀上提升自己的必要手段。只有看得更多,才知道什么是更好的寫法和實(shí)現(xiàn)。當(dāng)然,每個(gè)人現(xiàn)階段的能力有限,有很多技術(shù)點(diǎn)或設(shè)計(jì)思想當(dāng)前階段可能無法看到,但不要緊,你也可以拿我來做個(gè)墊背的,畢竟我是計(jì)劃寫一個(gè)源碼解析系列的。


