在系統(tǒng)從 0 到 1 的階段,為了讓系統(tǒng)快速上線锰提,我們通常是不考慮分層的曙痘。 但是隨著業(yè)務(wù)的越來越復(fù)雜,大量的代碼糾纏在一起立肘,會(huì)出現(xiàn)邏輯不清晰边坤、各模塊相互依賴、代碼擴(kuò)展性差谅年、改動(dòng)一處就牽一發(fā)而動(dòng)全身等問題茧痒。
這時(shí),對(duì)系統(tǒng)進(jìn)行分層就會(huì)被提上日程融蹂,那么我們要如何對(duì)架構(gòu)進(jìn)行分層旺订?架構(gòu)分層和高并發(fā)架構(gòu)設(shè)計(jì)又有什么關(guān)系呢?本節(jié)課超燃,我將帶你尋找答案区拳。
什么是分層架構(gòu)
軟件架構(gòu)分層在軟件工程中是一種常見的設(shè)計(jì)方式,它是將整體系統(tǒng)拆分成 N 個(gè)層次意乓,每個(gè)層次有獨(dú)立的職責(zé)樱调,多個(gè)層次協(xié)同提供完整的功能。
我們?cè)趧倓偝蔀槌绦騿T的時(shí)候届良,會(huì)被“教育”說系統(tǒng)的設(shè)計(jì)要是“MVC”(Model-View-Controller)架構(gòu)本涕。它將整體的系統(tǒng)分成了 Model(模型),View(視圖)和 Controller(控制器)三個(gè)層次伙窃,也就是將用戶視圖和業(yè)務(wù)處理隔離開菩颖,并且通過控制器連接起來,很好地實(shí)現(xiàn)了表現(xiàn)和邏輯的解耦为障,是一種標(biāo)準(zhǔn)的軟件分層架構(gòu)晦闰。
另外一種常見的分層方式是將整體架構(gòu)分為表現(xiàn)層、邏輯層和數(shù)據(jù)訪問層:
- 表現(xiàn)層鳍怨,顧名思義嘛呻右,就是展示數(shù)據(jù)結(jié)果和接受用戶指令的,是最靠近用戶的一層鞋喇;
- 邏輯層里面有復(fù)雜業(yè)務(wù)的具體實(shí)現(xiàn)声滥;
- 數(shù)據(jù)訪問層則是主要處理和存儲(chǔ)之間的交互。
這是在架構(gòu)上最簡單的一種分層方式。其實(shí)落塑,我們?cè)诓唤?jīng)意間已經(jīng)按照三層架構(gòu)來做系統(tǒng)分層設(shè)計(jì)了纽疟,比如在構(gòu)建項(xiàng)目的時(shí)候,我們通常會(huì)建立三個(gè)目錄:Web憾赁、Service 和 Dao污朽,它們分別對(duì)應(yīng)了表現(xiàn)層、邏輯層還有數(shù)據(jù)訪問層龙考。
除此之外蟆肆,如果我們稍加留意,就可以發(fā)現(xiàn)很多的分層的例子晦款。比如我們?cè)诖髮W(xué)中學(xué)到的 OSI 網(wǎng)絡(luò)模型炎功,它把整個(gè)網(wǎng)絡(luò)分了七層,自下而上分別是物理層缓溅、數(shù)據(jù)鏈路層亡问、網(wǎng)絡(luò)層、傳輸層肛宋、會(huì)話層州藕、表示層和應(yīng)用層。
工作中經(jīng)常能用到 TCP/IP 協(xié)議酝陈,它把網(wǎng)絡(luò)簡化成了四層床玻,即鏈路層、網(wǎng)絡(luò)層沉帮、傳輸層和應(yīng)用層锈死。每一層各司其職又互相幫助,網(wǎng)絡(luò)層負(fù)責(zé)端到端的尋址和建立連接穆壕,傳輸層負(fù)責(zé)端到端的數(shù)據(jù)傳輸?shù)却#瑫r(shí)相鄰兩層還會(huì)有數(shù)據(jù)的交互。這樣可以隔離關(guān)注點(diǎn)喇勋,讓不同的層專注做不同的事情缨该。
Linux 文件系統(tǒng)也是分層設(shè)計(jì)的,從下圖你可以清晰地看出文件系統(tǒng)的層次川背。在文件系統(tǒng)的最上層是虛擬文件系統(tǒng)(VFS)贰拿,用來屏蔽不同的文件系統(tǒng)之間的差異,提供統(tǒng)一的系統(tǒng)調(diào)用接口熄云。虛擬文件系統(tǒng)的下層是 Ext3膨更、Ext4 等各種文件系統(tǒng),再向下是為了屏蔽不同硬件設(shè)備的實(shí)現(xiàn)細(xì)節(jié)缴允,我們抽象出來的單獨(dú)的一層——通用塊設(shè)備層荚守,然后就是不同類型的磁盤了。
我們可以看到,某些層次負(fù)責(zé)的是對(duì)下層不同實(shí)現(xiàn)的抽象矗漾,從而對(duì)上層屏蔽實(shí)現(xiàn)細(xì)節(jié)锈候。比方說 VFS 對(duì)上層(系統(tǒng)調(diào)用層)來說提供了統(tǒng)一的調(diào)用接口,同時(shí)對(duì)下層中不同的文件系統(tǒng)規(guī)約了實(shí)現(xiàn)模型缩功,當(dāng)新增一種文件系統(tǒng)實(shí)現(xiàn)的時(shí)候晴及,只需要按照這種模型來設(shè)計(jì)都办,就可以無縫插入到 Linux 文件系統(tǒng)中嫡锌。
那么,為什么這么多系統(tǒng)一定要做分層的設(shè)計(jì)呢琳钉?答案是分層設(shè)計(jì)存在一定的優(yōu)勢(shì)势木。
分層有什么好處
分層的設(shè)計(jì)可以簡化系統(tǒng)設(shè)計(jì),讓不同的人專注做某一層次的事情歌懒。想象一下啦桌,如果你要設(shè)計(jì)一款網(wǎng)絡(luò)程序卻沒有分層,該是一件多么痛苦的事情及皂。
因?yàn)槟惚仨毷且粋€(gè)通曉網(wǎng)絡(luò)的全才甫男,要知道各種網(wǎng)絡(luò)設(shè)備的接口是什么樣的,以便可以將數(shù)據(jù)包發(fā)送給它验烧。你還要關(guān)注數(shù)據(jù)傳輸?shù)募?xì)節(jié)板驳,并且需要處理類似網(wǎng)絡(luò)擁塞,數(shù)據(jù)超時(shí)重傳這樣的復(fù)雜問題碍拆。當(dāng)然了若治,你更需要關(guān)注數(shù)據(jù)如何在網(wǎng)絡(luò)上安全傳輸,不會(huì)被別人窺探和篡改感混。
而有了分層的設(shè)計(jì)端幼,你只需要專注設(shè)計(jì)應(yīng)用層的程序就可以了,其他都可以交給下面幾層來完成弧满。
再有婆跑,分層之后可以做到很高的復(fù)用。比如庭呜,我們?cè)谠O(shè)計(jì)系統(tǒng) A 的時(shí)候洽蛀,發(fā)現(xiàn)某一層具有一定的通用性,那么我們可以把它抽取獨(dú)立出來疟赊,在設(shè)計(jì)系統(tǒng) B 的時(shí)候使用起來郊供,這樣可以減少研發(fā)周期,提升研發(fā)的效率近哟。
最后一點(diǎn)驮审,分層架構(gòu)可以讓我們更容易做橫向擴(kuò)展。如果系統(tǒng)沒有分層,當(dāng)流量增加時(shí)我們需要針對(duì)整體系統(tǒng)來做擴(kuò)展疯淫。但是地来,如果我們按照上面提到的三層架構(gòu)將系統(tǒng)分層后,就可以針對(duì)具體的問題來做細(xì)致的擴(kuò)展熙掺。
比如說未斑,業(yè)務(wù)邏輯里面包含有比較復(fù)雜的計(jì)算,導(dǎo)致 CPU 成為性能的瓶頸币绩,那這樣就可以把邏輯層單獨(dú)抽取出來獨(dú)立部署蜡秽,然后只對(duì)邏輯層來做擴(kuò)展,這相比于針對(duì)整體系統(tǒng)擴(kuò)展所付出的代價(jià)就要小的多了缆镣。
這一點(diǎn)也可以解釋我們課程開始時(shí)提出的問題:架構(gòu)分層究竟和高并發(fā)設(shè)計(jì)的關(guān)系是怎樣的芽突?在“01 | 高并發(fā)系統(tǒng):它的通用設(shè)計(jì)方法是什么?”中我們了解到董瞻,橫向擴(kuò)展是高并發(fā)系統(tǒng)設(shè)計(jì)的常用方法之一寞蚌,既然分層的架構(gòu)可以為橫向擴(kuò)展提供便捷, 那么支撐高并發(fā)的系統(tǒng)一定是分層的系統(tǒng)钠糊。
如何來做系統(tǒng)分層
說了這么多分層的優(yōu)點(diǎn)挟秤,那么當(dāng)我們要做分層設(shè)計(jì)的時(shí)候,需要考慮哪些關(guān)鍵因素呢抄伍?
在我看來艘刚,最主要的一點(diǎn)就是你需要理清楚每個(gè)層次的邊界是什么。你也許會(huì)問:“如果按照三層架構(gòu)來分層的話逝慧,每一層的邊界不是很容易就界定嗎昔脯?”
沒錯(cuò),當(dāng)業(yè)務(wù)邏輯簡單時(shí)笛臣,層次之間的邊界的確清晰云稚,開發(fā)新的功能時(shí)也知道哪些代碼要往哪兒寫。但是當(dāng)業(yè)務(wù)邏輯變得越來越復(fù)雜時(shí)沈堡,邊界就會(huì)變得越來越模糊静陈,給你舉個(gè)例子。
任何一個(gè)系統(tǒng)中都有用戶系統(tǒng)诞丽,最基本的接口是返回用戶信息的接口鲸拥,它調(diào)用邏輯層的 GetUser 方法,GetUser 方法又和 User DB 交互獲取數(shù)據(jù)僧免,就像下圖左邊展示的樣子刑赶。
這時(shí),產(chǎn)品提出一個(gè)需求懂衩,在 APP 中展示用戶信息的時(shí)候撞叨,如果用戶不存在金踪,那么要自動(dòng)給用戶創(chuàng)建一個(gè)用戶。同時(shí)牵敷,要做一個(gè) HTML5 的頁面胡岔,HTML5 頁面要保留之前的邏輯,也就是不需要?jiǎng)?chuàng)建用戶枷餐。這時(shí)邏輯層的邊界就變得不清晰靶瘸,表現(xiàn)層也承擔(dān)了一部分的業(yè)務(wù)邏輯(將獲取用戶和創(chuàng)建用戶接口編排起來)。
那我們要如何做呢毛肋?參照阿里發(fā)布的《阿里巴巴 Java 開發(fā)手冊(cè) v1.4.0(詳盡版)》怨咪,我們可以將原先的三層架構(gòu)細(xì)化成下面的樣子:
我來解釋一下這個(gè)分層架構(gòu)中的每一層的作用。
終端顯示層:各端模板渲染并執(zhí)行顯示的層村生。當(dāng)前主要是 Velocity 渲染惊暴,JS 渲染饼丘, JSP 渲染趁桃,移動(dòng)端展示等。
開放接口層:將 Service 層方法封裝成開放接口肄鸽,同時(shí)進(jìn)行網(wǎng)關(guān)安全控制和流量控制等卫病。
Web 層:主要是對(duì)訪問控制進(jìn)行轉(zhuǎn)發(fā),各類基本參數(shù)校驗(yàn)典徘,或者不復(fù)用的業(yè)務(wù)簡單處理等蟀苛。
Service 層:業(yè)務(wù)邏輯層。
Manager 層:通用業(yè)務(wù)處理層逮诲。這一層主要有兩個(gè)作用帜平,其一,你可以將原先 Service 層的一些通用能力下沉到這一層梅鹦,比如與緩存和存儲(chǔ)交互策略裆甩,中間件的接入;其二齐唆,你也可以在這一層封裝對(duì)第三方接口的調(diào)用嗤栓,比如調(diào)用支付服務(wù),調(diào)用審核服務(wù)等箍邮。
DAO 層:數(shù)據(jù)訪問層茉帅,與底層 MySQL、Oracle锭弊、HBase 等進(jìn)行數(shù)據(jù)交互堪澎。
外部接口或第三方平臺(tái):包括其它部門 RPC 開放接口,基礎(chǔ)平臺(tái)味滞,其它公司的 HTTP 接口樱蛤。
在這個(gè)分層架構(gòu)中主要增加了 Manager 層马昙,它與 Service 層的關(guān)系是:Manager 層提供原子的服務(wù)接口,Service 層負(fù)責(zé)依據(jù)業(yè)務(wù)邏輯來編排原子接口刹悴。
以上面的例子來說行楞,Manager 層提供創(chuàng)建用戶和獲取用戶信息的接口,而 Service 層負(fù)責(zé)將這兩個(gè)接口組裝起來土匀。這樣就把原先散布在表現(xiàn)層的業(yè)務(wù)邏輯都統(tǒng)一到了 Service 層子房,每一層的邊界就非常清晰了。
除此之外就轧,分層架構(gòu)需要考慮層次之間一定是相鄰層互相依賴证杭,數(shù)據(jù)的流轉(zhuǎn)也只能在相鄰的兩層之間流轉(zhuǎn)。
我們還是以三層架構(gòu)為例妒御,數(shù)據(jù)從表示層進(jìn)入之后一定要流轉(zhuǎn)到邏輯層解愤,做業(yè)務(wù)邏輯處理,然后流轉(zhuǎn)到數(shù)據(jù)訪問層來和數(shù)據(jù)庫交互乎莉。那么你可能會(huì)問:“如果業(yè)務(wù)邏輯很簡單的話可不可以從表示層直接到數(shù)據(jù)訪問層送讲,甚至直接讀數(shù)據(jù)庫呢?”
其實(shí)從功能上是可以的惋啃,但是從長遠(yuǎn)的架構(gòu)設(shè)計(jì)考慮哼鬓,這樣會(huì)造成層級(jí)調(diào)用的混亂,比方說如果表示層或者業(yè)務(wù)層可以直接操作數(shù)據(jù)庫边灭,那么一旦數(shù)據(jù)庫地址發(fā)生變更异希,你就需要在多個(gè)層次做更改,這樣就失去了分層的意義绒瘦,并且對(duì)于后面的維護(hù)或者重構(gòu)都會(huì)是災(zāi)難性的称簿。
分層架構(gòu)的不足
任何事物都不可能是盡善盡美的,分層架構(gòu)雖有優(yōu)勢(shì)也會(huì)有缺陷惰帽,它最主要的一個(gè)缺陷就是增加了代碼的復(fù)雜度憨降。
這是顯而易見的嘛,明明可以在接收到請(qǐng)求后就可以直接查詢數(shù)據(jù)庫獲得結(jié)果善茎,卻偏偏要在中間插入多個(gè)層次券册,并且有可能每個(gè)層次只是簡單地做數(shù)據(jù)的傳遞。有時(shí)增加一個(gè)小小的需求也需要更改所有層次上的代碼垂涯,看起來增加了開發(fā)的成本烁焙,并且從調(diào)試上來看也增加了復(fù)雜度,原本如果直接訪問數(shù)據(jù)庫我只需要調(diào)試一個(gè)方法耕赘,現(xiàn)在我卻要調(diào)試多個(gè)層次的多個(gè)方法骄蝇。
另外一個(gè)可能的缺陷是,如果我們把每個(gè)層次獨(dú)立部署操骡,層次間通過網(wǎng)絡(luò)來交互九火,那么多層的架構(gòu)在性能上會(huì)有損耗赚窃。這也是為什么服務(wù)化架構(gòu)性能要比單體架構(gòu)略差的原因,也就是所謂的“多一跳”問題岔激。
那我們是否要選擇分層的架構(gòu)呢勒极?答案當(dāng)然是肯定的。
你要知道虑鼎,任何的方案架構(gòu)都是有優(yōu)勢(shì)有缺陷的辱匿,天地尚且不全何況我們的架構(gòu)呢?分層架構(gòu)固然會(huì)增加系統(tǒng)復(fù)雜度炫彩,也可能會(huì)有性能的損耗匾七,但是相比于它能帶給我們的好處來說,這些都是可以接受的江兢,或者可以通過其它的方案解決的昨忆。我們?cè)谧鰶Q策的時(shí)候切不可以偏概全,因噎廢食杉允。
課程小結(jié)
今天我?guī)е懔私饬朔謱蛹軜?gòu)的優(yōu)勢(shì)和不足邑贴,以及我們?cè)趯?shí)際工作中如何來對(duì)架構(gòu)做分層。我想讓你了解的是夺颤,分層架構(gòu)是軟件設(shè)計(jì)思想的外在體現(xiàn)痢缎,是一種實(shí)現(xiàn)方式胁勺。我們熟知的一些軟件設(shè)計(jì)原則都在分層架構(gòu)中有所體現(xiàn)世澜。
比方說,單一職責(zé)原則規(guī)定每個(gè)類只有單一的功能署穗,在這里可以引申為每一層擁有單一職責(zé)寥裂,且層與層之間邊界清晰;迪米特法則原意是一個(gè)對(duì)象應(yīng)當(dāng)對(duì)其它對(duì)象有盡可能少的了解案疲,在分層架構(gòu)的體現(xiàn)是數(shù)據(jù)的交互不能跨層封恰,只能在相鄰層之間進(jìn)行;而開閉原則要求軟件對(duì)擴(kuò)展開放褐啡,對(duì)修改關(guān)閉诺舔。它的含義其實(shí)就是將抽象層和實(shí)現(xiàn)層分離,抽象層是對(duì)實(shí)現(xiàn)層共有特征的歸納總結(jié)备畦,不可以修改低飒,但是具體的實(shí)現(xiàn)是可以無限擴(kuò)展,隨意替換的懂盐。
掌握這些設(shè)計(jì)思想會(huì)自然而然地明白分層架構(gòu)設(shè)計(jì)的妙處褥赊,同時(shí)也能幫助我們做出更好的設(shè)計(jì)方案。