以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

架構設計一直是技術人的關注熱點,如何設計一個更優的架構對于實際的業務來說至關重要。本文騰訊云專家將從自身從事的一個k8s集群管理項目為例,重點剖析在項目開發過程中的三次架構演進歷程,即針對項目最早版本的dashboard架構出現的問題,重寫了一個新的skipper架構,在此基礎上,繼續不斷優化,最終得到一個較為合理架構的經歷。

通過本文,你將了解到架構設計的原則、重構的幾種模式、DDD中領域思想等,希望本文能為同行帶來參考。

本 文涉 及 的項目主要用于騰訊 云團隊 K8s 集群管理的項目,其核心業務包括創建 、 升 級、刪 除 集群 和 節點、集群監 控、巡 檢等。

dashboard是該項目最早的版本,主要包含API 請求處理和異步流程執行等核心功能,是團隊最早的核心模塊之一。但是隨著功能不斷增加,dashboard早期不合理的架構設計所導致的可讀性差、擴展性差,無法單測等問題逐漸暴露且愈發嚴重。為了讓dashboard的質量往更好的方向改進,團隊決定對其進行重構。考慮到直接重寫的代價和風險過大,團隊決定采用“修繕者”策略,即重創一個工程,承載dashboard新需求的實現,并逐步將舊功能遷移到新工程中,最終達到重寫dashboard的效果,skipper就是這個新工程。在遷移過程中,團隊對skipper的架構設計經過了幾次調整,逐步解決了dashboard中存在的問題,最終得到一個較為合理的架構,本文記錄了重構過程中的思考,和架構演變的過程。

1

架構的目標

一個好的架構,其終極目標應當是: 用最小的人力成本滿足構建和維護該系統的需求 。也就是說,好的架構目標應當是降低人力成本,這里包括的不僅僅是開發成本,還有構建運維成本。而增加軟件可變性就是架構達到最終目標的核心途徑,即架構主要是通過增加軟件的可變性來降低人力成本。

2

行為和架構哪個更重要

一個軟件的行為固然是很重要的,因為一個不能按預定行為工作的軟件是不產生價值的,所以某些開發者認為能實現軟件行為是最重要的,根本不該關心架構,反正壞的架構也不是實現不了行為,出了bug修復即可。但隨著軟件行為的改動,壞的架構將導致他們自己的工作越來越難以進行,改動的代碼越來越大,bug越來越多,項目最終可能不可維護。一個軟件的架構雖然不直接表現在行為上,但其最大的特點就是良好的可變性,即使目前行為不符合預期,也能通過低成本的改動將行為改變到預期。

可運行不可變軟件,最終會因為無法改變而導致行為無法迭代或者迭代慢而變成沒有價值; 可變不可運行的軟件,可通過迭代,變成可運行可變軟件。

所以架構比行為重要。

3

“惡魔”小時候也很可愛

一個不太好的架構,在項目初期有時難以察覺,因為此時項目模塊少,功能少,依賴關系顯而易見,一切顯得毫無惡意,甚至有點簡潔美。隨著項目的增長,模塊增加了,開發人員變多了,架構帶來的問題逐漸暴露了出來,混亂的層次關系,毫無章法的依賴關系,模塊權責不清等問題接踵而至。對開發人員而言,項目理解成本不斷增加,添加小功能都要先理清好幾個模塊的調用關系,難以測試導致上線后bug防不勝防,組件無法復用。項目逐漸長成大家聞風喪膽,避而不及的“大惡魔”。

4

識別過度設計

架構設計是為了讓未來的修改更加容易,但是未來誰又能完全預測準確呢,架構設計或多或少有一定猜測成分在里面,但是更多的是吸取IT行業幾十年發展過程中前輩們的經驗以及對業務特點的了解所作出的符合一定邏輯的猜測。

那什么算過度設計呢?從架構的目的是降低人力來看,就是該設計目前沒有任何強有力的邏輯能推出能在未來降低修改某種行為的人力成本,或者降低某種行為修改成本的同時,大大增加了另外一種行為的修改成本。

5

架構的理解成本

架構是有一定理解成本的,甚至架構設計之初會增加一定的系統理解成本,但是一個好的架構理解成本一定不會很高,因為架構的理解也是人力成本。在理解架構設計的意圖之前,因為其增加系統的理解成本而否定它的必要性是不合邏輯的。

好的架構,其關鍵意義在于降低項目發展過程中整體理解成本 也就是說,架構良好的項目隨著業務復雜度增加,項目理解成本增長也是緩慢的。架構不合理的項目隨著業務復雜度的增加,整體理解成本可能是指數增長的。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

1

拆遷者模式

根據當前業務的需求對軟件架構重新設計,并組織單獨的團隊,重新開發一個全新的版本,一次性完全替代原有的遺留系統。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

沒有采用拆遷者模式的原因:

  • 人力消耗巨大,需要一邊加新需求一邊重寫舊需求
  • 無法確保新的工程的設計比舊的好
  • 重寫過程中可能出現業務遺漏

2

絞殺者模式

保持原來的系統不變,當需要開發新功能時,重新開發一個服務,實現新功能,通過不斷構建新的服務,逐步使遺留系統失效,最終替換它。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

絞殺者模式還存在以下問題:

  • 不希望存在多個服務共存的問題。
  • 希望共享舊工程的cicd,運維,監控等能力。
  • 重構顆粒度過大,我們希望細到函數級別的重構。

3

修繕者模式

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

1

整體架構

dashboard核心功能分為兩大塊,一個是作為 web api server,接收http請求,另外一個是異步流程處理,用于耗時較長的功能,比如創建集群、集群升級等。dashboard整體采用mvc架構 controller模式,這里的controller模式是指通過不斷重試,最終將目標對象設置到某種目標狀態的模式,比如通過不斷重試,將創建中的集群的各部分屬性或者依賴的資源,設置到正常集群的狀態。dashboard的核心模塊如圖。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

  • mvc controller:用于接收http 請求,并調用service進行業務處理
  • mvc service:核心業務邏輯全部落在這一層
  • mvc dao:db相關操作都在這一層
  • mvc models: 包含各個對象的字段,比如集群,節點等
  • controller模式下的各個controller:每個controller邏輯差異很大,但是都是調用service進行對象狀態的初始化或者設置。
  • components:調用外部服務的模塊都在這里,比如調用計算資源服務創建虛擬機,調用網絡資源服務設置網絡等等 dashboard雖然有水平分層,但是每一層內部沒有組件的設計原則,也沒有代碼規范,每一層基本都是單一一個包,包內代碼質量不高,重復代碼較多。

2

具體實現

dashboard的工程目錄如下:

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

3

每一層一個包

這樣看來,dashboard的分層好像還挺清晰的,確實,相對于沒有分層,dashboard采用mvc架構進行分層本身是有一定合理性的,但是在具體實施的時候,卻出現了很多問題,其中較為嚴重的是每一層只有一個包。

比如controller包中,所有請求,無論哪個業務模塊的,全部放一起,根本無法區分哪些是集群相關的,哪些是監控相關的,哪些是節點相關的,哪些是網絡相關的。

如果說controller包一個文件一個請求還可以理解,那service層整個只有一個包,不分模塊,而且全是全局函數可維護性就很差了,由于核心業務邏輯全在service層,service的代碼量是所有層中最多的,隨著功能的增長,未來service將越來越臃腫。

其它層,如dao,甚至Component也是一個包。

4

依賴關系混亂

dashboard沒有關注各個模塊之間的依賴關系,只要不產生循環依賴就可以隨意依賴別的模塊,所以模塊之間依賴十分混亂。這直接導致模塊難以復用,例如component包中部分代碼依賴dao,依賴config,而dao和config又強依賴了配置文件和db。這導致如果要復用component包開發一個很簡單的工具,都需要給工具準備dashboard配置文件,甚至需要能連上db,下圖是godepgraph工具生成的component依賴圖。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

5

各層之間權責不明

dashboard雖然進行了分層,但是各層的權責并沒有嚴格實施,導致mvc controller層和dao層也包含了大量業務邏輯,甚至有大量與service層重復的業務邏輯。

6

每層內部沒有設計

dashboard只劃分了水平分層,但是對每一層內部,以及各層之間的通信方式沒有做出規定,各層內部可以隨意暴露公共函數。各層之間也是直接進行函數調用。

現在更詳細地介紹一下在dashboard的架構下所衍生出的具體問題,這些問題是skipper v1著重要解決的。

1

貧血模型導致dao層臃腫

mvc models層中的對象只有數值,沒有方法,所有對象的業務邏輯,無論輕重,都在其他層,這種模型稱為貧血模型。相對的,如果對象不僅包含數值,還包含基本的方法,例如自身生命周期設置,版本設置等等,就稱為充血模型。dashboard是貧血模型,這導致dao層比預期的要厚的多,因為包含了大量業務邏輯,比如設置默認字段,判斷字段是否是有效值等等,這些本應該是對象自身才知道的業務邏輯。厚重的dao層會導致dao層難以通過interface進行抽象,想換一種存儲簡直是不可能的任務。

2

無法單測

上文提到,dashboard中依賴關系十分混亂,而且一層只有一個包,這導致想進行單元測試是不可能的,因為對一個簡單的函數單測,可能需要直接連db,哪怕函數里根本不查db。dashboard中各層之間是直接調用全局函數的,并沒有通過interface進行隔離,這就導致想進行單測就必須通過monkey來進行全局函數打樁,不僅無法并發單測,還對體系結構有要求,因為monkey只支持amd64體系結構。

3

模塊劃分不清

dashboard只進行了水平分層,但是同層沒有分模塊,這導致:

  • 想復用模塊功能但是不知道對應的函數是哪個
  • 添加新功能不知道應該把代碼寫在哪

4

Controller模式能力不足

dashboard使用controller模式進行異步操作,但是controller模式在持久化和異步流程控制上能力較為薄弱。

  • 流程無法暫停,無法取消
  • 流程參數和進度沒地方存儲等

1

整體架構

基于dashboard存在的問題設計的skipper項目架構的v1版本,依然使用了mvc分層,但是針對dashboard的問題,重點關注了外部依賴接口化、db依賴接口化、充血模型、task異步流程、模塊劃分等。dashboard到skipper v1的架構變動如下圖:

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

2

外部依賴接口化

在skipper中,對外部服務的調用(component) 都用interface進行抽象,任何模塊都不直接使用component的具體實現,這解耦了業務邏輯和外部服務,component提供fake版本用于單元測試。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

3

充血模型

在skipper中,models層只會被core obj層和store interface所引用,所有其它模塊都直接使用包含充血模型的core obj層。在core obj中,每個對象都是充血模型的,其不僅包含一個或多個對象數據,還包含一些業務方法,比如將對象設置為升級狀態,比如將對象生命周期改為deleting等等,也就是說,原來處于dao中的業務邏輯被上升到core obj中,使得dao 層薄到只有最基本的CRUD操作,這對后面DB依賴接口化有巨大幫助。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

4

DB依賴接口化

由于使用了充血模型,存儲層只有最基本的CRUD,加入store interface來解耦系統和具體存儲很方便,store層還提供基于gorm的具體實現,以及fake版本的實現用于單元測試。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

5

異步流程

為了解決controller模式存在的問題,skipper開發一個task異步流程執行框架,用于執行一次性的異步流程,但依舊保留controller模式的存在,其中task controller是task異步流程框架的引擎:

  • controller模式用于需要一直運行的全局性旁路,比如節點狀態監控,task執行監控等。
  • task模式用于復雜的一次性流程,比如升級一個節點,升級一個集群等。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

6

service分包

skipper中也有service層,和dashboard不同的是,skipper的service會根據業務模塊進行分包,比如一個包專門處理集群升級,一個包專門處理監控組件,一個包專門處理巡檢等。skipper的service層依舊使用了全局函數,沒有進行封裝(后續將提到,這是skipper v1版本存在的一個問題)。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

7

可測試

由于外部服務以及db都可以用fake的了,service層的代碼是可以進行單測的。

這里以節點升級功能為例,介紹為什么skipper v1相對dashboard能降低人力。

功能簡介:節點升級功能是指將一批k8s節點上的組件版本從低版本升級至高版本,這是一個比較耗時的流程,所以不能在同步請求中直接完成,需要異步執行,且需要展示升級進度。由于節點升級是高危操作,一批節點升級過程中,需要支持用戶隨時暫停,取消升級。

dashboard中開發過程: 如果該功能在dashboard中實現,大概需要以下流程:

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

  • 考慮節點升級請求參數比較復雜,沒法存在現有表中,需要新建一個表用于存儲節點升級的參數和進度。
  • 編寫對應的models。
  • 編寫專門用于上述表的dao層代碼。
  • 編寫一個controller異步流程,要為該controller專門實現暫停,取消等控制機制。
  • 編寫專門的旁路進行監控告警。
  • service中實現節點升級核心流程。
  • 由于無法單測,覺得寫得差不多了,需要等待測試環境空閑時,部署到測試環境進行調試。注意,測試環境是公共的,別人可能也需要用。

skipper中開發過程 : 如果該功能在skipper中實現,將基于task異步流程實現,大概需要以下流程:

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

  • 由于task框架已經提供了參數,進度的存儲,以及task相關的dao代碼,所以不需要創建任何新的db表。
  • 由于task已經實現了統一的暫停,取消等任務控制機制,不需要編寫相關代碼。
  • 創建一個task handler,實現節點升級。
  • task有統一的監控,無需重復編寫。
  • 由于skipper是可單測的,在部署到測試環境之前,通過單元測試快速調通了核心邏輯。
  • 部署到測試環境進行集成測試,這時候bug已經很少了。

雖然skipper v1解決了dashboard存在的很多問題,但是其自身依然有很多不足,在新需求開發和舊代碼遷移過程中不斷暴露出來。

1

core obj過度設計

skipper為了采用充血模型,在core obj中進行了封裝,例如cluster對象,隱藏了dashboard中的多個models結構體,隱藏了某些字段實際是Json字段的,對外暴露出帶有方法的cluster對象,設計時候考慮了多種集群存在的可能性,所以整個對象對外不是一個實體,而是暴露了一個interface。

在實際使用時,發現為了對外暴露對象屬性,interface中充斥了大量的Get的Set方法,顯得很笨重,而且由于不同類型集群的差異并不體現在cluster對象本身,而是cluster的業務邏輯中,所以暴露Interface并沒有達到抽象集群的作用。

2

全局依賴

skipper v1認為像store, component中的外部組件都是單例的,所以使用了全局依賴。使用全局依賴使得整個工程用的是一個DB,這樣的方式至少存在以下幾個弊端:

  • 各模塊DB是耦合的,無法分開存儲,雖然目前所有模塊確實共用存儲,但是隨著模塊的成長,模塊DB獨立也是有可能的。
  • component里聚合所有外部服務這使得使用任何一個外部服務,就會依賴于所有外部服務,使用component的地方都需要從全局獲取對應的component,重復代碼較多。

3

模塊不內聚

雖然skipper v1中,各層基本都按功能進行分包了,但是模塊并不內聚,一些包之間依賴關系很明顯,應該屬于一個模塊的不同部分,并且由于只使用了水平分層,模塊的內部各層代碼分散到項目各層中并和其他模塊對應層代碼耦合在一起。

針對某一模塊,由于service層依舊使用了全局函數,除非有文檔說明,否則無法知道該模塊對其它模塊暴露了哪些API,其它模塊甚至可以直接讀寫該模塊的DB。例如集群監控模塊,當1.16版本的集群升級時,需要更新對應集群的監控配置,skipper v1中的實現是在集群升級代碼中顯示調用更新監控配置的函數,這就使得集群監控開發人員必須理解集群升級的代碼并知道在哪里調用更新監控配置的函數,這使得集群生命周期模塊和監控模塊是耦合的。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

為了解決skipper v1中的問題,設計原則相關的指導被重新審視。雖然比較警惕過度設計,也不喜歡在golang中使用過多設計模式以及層層封裝,但設計原則是所有語言通用的,因為設計原則只是一種思考的方向。

架構設計原則是軟件行業幾十年發展總結出的一些具有指導意義的思想,雖然在實踐時,很難完全遵循設計原則,但是識別其中違反原則的地方,并控制由于違反原則帶來的風險是很有必要的。

1

SRP:單一職責原則

SRP是最容易被誤解的原則,因為大多數人看到名字,就以為該原則指的是一個模塊只做一件事,但其實不是這樣的。SRP較為經典的描述是任何一個軟件模塊都應該有且僅有一個原因被修改。也就是Robert在《架構整潔之道》中描述的任何一個軟件模塊都應該只對一類行為者負責。

這里的行為者是指一個或多個有共同需求的人。在實踐中,集群生命周期模塊和監控模塊是不同的小團隊在維護,而skipper v1的監控模塊想支持集群升級時更新配置,卻需要改動集群生命周期模塊代碼,這其實就違反了SRP。

2

OCP:開閉原則

OCP是Bertrand Meyer于1988年提出的設計良好的計算機軟件應該易于擴展,同時抗拒修改。

OCP是進行系統架構設計的主導原則,其主要目的是讓系統易于擴展,同時限制其每次被修改所影響的范圍。實現方式是通過將系統劃分為一系列組件,并且將這些組件間的依賴關系按層次結構進行組織,使得高層組件不會因底層組件被修改而受到影響。skipper v1中task模式是符合開閉原則的,因為如果要添加一個新的異步流程,只要實現一個新的handler即可,并不需要修改task機制高層代碼。

3

LSP:里氏替換原則

1988年,Barbara Liskov在描述如何定義子類型時候寫下這樣一段話:

這里需要一種可替換性,如果對每一個類型為 T1的對象 o1,都有類型為 T2 的對象o2,使得以 T1定義的所有程序 P 在所有的對象 o1 都代換成 o2 時,程序 P 的行為沒有發生變化,那么類型 T2 是類型 T1 的子類型。

面向對象語言中有另外一種解釋:所有引用基類的地方必須能透明地使用其子類的對象。當然,golang不是面向對象語言,沒有父類,子類的概念,但是里氏原則對于interface的使用有著重要的指導意義,即:假設存在接口A的實現Aa和Ab,使用接口A的程序在傳入的具體實現由Aa改成Ab時,行為不發生變化。

在skipper v1中,store層是符合里氏替換原則的,因為使用dao版本的實現和使用fake版本的實現,store接口使用者行為是不變的。Robert在《架構整潔之道》給出了一個著名的反面例子,即正方形長方形問題。假設Class Rectangle表示長方形。假設Class Square集成了Rectangle表示正方形。使用Rectangle對象的程序并不能用Square對象來替換Rectangle對象,因為Rectangle長寬可以隨意設置,但是Square卻不行。

4

ISP:接口隔離原則

ISP的定義十分直觀:客戶端不應該依賴它不需要的接口,在skipper v1中store中定義的接口違反了ISP,因為該接口包含了所有模塊的數據庫操作接口,基于ISP原則,應該讓每個模塊自己擁有并維護自己單獨的store接口。

5

DIP:依賴反轉原則

DIP主要指導系統各層的依賴關系,高層模塊不應該依賴低層模塊,二者都應該依賴其抽象; 抽象不應該依賴細節; 細節應該依賴抽象。

從具體實現而言,如果想設計一個靈活的系統,在源碼層次的依賴關系中,就應該多引用抽象類型,而非具體實現。在具體實施時,《架構整潔之道》中給出了4點建議:

  • 應該避免在代碼中寫入與任何具體實現相關的名字,或者是其他容易變動的事物名字。
  • 應在代碼中多使用抽象接口,盡量避免使用那些多變的具體實現類。
  • 不要在具體實現類上創建衍生類,golang語言天生就符合這一點
  • 不要覆蓋包含具體實現的函數,即別重寫,在skipper v1的task模式中違反了這一條,因為task模式為了減少代碼重復,所有task handler都需要內嵌default handler,并重寫其覺得需要修改的函數。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

1

CCP:共同閉包原則

應該將那些會同時修改,并且為相同目的而修改的類放在同一個組件中,而將不會同時修改,并且不會為了相同目的的修改的那些類放在不同組件中。

CCP是SRP有很多相似的地方,統一描述他們的思想就是將由于相同原因而修改,并且需要同時修改的東西放在一起。將由于不同原因而修改,并且不同時修改的東西放在一起。

2

CRP:共同復用原則

不要強迫一個組件的用戶依賴他們不需要的東西。這個原則指出應該將那些會被同時用到的代碼放在同一個組件中。dashboard的依賴關系圖顯示dashboard嚴重違反了共同復用原則。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

3

ADP:無依賴環原則

組件依賴關系圖中不應該出現環。golang 編譯器實際上已經幫助避免了循環依賴。

4

SDP:穩定依賴原則

依 賴關 系必須要指向更穩定的方向。這條原則指出,一個預期會經常變更的組件不該被一個難以修改的組件所依賴,否則這個多變的組件也會變得難以被修改。這里所謂的穩定組件,就是指那些被別的組件依賴多的組件,不穩定的組件是那些依賴很多其他組件,但被其他組件依賴少的組件。穩定組件需要依賴不穩定組件時怎么辦呢?在他們中間加入一層穩定的抽象層。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

5

SAP:穩定抽象原則

一個組件的抽象化程度應與其穩定性保持一致。 SDP中提到,穩定的組件是不易修改的,這會導致整個項目的架構難以被修改,需要通過高度抽象這些穩定的組件,來讓其接受修改。 前一個原則SDP指出:依賴應該指向更加穩定的方向。而SAP指出:越穩定,抽象化程度應該越高。這兩個連起來就可以得出另外一個結論: 依賴關系應該指向更加抽象的方向。

領域驅動開發是一種用于復 雜軟件的架構設計思想,學習門檻比較高且對團隊成員整體架構水平要求較高,其實并不適合完全使用在skipper的開發中,只借鑒其中一部分即可。

1

水平分層

s kipper v1中依舊采用了mvc分層。 但是領域驅動開發,以及《架構整潔之道》都指出,應當存在一個應用層(《架構整潔之道》中稱為use cases層)用于處理依賴多個組件的業務邏輯,各層之間依賴于接口而非實現,且下層 不能依賴上層。 比如創建一個包含三個節點的集群,就同時需要操作集群模塊和節點模塊。

領域驅動開發中,每個領域稱為Domain,每個Domain有自己的領域實體,并且是充血模型,每個領域的存儲也是內聚在領域之中,綜合以上,水平分層應當如下。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

2

領域劃分與邊界

在領域驅動開發中不僅進行了水平分層,還進行了垂直切片,將應用層以下劃分成了不同領域(domain),每個領域責任明確且高度內聚。 領域的劃分應該滿足單一職責原則,每個領域應當只對同一類行為者負責,每次系統的修改都應該分析屬于哪個領域,如果某些領域總是同時被修改,他們應當被合并為一個領域。 一旦領域劃分后,不同領域之間需要制定嚴格的邊界,領域暴露的接口,事件,領域之間的依賴關系都該被嚴格把控。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

3

領域事件

領域可以定義事件并發布到事件總線總,如果對某個領域事件感興趣,就可以訂閱事件。 領域事件可以大大降低各領域間的耦合,且對系統擴展性有巨大好處。

例如在skipper v1中,如果劃分出了集群監控領域和集群生命周期管理領域,當有一天監控領域決定去掉集群升級過程中對監控配置文件的修改,需要在集群升級代碼里找調用監控配置文件升級的地方。 而如果采用了領域事件,則只需要讓集群生命周期模塊發布升級完成事件,并讓監控模塊訂閱或者取消訂閱事件進而做出配置文件修改邏輯即可。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

參考前兩文的探索,skipper v1做了一定調整。

1

整體架構

下圖是v1到v2的轉變,其核心是加入是領域模型,形成高內聚的業務領域組件。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

  • 將v1中的service層切成兩層,把跨多領域的業務邏輯上拉至application層中,讓剩下的業務邏輯包含明顯的業務邊界。
  • 再根據各個業務模塊的依賴關系緊密程度進行重組,形成領域,每個領域只處理自己領域的業務,每個領域對外暴露一套service接口用于描述該領域對外暴露的能力,領域可以利用Event bus對外發布事件,用于通知外部領域內正在發生的事。
  • 原來全局公用的存儲層,現在分散到各個領域自行維護,不同領域可以采用不同的存儲。
  • 原來放置全局的controller和task handler,現在由每個領域自行管理,系統依然提供controller和task的引擎(由task領域負責)。這使得領域業務邏輯更加內聚。
  • 注意各模塊的依賴關系,盡量遵循穩定依賴原則和穩定抽象原則,不穩定模塊盡量依賴于穩定模塊,如果需要讓穩定模塊依賴于不穩定模塊,引入interface進行抽象。

2

新領域孵化

隨著業務的發展,會有越來越多的領域被加入到skipper中(目前已經出現"虛擬集群"領域)。

當一個新的領域被加入到skipper中時,根據上邊的架構,只需要借鑒其他領域的設計,新建一個領域,并在讓領域負責人在此領域中迭代需求即可,這過程中,新領域可以依賴其它領域,監聽其它領域的事件等等,對其它領域而言都是無感的。

3

對比領域成長與獨立

隨 這領域內業務邏輯越來越復雜,或者因為業務調整,存在某個領域獨立出項目的情況(目前"集群監控"領域已準備獨立),由于我們的領域是高內聚的,領域獨立的難度并不大,對整個項目而言,也只是將剝離的領域從領域層轉移至infrastructure層,做為外部服務而已。

由于領域之間總是依賴于接口或者依賴于領域事件,當領域獨立時,依賴這個領域的業務邏輯是不需要進行修改的。

以k8s集群管理為例,大牛教你如何設計優秀項目架構(k8s集群架構圖)

4

微服務化

可 能隨著領域不斷剝離,項目的領域不斷的成為獨立的服務,當服務增多時,就需要引入更加統一有效的運維、監控、部署方案,這才是項目微服務化最自然的方式,項目應盡量是單體應用。

5

相對v1更能降低人力

以增加集群創建失敗通知機制為例,目前,這個 集群創建成功率雖然符合SLA,但是依然不是100%的。我們希望 當集群創建失敗時應該能第一時間收到通知,而 通知本身是一個比較簡單的需求。

skipper v1中開發: 在skipper v1中開發,最大問題是開發人員必須知道集群創建失敗的具體位置,這只有集群創建流程的開發人員才知道,為了加入通知功能,新人不得不去請教集群創建流程的開發人員,并且需要修改集群創建流程,由于修改了集群創建流程,還需要走測試,雖然通知功能的代碼不多,但是由于要修改集群創建流程,導致了人力成本的增加。

skipper v2中開發: 在skipper v2中開發,只需要單獨創建一個領域,專門用于系統各種需要觸達我們的通知,然后訂閱對應事件即可,比如該例子中,就是訂閱集群創建失敗事件。這種開發模式,不需要修改集群創建流程代碼,一切改動都在關鍵事件通知領域進行,且基于這種開發方式,就不會讓事件通知代碼散落在各個領域中。

架構調是一件整需要勇氣的事情。 一旦宣布進行項目架構調整,就是宣告現有項目架構不合理,也意味著必須將設計出比當前優秀的架構。 這個過程中,架構師可能會犯錯,需要進行一些猜測,或者和他人存在諸多觀點沖突,有時甚至需要有點“固執”才會成功。

我們反對過度設計,但是識別,或者說猜測項目未來符合邏輯的可能變動,將架構設計考慮進項目早期是十分有必 要的, 架構設計和調整應該貫穿項目的整個成長過程。 永遠記得架構投資的是未來,不能只顧當下。

版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。

(0)
上一篇 2022年6月17日 上午9:06
下一篇 2022年6月17日 上午9:18

相關推薦

在线A级毛片无码免费真人| 国产在线精品香蕉麻豆| 7777久久亚洲中文字幕蜜桃| 天堂在线www| jizz日本在线观看| 国模吧一区二区三区精品视频| a级片免费在线观看| 国产网红主播无码精品| 91国在线视频| 国产欧美日韩在线观看一区二区 | 91精品福利视频| 国产精品vⅰdeoXXXX国产| 高清无码一区二区在线观看吞精 | 日本香蕉一区二区三区| 久久精品国产99国产精品| 抱着cao才爽| 中文字幕在线观看一区二区三区| 好硬啊进得太深了h动态图120秒| 一个人看的日本www| 国内精品久久久久久久影视| 88av视频在线| 国产成人AV免费观看| 美女扒开尿口让男人捅爽| 又湿又紧又大又爽a视频国产| 理论片高清免费理论片| 亚洲综合区图片小说区| 欧洲成人午夜精品无码区久久| 亚洲av无码一区二区三区国产| 日批视频在线看| 东京热一精品无码av| 在私人影院里嗯啊h| 57pao成人国产永久免费视频| 国产成人在线免费观看| 绿巨人在线视频免费观看完整版| 农村妇女色又黄一级毛片不卡| 欧美黑人巨大白妞出浆| 亚洲国产成AV人天堂无码| 日本成本人三级在线观看2018| 中文字幕在线视频在线看| 在线视频免费国产成人| 668溜溜吧成人影院|