怎么看源代碼?


今天想跟大家分享下,作為技術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

學以致用

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

學以致用才是本質!

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

推薦閱讀:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM