技術(shù)Leader教你看源碼的本質(zhì)
前面我說(shuō)過(guò)技術(shù) leader 的幾個(gè)特質(zhì), 今天還想跟大家分享下,作為技術(shù) leader ,還要懂得研究和引入技術(shù),引入的 前提一定是要 Hold 住。怎么才叫 hold 住呢?就是能精通使用它,能夠深入了解它的架構(gòu)、原理,能夠剖析它的核心源代碼。
以研究 Nacos 為例,這次我分享下研究技術(shù)的方法,授之以漁,希望大家有所收獲,當(dāng)然也歡迎留言共同討論更好的技巧。
01 官方文檔,搭建demo使用
很多人喜歡買書看,看別人的博客,其實(shí)都是吃剩飯,別人也是看了官方文檔寫的。一名合格的技術(shù)人員, 盡量從源頭看,看官方的文檔,原汁原味的,耐心點(diǎn)一點(diǎn)點(diǎn)看。
Nacos 的官方文檔,怎么看這個(gè)過(guò)程我就不講了,基本上就是按目錄過(guò)一遍,然后根據(jù)官方例子搭建起來(lái),知道它的基本功能使用。
重點(diǎn)看看里面的架構(gòu)設(shè)計(jì)、模型概念。
02 了解功能設(shè)計(jì)主線,確定研究主線,高維度抽象功能模型
看完官方文檔,基本會(huì)用后,要確定深入研究的主線。 Nacos 不僅僅包含了服務(wù)管理功能,還包含了配置管理,元數(shù)據(jù)管理??吹竭@里其實(shí)也能明白為什么 Nacos 未來(lái)會(huì)成為注冊(cè)中心的趨勢(shì),因?yàn)樗瑫r(shí)包含了微服務(wù)的兩個(gè)套件:注冊(cè)中心、配置中心,用了它能少部署一個(gè)配置中心。下圖來(lái)自官方文檔:

圖片來(lái)源: nac os 官方文檔
這篇文章我們研究的主線是注冊(cè)中心,所以只研究它如何實(shí)現(xiàn)注冊(cè)中心的。 這個(gè)時(shí)候,我們要高維度看,注冊(cè)中心需要哪些功能?這些功能,是任何注冊(cè)中心都需要實(shí)現(xiàn)的功能,要把這些掌握清楚。顯然,注冊(cè)中心通用的功能模型包含:

1. 服務(wù)注冊(cè)
2. 服務(wù)心跳保活
3. 服務(wù)下線(正常下線、異常下線)
4. 服務(wù)發(fā)現(xiàn)
基本上實(shí)現(xiàn)上面四點(diǎn),一個(gè)單體的注冊(cè)中心就實(shí)現(xiàn)了。然后如果考慮分布式,還要設(shè)計(jì)它如何實(shí)現(xiàn) CP/AP 模式。
03 下載源代碼,提取精華
很多人看源碼,學(xué)源碼,往往都是看了一個(gè)寂寞,為了寂寞而寂寞。 到底要看什么?
1. 源碼看什么?
看源碼,要看作者怎么架構(gòu)、怎么設(shè)計(jì)、怎么實(shí)現(xiàn),并思考為什么要這么實(shí)現(xiàn),通過(guò)源碼看到了它里面的精髓,才算真看了源碼。不然就是看了個(gè)西瓜,吃了就沒(méi)了,就是個(gè)吃瓜群眾。相反,看源代碼 ,提煉模型、原理、機(jī)制、設(shè)計(jì)模式、并發(fā)經(jīng)驗(yàn)、網(wǎng)絡(luò)經(jīng)驗(yàn)、 OS 存儲(chǔ)機(jī)制等 ,那你才算真看了源碼,吸收了它的營(yíng)養(yǎng)。
2.源代碼怎么看呢?
拋開(kāi)技術(shù)積累和經(jīng)驗(yàn)因素外,方法也是很重要的一個(gè)部分。很多看源代碼都沒(méi)有經(jīng)驗(yàn),看到源代碼復(fù)雜,代碼又多,一看就懵逼,也不知道從哪里看起。
我先分享 3 個(gè)經(jīng)驗(yàn):
( 1 )找源頭,就是啟動(dòng)的地方, 這個(gè)一般從腳本里看可以到,大部分中間件都是封裝了啟動(dòng)腳本的,你就從這個(gè)啟動(dòng)腳本里找啟動(dòng)類,讓源碼能跑起來(lái),后續(xù)可以 debug 驗(yàn)證。
( 2 )只看主線代碼。 就是我們上面提煉的功能模型。那些日志、統(tǒng)計(jì)分析、異常分支、非主線分支第一次都不要看。
( 3 )先靜態(tài)看源碼, 不要?jiǎng)討B(tài) debug ,因?yàn)?nbsp;debug ,很容易陷入細(xì)節(jié),陷入各種分支,幾繞幾繞就懵逼了,然后就放棄了。靜態(tài)看源代碼,就是不斷鍛煉自己,讓自己只看主線代碼,那種明顯是分支的直接跳過(guò)不看,這樣快速的過(guò)主線。 實(shí)在有疑惑了,然后 debug 驗(yàn)證下。
我們來(lái)看看 Nacos 的源碼,版本是 1.4.2 ,分析下我是怎么看的。
( 1 )服務(wù)注冊(cè)如何實(shí)現(xiàn)的?如何確保高并發(fā)?
客戶端啟動(dòng)的時(shí)候,會(huì)通過(guò) http 請(qǐng)求發(fā)送注冊(cè)請(qǐng)求,請(qǐng)求鏈接采用 restful 模式。 服務(wù)端接受到注冊(cè)請(qǐng)求后,會(huì)把請(qǐng)求參數(shù)封裝放到一個(gè)阻塞隊(duì)列里,然后基于一個(gè)線程不斷的獲取這個(gè)阻塞隊(duì)列的信息,放入到注冊(cè)表中。
可以看到高并發(fā)設(shè)計(jì)的一個(gè)關(guān)鍵點(diǎn):異步。 這里還可以對(duì)比延伸, zookeeper 如何實(shí)現(xiàn)的? Eureka 如何實(shí)現(xiàn)的?這些實(shí)現(xiàn)之間有什么優(yōu)劣?它們能否做到高并發(fā)?是否也是異步? 這些就留給讀者探索了。
(2)服務(wù)注冊(cè)表是如何設(shè)計(jì)的?為什么這么設(shè)計(jì)?以及怎么防止多節(jié)點(diǎn)的讀寫并發(fā)沖突?
Nacos 支持 CP 和 AP 模式,如果不懂 CP 和 AP 的自己百度了,這種簡(jiǎn)單的概念我就不科普了。
①AP 模式下,是基于內(nèi)存存儲(chǔ)的,底層其實(shí)是一個(gè)雙重的 map 結(jié)構(gòu)。 CP 模式下,數(shù)據(jù)是存儲(chǔ)到文件的。這里我們主要還是研究 AP 模式。因?yàn)榇蠖鄶?shù)場(chǎng)景下,我們注冊(cè)中心更適合 AP 模式。

看到這個(gè) map 結(jié)構(gòu),有沒(méi)有思考過(guò)為什么這么設(shè)計(jì)? namespace 的目的是? group 的目的是什么? 如果有一定 Devops 經(jīng)驗(yàn)的同學(xué)知道,我們一個(gè)項(xiàng)目環(huán)境往往可能有多套,比如開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、預(yù)發(fā)布環(huán)境、線上環(huán)境等。如果每一套環(huán)境都部署一個(gè)注冊(cè)中心,是不是很麻煩。所以這里 namespace 的目的,就是可以用同一套注冊(cè)中心,基于 namespace 來(lái)隔離這些不同的環(huán)境。
那么 group 的目的是什么呢?如果我們用過(guò) dubbo 就知道這個(gè)概念了,對(duì)服務(wù)進(jìn)行分組。有時(shí)候我們一個(gè)服務(wù)剛開(kāi)始是一個(gè)大服務(wù),但隨著業(yè)務(wù)擴(kuò)展,有時(shí)候需要拆成幾個(gè)小服務(wù),這樣就可以設(shè)置為一個(gè) group。
這些都是基于 可擴(kuò)展性 來(lái)考慮設(shè)計(jì)的。我們看看官方文檔的數(shù)據(jù)模型:

圖片來(lái)源: nac os 官方文檔
② 怎么防止讀寫沖突呢?
核心點(diǎn):讀寫分離,采用了寫時(shí)復(fù)制模式,提升了高并發(fā)。 就是寫的時(shí)候,拷貝一份舊的實(shí)例,對(duì)這份拷貝數(shù)據(jù)修改,修改完后,再?gòu)?fù)制過(guò)去,讀直接讀舊實(shí)例。
讀寫分離這種模式,避免了加鎖沖突,提升了高并發(fā)能力。讀過(guò) Eureka 源碼的了解,它的實(shí)現(xiàn)是基于多級(jí)緩存來(lái)實(shí)現(xiàn)的,然后緩存之間同步數(shù)據(jù)。時(shí)效性顯然沒(méi)有 Nacos 的好。
這里還要思考一個(gè)點(diǎn),這里復(fù)制,復(fù)制的是什么?如果寫時(shí)復(fù)制,把所有的數(shù)據(jù)都復(fù)制,顯然內(nèi)存吃不消的。這里研究下官網(wǎng)的服務(wù)模型,服務(wù)下面封裝的是一個(gè)個(gè)集群,集群下面是實(shí)例。
為什么有集群這個(gè)概念呢? 如果公司規(guī)模大一點(diǎn)的同學(xué)會(huì)知道,為了容災(zāi)高可用,一個(gè)服務(wù),可能是多機(jī)房部署的。比如一個(gè)服務(wù)可能在亦莊機(jī)房部署一個(gè)集群,兆維機(jī)房下也有一個(gè)集群。這里可以看到 nacos 模型設(shè)計(jì)的是非常巧妙的,基本上很多點(diǎn)都考慮到。
圖片來(lái)源: nac os 官方文檔
我們看源代碼也可以驗(yàn)證,可以看到 Service 下面,封裝了一個(gè) clusterMap。

而 cluster 下面又封裝了具體的實(shí)例集合,畫橫線的部分。

所以,這里的寫時(shí)復(fù)制,它復(fù)制的是這個(gè)實(shí)例所屬的集群結(jié)構(gòu),我把核心代碼截圖出來(lái),
先復(fù)制舊的實(shí)例,放到一個(gè) oldmap 里面。

對(duì)舊的 map 做一系列運(yùn)算操作,比如下線一個(gè)實(shí)例 , 然后把結(jié)果放到 ips 。

最后把新的服務(wù)實(shí)例集合賦值回去。

可以看到這里面有很多技巧,這些都可以學(xué)習(xí),以后自己設(shè)計(jì)中間件或者寫代碼的時(shí)候,都是可以直接用的。
(3)服務(wù)心跳是如何保活的?
客戶端每 5s 發(fā)送心跳給服務(wù)端,通過(guò) http 請(qǐng)求調(diào)用發(fā)送給服務(wù)端。 服務(wù)端開(kāi)啟健康檢查任務(wù),每隔 5s 檢查一次,如果發(fā)現(xiàn)超過(guò) 15s 沒(méi)有收到心跳,設(shè)置健康狀態(tài)為 false. 如果超過(guò) 30s 沒(méi)有收到心跳,直接剔除實(shí)例。

這里我們想一個(gè)問(wèn)題,服務(wù)端開(kāi)啟健康檢查任務(wù),如果集群模式下,每個(gè)服務(wù)端都要判斷嗎?這個(gè)會(huì)不會(huì)很耗性能?

我們看到健康檢查任務(wù)里有這樣一段代碼 , 它會(huì)根據(jù)服務(wù)名稱通過(guò) hash 運(yùn)算后對(duì)機(jī)器結(jié)點(diǎn)數(shù)取模,判斷是否要執(zhí)行健康檢查代碼。也就是說(shuō),集群模式下,不管啟動(dòng)了多個(gè)服務(wù)實(shí)例,任何一個(gè)服務(wù),正常情況下只有一個(gè)結(jié)點(diǎn)來(lái)執(zhí)行健康檢查代碼。但可能以為時(shí)效性,如果其他節(jié)點(diǎn)多執(zhí)行一次,也沒(méi)什么大影響對(duì)吧。當(dāng)然這里面還有一些細(xì)節(jié),都可以深扣,服務(wù)發(fā)現(xiàn),時(shí)效性是多大?

(4)服務(wù)是如何下線的?
超過(guò) 30s 未收到心跳,就會(huì)剔除,這個(gè)上面我們知道了,剔除調(diào)用的其實(shí)是自己的 deregister 方法:
跟進(jìn)去看一下,我們發(fā)現(xiàn)刪除方法對(duì) service 也是加了鎖的,也就是說(shuō)對(duì)同一個(gè)服務(wù)的修改,是做了防并發(fā)的。

最后刪除,本質(zhì)也是基于異步的,這個(gè)和注冊(cè)邏輯類似。

(5)客戶端如何發(fā)現(xiàn)服務(wù)的,服務(wù)修改是如何感知的?
① 客戶端先從本地緩存獲取服務(wù)實(shí)例,如果為空,則從服務(wù)端拉取。

并啟動(dòng)一個(gè)定時(shí)任務(wù),定期更新服務(wù)端最新實(shí)例信息。
② 服務(wù)端修改后,通過(guò) udp 協(xié)議推送
一方面基于 udp 推送提升了實(shí)時(shí)性,另一方面, udp 雖然可能丟包,但客戶端定時(shí)拉取可以作為兜底。這個(gè)設(shè)計(jì)真的很巧妙。
然后 Nacos 的 CP 模式,基于 raft 協(xié)議實(shí)現(xiàn)的一致性。還有它的配置中心架構(gòu)是如何設(shè)計(jì)的,限于篇幅,就不再展開(kāi)了。大家按照我的思路,去研究就好。記住看源碼,根據(jù)主線看,然后學(xué)習(xí)它的機(jī)制、原理。不要緊緊只是看個(gè)代碼。
3.提取源碼精華
看完源碼后,需要提取總結(jié)里面的精華,這里提取了部分用于舉例,大家可以根據(jù)自己的邏輯提取精華,不斷提取精華,不斷內(nèi)化成自己的經(jīng)驗(yàn),技術(shù)才能得到質(zhì)的飛躍。
|
維度 |
核心點(diǎn) |
描述 |
總結(jié) |
|
接口設(shè)計(jì) |
版本設(shè)計(jì) |
/nacos/v1/ns/instance |
設(shè)計(jì)接口的時(shí)候考慮版本設(shè)計(jì) |
|
設(shè)計(jì)模式 |
代理模式 |
DelegateConsistencyServiceImpl NamingClientProxyDelegate |
基于是否臨時(shí)節(jié)點(diǎn)選擇一致性協(xié)議具體實(shí)現(xiàn),臨時(shí)節(jié)點(diǎn)是 Distro, 持久節(jié)點(diǎn)是 raft 客戶端代理 |
|
工廠模式 |
NacosFactory |
該類統(tǒng)一提供了創(chuàng)建 ConfigService (配置中心服務(wù))、 NamingService (注冊(cè)中心服務(wù))和 NamingMaintainService (注冊(cè)中心實(shí)例操作服務(wù))的實(shí)例化方法,并且里面使用了反射機(jī)制 |
|
|
架構(gòu)設(shè)計(jì) |
可擴(kuò)展設(shè)計(jì) |
數(shù)據(jù)模型 |
命名空間支持環(huán)境隔離 服務(wù)分組 服務(wù)實(shí)例支持集群 |
|
高并發(fā)設(shè)計(jì) |
異步、讀寫分離、寫時(shí)復(fù)制、緩存機(jī)制 |
熟悉基本套路,在考慮高并發(fā)時(shí)都可以套用 |
|
|
高可用設(shè)計(jì) |
從客戶端、心跳機(jī)制、服務(wù)端多個(gè)角度確保了高可用機(jī)制 |
客戶端重試機(jī)制、客戶端本地緩存文件及故障轉(zhuǎn)移機(jī)制、服務(wù)端集群、一致性協(xié)議 (ap) |
|
|
分層架構(gòu)設(shè)計(jì) |
架構(gòu)層次非常清晰 |
整體架構(gòu)也好,服務(wù)注冊(cè)發(fā)現(xiàn)也好,架構(gòu)分層很清晰。比如服務(wù)注冊(cè)發(fā)現(xiàn): Controller ->ServiceManager->ConsistencyService |
|
|
中間件底層源碼機(jī)制 |
高并發(fā)容器 |
ArrayBlockingQueue 、 ConcurrentHashMap 、 |
大部分中間底層本質(zhì)就是高并發(fā)容器、線程池、定時(shí)任務(wù)、網(wǎng)絡(luò),剩下的就是具體業(yè)務(wù)。 |
|
線程池 |
ThreadPoolManager 線程池生命周期管理、 ThreadPoolExecutor |
||
|
定時(shí)任務(wù) |
ScheduledThreadPoolExecutor |
4.學(xué)以致用
學(xué)習(xí)完源碼,吸取精華不是目的,目的還是要學(xué)以致用。常見(jiàn)的路徑有:參加開(kāi)源社區(qū),自研中間件投入到生產(chǎn)實(shí)踐,內(nèi)部分享經(jīng)驗(yàn),外部演講分享。
學(xué)以致用才是本質(zhì)!
本文以研究Nacos為例,以實(shí)踐步驟分享研究技術(shù)的方法;對(duì)于微服務(wù)架構(gòu),后續(xù)將有其他同學(xué)分享Service Mesh,敬請(qǐng)關(guān)注!
























