中文字幕 日本 在线 高清,久久精品国产99精品国,超碰人人香蕉,一区二区三区无码高清视频

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

架構(gòu)設(shè)計(jì)一直是技術(shù)人的關(guān)注熱點(diǎn),如何設(shè)計(jì)一個(gè)更優(yōu)的架構(gòu)對(duì)于實(shí)際的業(yè)務(wù)來說至關(guān)重要。本文騰訊云專家將從自身從事的一個(gè)k8s集群管理項(xiàng)目為例,重點(diǎn)剖析在項(xiàng)目開發(fā)過程中的三次架構(gòu)演進(jìn)歷程,即針對(duì)項(xiàng)目最早版本的dashboard架構(gòu)出現(xiàn)的問題,重寫了一個(gè)新的skipper架構(gòu),在此基礎(chǔ)上,繼續(xù)不斷優(yōu)化,最終得到一個(gè)較為合理架構(gòu)的經(jīng)歷。

通過本文,你將了解到架構(gòu)設(shè)計(jì)的原則、重構(gòu)的幾種模式、DDD中領(lǐng)域思想等,希望本文能為同行帶來參考。

本 文涉 及 的項(xiàng)目主要用于騰訊 云團(tuán)隊(duì) K8s 集群管理的項(xiàng)目,其核心業(yè)務(wù)包括創(chuàng)建 、 升 級(jí)、刪 除 集群 和 節(jié)點(diǎn)、集群監(jiān) 控、巡 檢等。

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

1

架構(gòu)的目標(biāo)

一個(gè)好的架構(gòu),其終極目標(biāo)應(yīng)當(dāng)是: 用最小的人力成本滿足構(gòu)建和維護(hù)該系統(tǒng)的需求 。也就是說,好的架構(gòu)目標(biāo)應(yīng)當(dāng)是降低人力成本,這里包括的不僅僅是開發(fā)成本,還有構(gòu)建運(yùn)維成本。而增加軟件可變性就是架構(gòu)達(dá)到最終目標(biāo)的核心途徑,即架構(gòu)主要是通過增加軟件的可變性來降低人力成本。

2

行為和架構(gòu)哪個(gè)更重要

一個(gè)軟件的行為固然是很重要的,因?yàn)橐粋€(gè)不能按預(yù)定行為工作的軟件是不產(chǎn)生價(jià)值的,所以某些開發(fā)者認(rèn)為能實(shí)現(xiàn)軟件行為是最重要的,根本不該關(guān)心架構(gòu),反正壞的架構(gòu)也不是實(shí)現(xiàn)不了行為,出了bug修復(fù)即可。但隨著軟件行為的改動(dòng),壞的架構(gòu)將導(dǎo)致他們自己的工作越來越難以進(jìn)行,改動(dòng)的代碼越來越大,bug越來越多,項(xiàng)目最終可能不可維護(hù)。一個(gè)軟件的架構(gòu)雖然不直接表現(xiàn)在行為上,但其最大的特點(diǎn)就是良好的可變性,即使目前行為不符合預(yù)期,也能通過低成本的改動(dòng)將行為改變到預(yù)期。

可運(yùn)行不可變軟件,最終會(huì)因?yàn)闊o法改變而導(dǎo)致行為無法迭代或者迭代慢而變成沒有價(jià)值; 可變不可運(yùn)行的軟件,可通過迭代,變成可運(yùn)行可變軟件。

所以架構(gòu)比行為重要。

3

“惡魔”小時(shí)候也很可愛

一個(gè)不太好的架構(gòu),在項(xiàng)目初期有時(shí)難以察覺,因?yàn)榇藭r(shí)項(xiàng)目模塊少,功能少,依賴關(guān)系顯而易見,一切顯得毫無惡意,甚至有點(diǎn)簡(jiǎn)潔美。隨著項(xiàng)目的增長(zhǎng),模塊增加了,開發(fā)人員變多了,架構(gòu)帶來的問題逐漸暴露了出來,混亂的層次關(guān)系,毫無章法的依賴關(guān)系,模塊權(quán)責(zé)不清等問題接踵而至。對(duì)開發(fā)人員而言,項(xiàng)目理解成本不斷增加,添加小功能都要先理清好幾個(gè)模塊的調(diào)用關(guān)系,難以測(cè)試導(dǎo)致上線后bug防不勝防,組件無法復(fù)用。項(xiàng)目逐漸長(zhǎng)成大家聞風(fēng)喪膽,避而不及的“大惡魔”。

4

識(shí)別過度設(shè)計(jì)

架構(gòu)設(shè)計(jì)是為了讓未來的修改更加容易,但是未來誰又能完全預(yù)測(cè)準(zhǔn)確呢,架構(gòu)設(shè)計(jì)或多或少有一定猜測(cè)成分在里面,但是更多的是吸取IT行業(yè)幾十年發(fā)展過程中前輩們的經(jīng)驗(yàn)以及對(duì)業(yè)務(wù)特點(diǎn)的了解所作出的符合一定邏輯的猜測(cè)。

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

5

架構(gòu)的理解成本

架構(gòu)是有一定理解成本的,甚至架構(gòu)設(shè)計(jì)之初會(huì)增加一定的系統(tǒng)理解成本,但是一個(gè)好的架構(gòu)理解成本一定不會(huì)很高,因?yàn)榧軜?gòu)的理解也是人力成本。在理解架構(gòu)設(shè)計(jì)的意圖之前,因?yàn)槠湓黾酉到y(tǒng)的理解成本而否定它的必要性是不合邏輯的。

好的架構(gòu),其關(guān)鍵意義在于降低項(xiàng)目發(fā)展過程中整體理解成本 也就是說,架構(gòu)良好的項(xiàng)目隨著業(yè)務(wù)復(fù)雜度增加,項(xiàng)目理解成本增長(zhǎng)也是緩慢的。架構(gòu)不合理的項(xiàng)目隨著業(yè)務(wù)復(fù)雜度的增加,整體理解成本可能是指數(shù)增長(zhǎng)的。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

1

拆遷者模式

根據(jù)當(dāng)前業(yè)務(wù)的需求對(duì)軟件架構(gòu)重新設(shè)計(jì),并組織單獨(dú)的團(tuán)隊(duì),重新開發(fā)一個(gè)全新的版本,一次性完全替代原有的遺留系統(tǒng)。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

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

  • 人力消耗巨大,需要一邊加新需求一邊重寫舊需求
  • 無法確保新的工程的設(shè)計(jì)比舊的好
  • 重寫過程中可能出現(xiàn)業(yè)務(wù)遺漏

2

絞殺者模式

保持原來的系統(tǒng)不變,當(dāng)需要開發(fā)新功能時(shí),重新開發(fā)一個(gè)服務(wù),實(shí)現(xiàn)新功能,通過不斷構(gòu)建新的服務(wù),逐步使遺留系統(tǒng)失效,最終替換它。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

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

  • 不希望存在多個(gè)服務(wù)共存的問題。
  • 希望共享舊工程的cicd,運(yùn)維,監(jiān)控等能力。
  • 重構(gòu)顆粒度過大,我們希望細(xì)到函數(shù)級(jí)別的重構(gòu)。

3

修繕者模式

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

1

整體架構(gòu)

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

  • mvc controller:用于接收http 請(qǐng)求,并調(diào)用service進(jìn)行業(yè)務(wù)處理
  • mvc service:核心業(yè)務(wù)邏輯全部落在這一層
  • mvc dao:db相關(guān)操作都在這一層
  • mvc models: 包含各個(gè)對(duì)象的字段,比如集群,節(jié)點(diǎn)等
  • controller模式下的各個(gè)controller:每個(gè)controller邏輯差異很大,但是都是調(diào)用service進(jìn)行對(duì)象狀態(tài)的初始化或者設(shè)置。
  • components:調(diào)用外部服務(wù)的模塊都在這里,比如調(diào)用計(jì)算資源服務(wù)創(chuàng)建虛擬機(jī),調(diào)用網(wǎng)絡(luò)資源服務(wù)設(shè)置網(wǎng)絡(luò)等等 dashboard雖然有水平分層,但是每一層內(nèi)部沒有組件的設(shè)計(jì)原則,也沒有代碼規(guī)范,每一層基本都是單一一個(gè)包,包內(nèi)代碼質(zhì)量不高,重復(fù)代碼較多。

2

具體實(shí)現(xiàn)

dashboard的工程目錄如下:

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

3

每一層一個(gè)包

這樣看來,dashboard的分層好像還挺清晰的,確實(shí),相對(duì)于沒有分層,dashboard采用mvc架構(gòu)進(jìn)行分層本身是有一定合理性的,但是在具體實(shí)施的時(shí)候,卻出現(xiàn)了很多問題,其中較為嚴(yán)重的是每一層只有一個(gè)包。

比如controller包中,所有請(qǐng)求,無論哪個(gè)業(yè)務(wù)模塊的,全部放一起,根本無法區(qū)分哪些是集群相關(guān)的,哪些是監(jiān)控相關(guān)的,哪些是節(jié)點(diǎn)相關(guān)的,哪些是網(wǎng)絡(luò)相關(guān)的。

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

其它層,如dao,甚至Component也是一個(gè)包。

4

依賴關(guān)系混亂

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

5

各層之間權(quán)責(zé)不明

dashboard雖然進(jìn)行了分層,但是各層的權(quán)責(zé)并沒有嚴(yán)格實(shí)施,導(dǎo)致mvc controller層和dao層也包含了大量業(yè)務(wù)邏輯,甚至有大量與service層重復(fù)的業(yè)務(wù)邏輯。

6

每層內(nèi)部沒有設(shè)計(jì)

dashboard只劃分了水平分層,但是對(duì)每一層內(nèi)部,以及各層之間的通信方式?jīng)]有做出規(guī)定,各層內(nèi)部可以隨意暴露公共函數(shù)。各層之間也是直接進(jìn)行函數(shù)調(diào)用。

現(xiàn)在更詳細(xì)地介紹一下在dashboard的架構(gòu)下所衍生出的具體問題,這些問題是skipper v1著重要解決的。

1

貧血模型導(dǎo)致dao層臃腫

mvc models層中的對(duì)象只有數(shù)值,沒有方法,所有對(duì)象的業(yè)務(wù)邏輯,無論輕重,都在其他層,這種模型稱為貧血模型。相對(duì)的,如果對(duì)象不僅包含數(shù)值,還包含基本的方法,例如自身生命周期設(shè)置,版本設(shè)置等等,就稱為充血模型。dashboard是貧血模型,這導(dǎo)致dao層比預(yù)期的要厚的多,因?yàn)榘舜罅繕I(yè)務(wù)邏輯,比如設(shè)置默認(rèn)字段,判斷字段是否是有效值等等,這些本應(yīng)該是對(duì)象自身才知道的業(yè)務(wù)邏輯。厚重的dao層會(huì)導(dǎo)致dao層難以通過interface進(jìn)行抽象,想換一種存儲(chǔ)簡(jiǎn)直是不可能的任務(wù)。

2

無法單測(cè)

上文提到,dashboard中依賴關(guān)系十分混亂,而且一層只有一個(gè)包,這導(dǎo)致想進(jìn)行單元測(cè)試是不可能的,因?yàn)閷?duì)一個(gè)簡(jiǎn)單的函數(shù)單測(cè),可能需要直接連db,哪怕函數(shù)里根本不查db。dashboard中各層之間是直接調(diào)用全局函數(shù)的,并沒有通過interface進(jìn)行隔離,這就導(dǎo)致想進(jìn)行單測(cè)就必須通過monkey來進(jìn)行全局函數(shù)打樁,不僅無法并發(fā)單測(cè),還對(duì)體系結(jié)構(gòu)有要求,因?yàn)閙onkey只支持amd64體系結(jié)構(gòu)。

3

模塊劃分不清

dashboard只進(jìn)行了水平分層,但是同層沒有分模塊,這導(dǎo)致:

  • 想復(fù)用模塊功能但是不知道對(duì)應(yīng)的函數(shù)是哪個(gè)
  • 添加新功能不知道應(yīng)該把代碼寫在哪

4

Controller模式能力不足

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

  • 流程無法暫停,無法取消
  • 流程參數(shù)和進(jìn)度沒地方存儲(chǔ)等

1

整體架構(gòu)

基于dashboard存在的問題設(shè)計(jì)的skipper項(xiàng)目架構(gòu)的v1版本,依然使用了mvc分層,但是針對(duì)dashboard的問題,重點(diǎn)關(guān)注了外部依賴接口化、db依賴接口化、充血模型、task異步流程、模塊劃分等。dashboard到skipper v1的架構(gòu)變動(dòng)如下圖:

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

2

外部依賴接口化

在skipper中,對(duì)外部服務(wù)的調(diào)用(component) 都用interface進(jìn)行抽象,任何模塊都不直接使用component的具體實(shí)現(xiàn),這解耦了業(yè)務(wù)邏輯和外部服務(wù),component提供fake版本用于單元測(cè)試。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

3

充血模型

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

4

DB依賴接口化

由于使用了充血模型,存儲(chǔ)層只有最基本的CRUD,加入store interface來解耦系統(tǒng)和具體存儲(chǔ)很方便,store層還提供基于gorm的具體實(shí)現(xiàn),以及fake版本的實(shí)現(xiàn)用于單元測(cè)試。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

5

異步流程

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

  • controller模式用于需要一直運(yùn)行的全局性旁路,比如節(jié)點(diǎn)狀態(tài)監(jiān)控,task執(zhí)行監(jiān)控等。
  • task模式用于復(fù)雜的一次性流程,比如升級(jí)一個(gè)節(jié)點(diǎn),升級(jí)一個(gè)集群等。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

6

service分包

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

7

可測(cè)試

由于外部服務(wù)以及db都可以用fake的了,service層的代碼是可以進(jìn)行單測(cè)的。

這里以節(jié)點(diǎn)升級(jí)功能為例,介紹為什么skipper v1相對(duì)dashboard能降低人力。

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

dashboard中開發(fā)過程: 如果該功能在dashboard中實(shí)現(xiàn),大概需要以下流程:

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

  • 考慮節(jié)點(diǎn)升級(jí)請(qǐng)求參數(shù)比較復(fù)雜,沒法存在現(xiàn)有表中,需要新建一個(gè)表用于存儲(chǔ)節(jié)點(diǎn)升級(jí)的參數(shù)和進(jìn)度。
  • 編寫對(duì)應(yīng)的models。
  • 編寫專門用于上述表的dao層代碼。
  • 編寫一個(gè)controller異步流程,要為該controller專門實(shí)現(xiàn)暫停,取消等控制機(jī)制。
  • 編寫專門的旁路進(jìn)行監(jiān)控告警。
  • service中實(shí)現(xiàn)節(jié)點(diǎn)升級(jí)核心流程。
  • 由于無法單測(cè),覺得寫得差不多了,需要等待測(cè)試環(huán)境空閑時(shí),部署到測(cè)試環(huán)境進(jìn)行調(diào)試。注意,測(cè)試環(huán)境是公共的,別人可能也需要用。

skipper中開發(fā)過程 : 如果該功能在skipper中實(shí)現(xiàn),將基于task異步流程實(shí)現(xiàn),大概需要以下流程:

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

  • 由于task框架已經(jīng)提供了參數(shù),進(jìn)度的存儲(chǔ),以及task相關(guān)的dao代碼,所以不需要?jiǎng)?chuàng)建任何新的db表。
  • 由于task已經(jīng)實(shí)現(xiàn)了統(tǒng)一的暫停,取消等任務(wù)控制機(jī)制,不需要編寫相關(guān)代碼。
  • 創(chuàng)建一個(gè)task handler,實(shí)現(xiàn)節(jié)點(diǎn)升級(jí)。
  • task有統(tǒng)一的監(jiān)控,無需重復(fù)編寫。
  • 由于skipper是可單測(cè)的,在部署到測(cè)試環(huán)境之前,通過單元測(cè)試快速調(diào)通了核心邏輯。
  • 部署到測(cè)試環(huán)境進(jìn)行集成測(cè)試,這時(shí)候bug已經(jīng)很少了。

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

1

core obj過度設(shè)計(jì)

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

在實(shí)際使用時(shí),發(fā)現(xiàn)為了對(duì)外暴露對(duì)象屬性,interface中充斥了大量的Get的Set方法,顯得很笨重,而且由于不同類型集群的差異并不體現(xiàn)在cluster對(duì)象本身,而是cluster的業(yè)務(wù)邏輯中,所以暴露Interface并沒有達(dá)到抽象集群的作用。

2

全局依賴

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

  • 各模塊DB是耦合的,無法分開存儲(chǔ),雖然目前所有模塊確實(shí)共用存儲(chǔ),但是隨著模塊的成長(zhǎng),模塊DB獨(dú)立也是有可能的。
  • component里聚合所有外部服務(wù)這使得使用任何一個(gè)外部服務(wù),就會(huì)依賴于所有外部服務(wù),使用component的地方都需要從全局獲取對(duì)應(yīng)的component,重復(fù)代碼較多。

3

模塊不內(nèi)聚

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

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

為了解決skipper v1中的問題,設(shè)計(jì)原則相關(guān)的指導(dǎo)被重新審視。雖然比較警惕過度設(shè)計(jì),也不喜歡在golang中使用過多設(shè)計(jì)模式以及層層封裝,但設(shè)計(jì)原則是所有語言通用的,因?yàn)樵O(shè)計(jì)原則只是一種思考的方向。

架構(gòu)設(shè)計(jì)原則是軟件行業(yè)幾十年發(fā)展總結(jié)出的一些具有指導(dǎo)意義的思想,雖然在實(shí)踐時(shí),很難完全遵循設(shè)計(jì)原則,但是識(shí)別其中違反原則的地方,并控制由于違反原則帶來的風(fēng)險(xiǎn)是很有必要的。

1

SRP:單一職責(zé)原則

SRP是最容易被誤解的原則,因?yàn)榇蠖鄶?shù)人看到名字,就以為該原則指的是一個(gè)模塊只做一件事,但其實(shí)不是這樣的。SRP較為經(jīng)典的描述是任何一個(gè)軟件模塊都應(yīng)該有且僅有一個(gè)原因被修改。也就是Robert在《架構(gòu)整潔之道》中描述的任何一個(gè)軟件模塊都應(yīng)該只對(duì)一類行為者負(fù)責(zé)。

這里的行為者是指一個(gè)或多個(gè)有共同需求的人。在實(shí)踐中,集群生命周期模塊和監(jiān)控模塊是不同的小團(tuán)隊(duì)在維護(hù),而skipper v1的監(jiān)控模塊想支持集群升級(jí)時(shí)更新配置,卻需要改動(dòng)集群生命周期模塊代碼,這其實(shí)就違反了SRP。

2

OCP:開閉原則

OCP是Bertrand Meyer于1988年提出的設(shè)計(jì)良好的計(jì)算機(jī)軟件應(yīng)該易于擴(kuò)展,同時(shí)抗拒修改。

OCP是進(jìn)行系統(tǒng)架構(gòu)設(shè)計(jì)的主導(dǎo)原則,其主要目的是讓系統(tǒng)易于擴(kuò)展,同時(shí)限制其每次被修改所影響的范圍。實(shí)現(xiàn)方式是通過將系統(tǒng)劃分為一系列組件,并且將這些組件間的依賴關(guān)系按層次結(jié)構(gòu)進(jìn)行組織,使得高層組件不會(huì)因底層組件被修改而受到影響。skipper v1中task模式是符合開閉原則的,因?yàn)槿绻砑右粋€(gè)新的異步流程,只要實(shí)現(xiàn)一個(gè)新的handler即可,并不需要修改task機(jī)制高層代碼。

3

LSP:里氏替換原則

1988年,Barbara Liskov在描述如何定義子類型時(shí)候?qū)懴逻@樣一段話:

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

面向?qū)ο笳Z言中有另外一種解釋:所有引用基類的地方必須能透明地使用其子類的對(duì)象。當(dāng)然,golang不是面向?qū)ο笳Z言,沒有父類,子類的概念,但是里氏原則對(duì)于interface的使用有著重要的指導(dǎo)意義,即:假設(shè)存在接口A的實(shí)現(xiàn)Aa和Ab,使用接口A的程序在傳入的具體實(shí)現(xiàn)由Aa改成Ab時(shí),行為不發(fā)生變化。

在skipper v1中,store層是符合里氏替換原則的,因?yàn)槭褂胐ao版本的實(shí)現(xiàn)和使用fake版本的實(shí)現(xiàn),store接口使用者行為是不變的。Robert在《架構(gòu)整潔之道》給出了一個(gè)著名的反面例子,即正方形長(zhǎng)方形問題。假設(shè)Class Rectangle表示長(zhǎng)方形。假設(shè)Class Square集成了Rectangle表示正方形。使用Rectangle對(duì)象的程序并不能用Square對(duì)象來替換Rectangle對(duì)象,因?yàn)镽ectangle長(zhǎng)寬可以隨意設(shè)置,但是Square卻不行。

4

ISP:接口隔離原則

ISP的定義十分直觀:客戶端不應(yīng)該依賴它不需要的接口,在skipper v1中store中定義的接口違反了ISP,因?yàn)樵摻涌诎怂心K的數(shù)據(jù)庫操作接口,基于ISP原則,應(yīng)該讓每個(gè)模塊自己擁有并維護(hù)自己?jiǎn)为?dú)的store接口。

5

DIP:依賴反轉(zhuǎn)原則

DIP主要指導(dǎo)系統(tǒng)各層的依賴關(guān)系,高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象; 抽象不應(yīng)該依賴細(xì)節(jié); 細(xì)節(jié)應(yīng)該依賴抽象。

從具體實(shí)現(xiàn)而言,如果想設(shè)計(jì)一個(gè)靈活的系統(tǒng),在源碼層次的依賴關(guān)系中,就應(yīng)該多引用抽象類型,而非具體實(shí)現(xiàn)。在具體實(shí)施時(shí),《架構(gòu)整潔之道》中給出了4點(diǎn)建議:

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

1

CCP:共同閉包原則

應(yīng)該將那些會(huì)同時(shí)修改,并且為相同目的而修改的類放在同一個(gè)組件中,而將不會(huì)同時(shí)修改,并且不會(huì)為了相同目的的修改的那些類放在不同組件中。

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

2

CRP:共同復(fù)用原則

不要強(qiáng)迫一個(gè)組件的用戶依賴他們不需要的東西。這個(gè)原則指出應(yīng)該將那些會(huì)被同時(shí)用到的代碼放在同一個(gè)組件中。dashboard的依賴關(guān)系圖顯示dashboard嚴(yán)重違反了共同復(fù)用原則。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

3

ADP:無依賴環(huán)原則

組件依賴關(guān)系圖中不應(yīng)該出現(xiàn)環(huán)。golang 編譯器實(shí)際上已經(jīng)幫助避免了循環(huán)依賴。

4

SDP:穩(wěn)定依賴原則

依 賴關(guān) 系必須要指向更穩(wěn)定的方向。這條原則指出,一個(gè)預(yù)期會(huì)經(jīng)常變更的組件不該被一個(gè)難以修改的組件所依賴,否則這個(gè)多變的組件也會(huì)變得難以被修改。這里所謂的穩(wěn)定組件,就是指那些被別的組件依賴多的組件,不穩(wěn)定的組件是那些依賴很多其他組件,但被其他組件依賴少的組件。穩(wěn)定組件需要依賴不穩(wěn)定組件時(shí)怎么辦呢?在他們中間加入一層穩(wěn)定的抽象層。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

5

SAP:穩(wěn)定抽象原則

一個(gè)組件的抽象化程度應(yīng)與其穩(wěn)定性保持一致。 SDP中提到,穩(wěn)定的組件是不易修改的,這會(huì)導(dǎo)致整個(gè)項(xiàng)目的架構(gòu)難以被修改,需要通過高度抽象這些穩(wěn)定的組件,來讓其接受修改。 前一個(gè)原則SDP指出:依賴應(yīng)該指向更加穩(wěn)定的方向。而SAP指出:越穩(wěn)定,抽象化程度應(yīng)該越高。這兩個(gè)連起來就可以得出另外一個(gè)結(jié)論: 依賴關(guān)系應(yīng)該指向更加抽象的方向。

領(lǐng)域驅(qū)動(dòng)開發(fā)是一種用于復(fù) 雜軟件的架構(gòu)設(shè)計(jì)思想,學(xué)習(xí)門檻比較高且對(duì)團(tuán)隊(duì)成員整體架構(gòu)水平要求較高,其實(shí)并不適合完全使用在skipper的開發(fā)中,只借鑒其中一部分即可。

1

水平分層

s kipper v1中依舊采用了mvc分層。 但是領(lǐng)域驅(qū)動(dòng)開發(fā),以及《架構(gòu)整潔之道》都指出,應(yīng)當(dāng)存在一個(gè)應(yīng)用層(《架構(gòu)整潔之道》中稱為use cases層)用于處理依賴多個(gè)組件的業(yè)務(wù)邏輯,各層之間依賴于接口而非實(shí)現(xiàn),且下層 不能依賴上層。 比如創(chuàng)建一個(gè)包含三個(gè)節(jié)點(diǎn)的集群,就同時(shí)需要操作集群模塊和節(jié)點(diǎn)模塊。

領(lǐng)域驅(qū)動(dòng)開發(fā)中,每個(gè)領(lǐng)域稱為Domain,每個(gè)Domain有自己的領(lǐng)域?qū)嶓w,并且是充血模型,每個(gè)領(lǐng)域的存儲(chǔ)也是內(nèi)聚在領(lǐng)域之中,綜合以上,水平分層應(yīng)當(dāng)如下。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

2

領(lǐng)域劃分與邊界

在領(lǐng)域驅(qū)動(dòng)開發(fā)中不僅進(jìn)行了水平分層,還進(jìn)行了垂直切片,將應(yīng)用層以下劃分成了不同領(lǐng)域(domain),每個(gè)領(lǐng)域責(zé)任明確且高度內(nèi)聚。 領(lǐng)域的劃分應(yīng)該滿足單一職責(zé)原則,每個(gè)領(lǐng)域應(yīng)當(dāng)只對(duì)同一類行為者負(fù)責(zé),每次系統(tǒng)的修改都應(yīng)該分析屬于哪個(gè)領(lǐng)域,如果某些領(lǐng)域總是同時(shí)被修改,他們應(yīng)當(dāng)被合并為一個(gè)領(lǐng)域。 一旦領(lǐng)域劃分后,不同領(lǐng)域之間需要制定嚴(yán)格的邊界,領(lǐng)域暴露的接口,事件,領(lǐng)域之間的依賴關(guān)系都該被嚴(yán)格把控。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

3

領(lǐng)域事件

領(lǐng)域可以定義事件并發(fā)布到事件總線總,如果對(duì)某個(gè)領(lǐng)域事件感興趣,就可以訂閱事件。 領(lǐng)域事件可以大大降低各領(lǐng)域間的耦合,且對(duì)系統(tǒng)擴(kuò)展性有巨大好處。

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

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

參考前兩文的探索,skipper v1做了一定調(diào)整。

1

整體架構(gòu)

下圖是v1到v2的轉(zhuǎn)變,其核心是加入是領(lǐng)域模型,形成高內(nèi)聚的業(yè)務(wù)領(lǐng)域組件。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

  • 將v1中的service層切成兩層,把跨多領(lǐng)域的業(yè)務(wù)邏輯上拉至application層中,讓剩下的業(yè)務(wù)邏輯包含明顯的業(yè)務(wù)邊界。
  • 再根據(jù)各個(gè)業(yè)務(wù)模塊的依賴關(guān)系緊密程度進(jìn)行重組,形成領(lǐng)域,每個(gè)領(lǐng)域只處理自己領(lǐng)域的業(yè)務(wù),每個(gè)領(lǐng)域?qū)ν獗┞兑惶譻ervice接口用于描述該領(lǐng)域?qū)ν獗┞兜哪芰ΓI(lǐng)域可以利用Event bus對(duì)外發(fā)布事件,用于通知外部領(lǐng)域內(nèi)正在發(fā)生的事。
  • 原來全局公用的存儲(chǔ)層,現(xiàn)在分散到各個(gè)領(lǐng)域自行維護(hù),不同領(lǐng)域可以采用不同的存儲(chǔ)。
  • 原來放置全局的controller和task handler,現(xiàn)在由每個(gè)領(lǐng)域自行管理,系統(tǒng)依然提供controller和task的引擎(由task領(lǐng)域負(fù)責(zé))。這使得領(lǐng)域業(yè)務(wù)邏輯更加內(nèi)聚。
  • 注意各模塊的依賴關(guān)系,盡量遵循穩(wěn)定依賴原則和穩(wěn)定抽象原則,不穩(wěn)定模塊盡量依賴于穩(wěn)定模塊,如果需要讓穩(wěn)定模塊依賴于不穩(wěn)定模塊,引入interface進(jìn)行抽象。

2

新領(lǐng)域孵化

隨著業(yè)務(wù)的發(fā)展,會(huì)有越來越多的領(lǐng)域被加入到skipper中(目前已經(jīng)出現(xiàn)"虛擬集群"領(lǐng)域)。

當(dāng)一個(gè)新的領(lǐng)域被加入到skipper中時(shí),根據(jù)上邊的架構(gòu),只需要借鑒其他領(lǐng)域的設(shè)計(jì),新建一個(gè)領(lǐng)域,并在讓領(lǐng)域負(fù)責(zé)人在此領(lǐng)域中迭代需求即可,這過程中,新領(lǐng)域可以依賴其它領(lǐng)域,監(jiān)聽其它領(lǐng)域的事件等等,對(duì)其它領(lǐng)域而言都是無感的。

3

對(duì)比領(lǐng)域成長(zhǎng)與獨(dú)立

隨 這領(lǐng)域內(nèi)業(yè)務(wù)邏輯越來越復(fù)雜,或者因?yàn)闃I(yè)務(wù)調(diào)整,存在某個(gè)領(lǐng)域獨(dú)立出項(xiàng)目的情況(目前"集群監(jiān)控"領(lǐng)域已準(zhǔn)備獨(dú)立),由于我們的領(lǐng)域是高內(nèi)聚的,領(lǐng)域獨(dú)立的難度并不大,對(duì)整個(gè)項(xiàng)目而言,也只是將剝離的領(lǐng)域從領(lǐng)域?qū)愚D(zhuǎn)移至infrastructure層,做為外部服務(wù)而已。

由于領(lǐng)域之間總是依賴于接口或者依賴于領(lǐng)域事件,當(dāng)領(lǐng)域獨(dú)立時(shí),依賴這個(gè)領(lǐng)域的業(yè)務(wù)邏輯是不需要進(jìn)行修改的。

以k8s集群管理為例,大牛教你如何設(shè)計(jì)優(yōu)秀項(xiàng)目架構(gòu)(k8s集群架構(gòu)圖)

4

微服務(wù)化

可 能隨著領(lǐng)域不斷剝離,項(xiàng)目的領(lǐng)域不斷的成為獨(dú)立的服務(wù),當(dāng)服務(wù)增多時(shí),就需要引入更加統(tǒng)一有效的運(yùn)維、監(jiān)控、部署方案,這才是項(xiàng)目微服務(wù)化最自然的方式,項(xiàng)目應(yīng)盡量是單體應(yīng)用。

5

相對(duì)v1更能降低人力

以增加集群創(chuàng)建失敗通知機(jī)制為例,目前,這個(gè) 集群創(chuàng)建成功率雖然符合SLA,但是依然不是100%的。我們希望 當(dāng)集群創(chuàng)建失敗時(shí)應(yīng)該能第一時(shí)間收到通知,而 通知本身是一個(gè)比較簡(jiǎn)單的需求。

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

skipper v2中開發(fā): 在skipper v2中開發(fā),只需要單獨(dú)創(chuàng)建一個(gè)領(lǐng)域,專門用于系統(tǒng)各種需要觸達(dá)我們的通知,然后訂閱對(duì)應(yīng)事件即可,比如該例子中,就是訂閱集群創(chuàng)建失敗事件。這種開發(fā)模式,不需要修改集群創(chuàng)建流程代碼,一切改動(dòng)都在關(guān)鍵事件通知領(lǐng)域進(jìn)行,且基于這種開發(fā)方式,就不會(huì)讓事件通知代碼散落在各個(gè)領(lǐng)域中。

架構(gòu)調(diào)是一件整需要勇氣的事情。 一旦宣布進(jìn)行項(xiàng)目架構(gòu)調(diào)整,就是宣告現(xiàn)有項(xiàng)目架構(gòu)不合理,也意味著必須將設(shè)計(jì)出比當(dāng)前優(yōu)秀的架構(gòu)。 這個(gè)過程中,架構(gòu)師可能會(huì)犯錯(cuò),需要進(jìn)行一些猜測(cè),或者和他人存在諸多觀點(diǎn)沖突,有時(shí)甚至需要有點(diǎn)“固執(zhí)”才會(huì)成功。

我們反對(duì)過度設(shè)計(jì),但是識(shí)別,或者說猜測(cè)項(xiàng)目未來符合邏輯的可能變動(dòng),將架構(gòu)設(shè)計(jì)考慮進(jìn)項(xiàng)目早期是十分有必 要的, 架構(gòu)設(shè)計(jì)和調(diào)整應(yīng)該貫穿項(xiàng)目的整個(gè)成長(zhǎng)過程。 永遠(yuǎn)記得架構(gòu)投資的是未來,不能只顧當(dāng)下。

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。

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

相關(guān)推薦