ZStack源碼剖析:如何在百萬行代碼中快速迭代

本文首發於泊浮目的專欄: https://segmentfault.com/blog...

前言

ZStack是下一代開源的雲計算IaaS(基礎架構即服務)軟件。它主要面向的是將來的智能數據中心,經過提供的API來管理包括計算、存儲和網絡在內的數據中心的各類資源。跟OpenStack相比,ZStack具備易用、穩定、靈活、超高性能等特色。其單管理節點能夠管理1萬臺物理機規模集羣,多個管理節點構建的集羣能夠作到使用一個數據庫、一套消息總線管理10萬臺物理機、數百萬個虛擬機節點、併發處理數萬個API。java

如下是ZStackV2.2的服務架構圖
官網地址: http://www.zstack.io/

核心開源引擎ZStack GitHub:https://github.com/zstackio/z...git

ZStack-Utility GitHub:https://github.com/zstackio/z...github

閱讀源碼若是不想使用IDE,建議配合https://github.com/buunguyen/...web

本文將對核心引擎-ZStack的源碼進行剖析。在ZStack官網上咱們能夠看到其每一個版本的發佈都是攜帶了許多的新特性。在筆者看來,可以快速迭代的緣由首先是來自於每位工程師的辛勤付出。除此以外,因其還有些軟件工程領域中沉澱下來的最佳實踐:算法

  • 良好的架構設計
  • 覆蓋較爲全面的測試
  • 恰當好處的使用設計模式

良好的架構設計

異步架構

Iaas的核心應該作的是管控層,而不是數據層。故ZStack僅僅也是作出一些「決策」而已——在設計系統的時候,應不考慮在這些決策的執行上消耗大量的資源。在面對大量請求或者「決策」的時候,若是使用多線程來處理阻塞式IO模型時會遇到一些問題:數據庫

  • 阻塞模型的吞吐量受到線程池大小的限制;
  • 建立並使用許多線程會耗費額外的時間用於上下文切換,影響系統性能。

而非阻塞、異步的消息驅動系統能夠只運行少許的線程,而且不阻塞這些線程,只在須要計算資源時才使用它們。這大大提升了系統的響應速度,而且可以更高效地利用系統資源。segmentfault

故,ZStack採用了異步架構,分別由三個部分組成:設計模式

  • 異步消息
  • 異步方法
  • 異步HTTP 請求

若是在系統中的一部分採用異步設計,是不行的。這樣仍是會由於同步而無法享受異步帶來的「福利」。故此整個系統都得采用異步架構。網絡

  • 相對的,開發者們在編寫異步代碼的時候得格外當心。
  • 在系統設計中,異步調用能夠減小系統在IO上出現瓶頸的可能性。
擴展連接 : ZStack--可拓展性的祕密武器1:異步架構

無狀態服務

ZStack中,每個服務都是獨立存在的。爲了方便的管理更多的物理機,ZStack推薦採用集羣部署MN。但這樣就會遇到一個問題,不一樣MN下面有着不一樣的幾個服務存在,在這裏咱們設其爲X個服務。在10個MN部署的狀況下,可能就是10X個服務。那麼在一個資源須要操做時,我須要發送向對應的MN。那麼如何找到那個MN呢?最直觀的想法就是在各個MN中保存相應的「服務表」,這便是一種狀態。那麼在分佈式系統中,採用有狀態的服務絕對不是一個好的選擇,它會嚴重影響系統的擴展性。ZStack巧妙的採用了一致性哈希算法+MQ解決了這個問題。多線程

  • 這在系統設計中實爲是一種使用一致性hash技術的負載均衡
擴展連接: ZStack—可拓展性祕密武器2:無狀態的服務

無鎖架構

解決併發的問題不必定要用顯式的鎖,也能夠對同一資源作操做的任務作成隊列使其串行執行。

注意:併發 != 並行
擴展連接: ZStack--可拓展性祕密武器3:無鎖架構

鬆耦合架構

項目模塊化

Intellij中打開ZStack的代碼,會發現大多數目錄底下都會有一個pom.xml文件,ZStack採用了模塊化項目。模塊化的好處在工程實踐中不言而喻的,好比:

  • 能夠在不影響整個系統的狀況下替換某個模塊
  • 開發者只要專心的在本身的模塊中工做便可
  • 減小系統耦合度,提升內聚,減小資源循環依賴,加強系統框架設計
  • ...

下面來看一下ZStack中代碼的結構:

代碼結構

截圖於2017.9.22
名稱 簡介
build 用於Java部分的編譯、打包、部署等
conf 配置文件及SQL文件的放置;Spring Service配置存放;持久化文件配置
core 核心模塊。實現系統的核心功能——包括數據庫、消息總線、工做流實現等等
coregroovy ZStack的最新測試採用了Groovy,這裏是對測試庫作的支持
header 消息以及Entity的定義
plugin 顧名思義。其中很多組件都以插件化開發,提供較高的靈活性
sdk 測試庫使用的SDK
simulator 對於測試庫支持的又一模塊,主要用戶simulator agent的行爲
testlib 測試庫
test 測試模塊
工具類 工具包。目前僅僅支持了doc生成
utils 代碼中使用的工具類
其餘 功能實現模塊

經過MQ來解耦合

ZStack中,每一個功能實現模塊都會被稱爲服務——一個獨立的服務。各個服務之間的通訊由MQ來承擔。這就像是傳統的CSE,C和E是不耦合的,經過S來交互。一樣的,一個服務須要向另外一個服務發起調用,只需往消息總線發送消息,並指定這個服務ID(Service ID)便可。若是某個服務的代碼須要大量重構或者作成微服務,只要提供相同的服務並註冊到MQ上就能夠了。這就是事件驅動架構(Event Driven Architecture)的一種典型實現。

CSE:Controller、Service、Entity。注:稱做Domain或者Model都是不專業的。Domain是一個領域對象,每每咱們再作傳統Java軟件web開發中,這些Domain都是貧血模型,是沒有行爲的,或是沒有足夠的領域模型的行爲的,因此,以這個理論來說,這些Domain都應該是一個普通的entity對象,並不是領域對象,因此請把包名改成:com.xxx.entity。

舉個簡單明瞭的例子。若是每一個對象的行爲都是經過消息來決定的(好比一個方法須要message獲得回覆後才能do something...),那麼這個對象僅僅對消息總線產生了依賴。在測試中,將會發揮巨大的威力——咱們只須要改變handle message處的行爲,就可使一個對象行爲作出相應的變化。這樣可使mock的單位變得更小,同時也能夠變得更加靈活。

試想若是經過函數調用:

//方法a中的代碼
xxService.method1();
xx2Service.method2();

在測試中該如何解耦?但若是經過MQ——即一個消息來調用xxService.method1(),那麼方法a對xxService就沒有了直接的依賴。

使用Spring

不瞭解Spring的人能夠看: 看起來很長但仍是有用的Spring學習筆記

在代碼中,每當咱們New出一個對象時,這個模塊便對這個對象產生了依賴。當咱們須要測試的時候就不得不去Mock它。當依賴的對象or Field 有成千上萬個的時候,這就是一場災難了。代碼變得愈發不可測,坑就越多,開發者在擴展or維護項目的時候就會愈發的乏力。這就像是咱們以前提到的MQ,服務1->MQ->服務2,因爲中間隔了一個MQ,因而服務1和服務2沒有必然的關係。一樣的,從對象1->調用->對象2對象1->調用->Spring提供的IOC容器->對象2,這樣使對象與對象之間也沒有了直接調用關係,對象1只要知道它要調用的對象實現了其須要的Interface就是能夠調用的。

除了Autowired的正確使用姿式。在ZStack中,還有一類頗有意思的代碼,通常稱之爲xxxExtensionPoint。其本質就是定義一個接口,而後其實現類做爲Bean經過XML註冊到IOC中。在須要使用的時候,經過Spring獲取到全部實現該接口的對象,調用其函數。這樣就會使代碼變得很是的靈活。

例如,ZStack分爲多個版本——開源版、企業版、混合雲版等。若是一個服務在不一樣版本中的處理邏輯須要稍許不一樣,那麼就能夠在開源版的代碼中註冊一個接口,在另外一個版本的服務中實現該接口。這樣也不會影響到開源版的原有邏輯。從模塊上看咱們代碼的是鬆耦合而且沒法直接調用的,可是在內存中,倒是能夠調用獲得的。

覆蓋較爲全面的測試

ZStack

  • 開發者Fix每個Bug都是須要補充相應的Case;
  • 每個Feature在進去以前更會由開發工程師與QA工程師同時制定測試場景並Cover;
  • 每個PR(pull request)進去以前都會經過PR系統跑過全部的Case,以防止Bug的Regression;

因爲ZStack源碼作到了必定的解耦合(上述提到)與無狀態,使得集成測試得以進行。

其首席架構師Frank.Zhang曾說過:咱們開發者在寫代碼的時候每每就應該考慮該怎麼寫測試了。

想了解ZStack的測試框架,能夠看: ZStack WiKi :管理節點基於模擬器的Integration Test框架

恰當好處的使用設計模式

ZStack中,設計模式有較爲良好的實踐。筆者有機會將會在以後的系列文章分析其中的典型案例以及在代碼中使用極其頻繁的核心工具。

相關文章
相關標籤/搜索