怎麼看源代碼?

今天想跟大家分享下,作爲技術Leader,要懂得研究和引入技術,引入的前提一定是要Hold住。怎麼才叫Hold住呢?就是能精通使用它,能夠深入瞭解它的架構、原理,能夠剖析它的核心源代碼。

以研究Nacos爲例,這次我分享下研究技術的方法,授之以漁,希望大家有所收穫,當然也歡迎留言共同討論更好的技巧。


 1 
官方文檔,搭建demo使用

 

很多人喜歡買書看,看別人的博客,其實都是喫剩飯,別人也是看了官方文檔寫的。一名合格的技術人員,儘量從源頭看,看官方的文檔,原汁原味的,耐心點一點點看。

Nacos的官方文檔,怎麼看這個過程我就不講了,基本上就是按目錄過一遍,然後根據官方例子搭建起來,知道它的基本功能使用。

重點看看裏面的架構設計、模型概念。


 2 
瞭解功能設計主線,確定研究主線,高維度抽象功能模型

 

看完官方文檔,基本會用後,要確定深入研究的主線。Nacos不僅僅包含了服務管理功能,還包含了配置管理,元數據管理。看到這裏其實也能明白爲什麼Nacos未來會成爲註冊中心的趨勢,因爲它同時包含了微服務的兩個套件:註冊中心、配置中心,用了它能少部署一個配置中心。下圖來自官方文檔:

圖片

圖片來源:Nacos官方文檔

這篇文章我們研究的主線是註冊中心,所以只研究它如何實現註冊中心的。這個時候,我們要高維度看,註冊中心需要哪些功能?這些功能,是任何註冊中心都需要實現的功能,要把這些掌握清楚。顯然,註冊中心通用的功能模型包含:

圖片

 

  1. 服務註冊

  2. 服務心跳保活

  3. 服務下線(正常下線、異常下線)

  4. 服務發現

基本上實現上面四點,一個單體的註冊中心就實現了。然後如果考慮分佈式,還要設計它如何實現CP/AP模式。


 3 
下載源代碼,提取精華

 

很多人看源碼,學源碼,往往都是看了一個寂寞,爲了寂寞而寂寞。到底要看什麼?

源碼看什麼?

看源碼,要看作者怎麼架構、怎麼設計、怎麼實現,並思考爲什麼要這麼實現,通過源碼看到了它裏面的精髓,纔算真看了源碼。不然就是看了個西瓜,吃了就沒了,就是個喫瓜羣衆。相反,看源代碼,提煉模型、原理、機制、設計模式、併發經驗、網絡經驗、OS存儲機制等,那你纔算真看了源碼,吸收了它的營養。

源代碼怎麼看呢?

拋開技術積累和經驗因素外,方法也是很重要的一個部分。很多看源代碼都沒有經驗,看到源代碼複雜,代碼又多,一看就懵逼,也不知道從哪裏看起。

我先分享3個經驗:

  1. 找源頭,就是啓動的地方。這個一般從腳本里看可以到,大部分中間件都是封裝了啓動腳本的,你就從這個啓動腳本里找啓動類,讓源碼能跑起來,後續可以debug驗證。

  2. 只看主線代碼,就是我們上面提煉的功能模型。那些日誌、統計分析、異常分支、非主線分支第一次都不要看。

  3. 先靜態看源碼,不要動態debug,因爲debug,很容易陷入細節,陷入各種分支,幾繞幾繞就懵逼了,然後就放棄了。靜態看源代碼,就是不斷鍛鍊自己,讓自己只看主線代碼,那種明顯是分支的直接跳過不看,這樣快速的過主線。實在有疑惑了,然後debug驗證下。

我們來看看Nacos的源碼,版本是1.4.2,分析下我是怎麼看的。

1、服務註冊如何實現的?如何確保高併發?

客戶端啓動的時候,會通過http請求發送註冊請求,請求鏈接採用RESTful模式。服務端接受到註冊請求後,會把請求參數封裝放到一個阻塞隊列裏,然後基於一個線程不斷的獲取這個阻塞隊列的信息,放入到註冊表中。

可以看到高併發設計的一個關鍵點:異步。這裏還可以對比延伸,ZooKeeper如何實現的?Eureka如何實現的?這些實現之間有什麼優劣?它們能否做到高併發?是否也是異步?這些就留給讀者探索了。

2、服務註冊表是如何設計的?爲什麼這麼設計?以及怎麼防止多節點的讀寫併發衝突?

Nacos支持CP和AP模式,如果不懂CP和AP的自己百度了,這種簡單的概念我就不科普了。

①AP模式下,是基於內存存儲的,底層其實是一個雙重的map結構。CP模式下,數據是存儲到文件的。這裏我們主要還是研究AP模式。因爲大多數場景下,我們註冊中心更適合AP模式。

圖片

 

看到這個map結構,有沒有思考過爲什麼這麼設計?namespace的目的是?group的目的是什麼?如果有一定DevOps經驗的同學知道,我們一個項目環境往往可能有多套,比如開發環境、測試環境、預發佈環境、線上環境等。如果每一套環境都部署一個註冊中心,是不是很麻煩。所以這裏namespace的目的,就是可以用同一套註冊中心,基於namespace來隔離這些不同的環境。

那麼group的目的是什麼呢?如果我們用過Dubbo就知道這個概念了,對服務進行分組。有時候我們一個服務剛開始是一個大服務,但隨着業務擴展,有時候需要拆成幾個小服務,這樣就可以設置爲一個group。

這些都是基於可擴展性來考慮設計的。我們看看官方文檔的數據模型:

圖片

圖片來源:Nacos官方文檔

②怎麼防止讀寫衝突呢?

核心點:讀寫分離,採用了寫時複製模式,提升了高併發。就是寫的時候,拷貝一份舊的實例,對這份拷貝數據修改,修改完後,再複製過去,讀直接讀舊實例。

讀寫分離這種模式,避免了加鎖衝突,提升了高併發能力。讀過Eureka源碼的瞭解,它的實現是基於多級緩存來實現的,然後緩存之間同步數據。時效性顯然沒有Nacos的好。

這裏還要思考一個點,這裏複製,複製的是什麼?如果寫時複製,把所有的數據都複製,顯然內存喫不消的。這裏研究下官網的服務模型,服務下面封裝的是一個個集羣,集羣下面是實例。

爲什麼有集羣這個概念呢?如果公司規模大一點的同學會知道,爲了容災高可用,一個服務,可能是多機房部署的。比如一個服務可能在亦莊機房部署一個集羣,兆維機房下也有一個集羣。這裏可以看到Nacos模型設計的是非常巧妙的,基本上很多點都考慮到。

圖片

 

我們看源代碼也可以驗證,可以看到Service下面,封裝了一個clusterMap。

圖片

 

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

圖片

 

所以,這裏的寫時複製,它複製的是這個實例所屬的集羣結構,我把核心代碼截圖出來。

先複製舊的實例,放到一個oldMap裏面。

圖片

 

對舊的Map做一系列運算操作,比如下線一個實例,然後把結果放到IPS。

圖片

最後把新的服務實例集合賦值回去。

圖片

 

可以看到這裏面有很多技巧,這些都可以學習,以後自己設計中間件或者寫代碼的時候,都是可以直接用的。

3、服務心跳是如何保活的?

客戶端每5s發送心跳給服務端,通過http請求調用發送給服務端。服務端開啓健康檢查任務,每隔5s檢查一次,如果發現超過15s沒有收到心跳,設置健康狀態爲false,如果超過30s沒有收到心跳,直接剔除實例。

圖片

 

這裏我們想一個問題,服務端開啓健康檢查任務,如果集羣模式下,每個服務端都要判斷嗎?這個會不會很耗性能?

圖片

 

我們看到健康檢查任務裏有這樣一段代碼,它會根據服務名稱通過hash運算後對機器結點數取模,判斷是否要執行健康檢查代碼。也就是說,集羣模式下,不管啓動了多個服務實例,任何一個服務,正常情況下只有一個結點來執行健康檢查代碼。但可能以爲時效性,如果其他節點多執行一次,也沒什麼大影響對吧。當然這裏面還有一些細節,都可以深扣,服務發現,時效性是多大?

圖片

 

4、服務是如何下線的?

超過30s未收到心跳,就會剔除,這個上面我們知道了,剔除調用的其實是自己的deregister方法:

圖片

 

跟進去看一下,我們發現刪除方法對Service也是加了鎖的,也就是說對同一個服務的修改,是做了防併發的。

圖片

 

最後刪除,本質也是基於異步的,這個和註冊邏輯類似。

圖片

 

5、戶端如何發現服務的,服務修改是如何感知的?

① 客戶端先從本地緩存獲取服務實例,如果爲空,則從服務端拉取。

圖片

 

並啓動一個定時任務,定期更新服務端最新實例信息。

圖片

② 服務端修改後,通過UDP協議推送

一方面基於UDP推送提升了實時性,另一方面,UDP雖然可能丟包,但客戶端定時拉取可以作爲兜底。這個設計真的很巧妙。

然後Nacos的CP模式,基於Raft協議實現的一致性。還有它的配置中心架構是如何設計的,限於篇幅,就不再展開了。大家按照我的思路,去研究就好。記住看源碼,根據主線看,然後學習它的機制、原理。不要緊緊只是看個代碼。

提取源碼精華

看完源碼後,需要提取總結裏面的精華,這裏提取了部分用於舉例,大家可以根據自己的邏輯提取精華,不斷提取精華,不斷內化成自己的經驗,技術才能得到質的飛躍。

維度
核心點
描述
總結
接口設計
版本設計
/nacos/v1/ns/instance
設計接口的時候考慮版本設計
設計模式
代理模式
DelegateConsistencyServiceImplNamingClientProxyDelegate
基於是否臨時節點選擇一致性協議具體實現,臨時節點是Distro,持久節點是raft客戶端代理。
代理模式
NacosFactory
該類統一提供了創建ConfigService(配置中心服務)、NamingService(註冊中心服務)和NamingMaintainService(註冊中心實例操作服務)的實例化方法,並且裏面使用了反射機制。
架構設計
可擴展設計
數據模型

命名空間支持環境隔離;

服務分組;

服務實例支持集羣。

高併發設計
異步、讀寫分離、寫時複製、緩存機制。
熟悉基本套路,在考慮高併發時都可以套用。
高可用設計
從客戶端、心跳機制、服務端多個角度確保了高可用機制。
客戶端重試機制、客戶端本地緩存文件及故障轉移機制、服務端集羣、一致性協議(AP)。
分層架構設計
架構層次非常清晰。
整體架構也好,服務註冊發現也好,架構分層很清晰。比如服務註冊發現:Controller ->ServiceManager->ConsistencyService
中間件底層源碼機制
高併發容器
ArrayBlockingQueue、ConcurrentHashMap
大部分中間底層本質就是高併發容器、線程池、定時任務、網絡,剩下的就是具體業務。
線程池
ThreadPoolManager線程池生命週期管理、ThreadPoolExecutor
定時任務
ScheduledThreadPoolExecutor

學以致用

學習完源碼,吸取精華不是目的,目的還是要學以致用。常見的路徑有:參加開源社區,自研中間件投入到生產實踐,內部分享經驗,外部演講分享。

學以致用纔是本質!

本文源自公衆號八戒技術團隊,分佈式實驗室已獲完整授權。

推薦閱讀:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章