程序員過關(guān)斬將--論系統(tǒng)設(shè)計(jì)的高可擴(kuò)展性
“此文僅僅代表個(gè)人意見,并非行業(yè)標(biāo)準(zhǔn)
“MQ是萬能的高擴(kuò)展方式?
“面向接口是萬能的高擴(kuò)展方式?
說到系統(tǒng)設(shè)計(jì)的三高,每一高都是一個(gè)很龐大的話題,甚至可以用一本書甚至N本書來詳細(xì)闡述。其中高可擴(kuò)展性是系統(tǒng)架構(gòu)的眾多目標(biāo)之一。歸根結(jié)底,系統(tǒng)的架構(gòu)要為最終的業(yè)務(wù)服務(wù),脫離業(yè)務(wù)來談架構(gòu)其實(shí)比耍流氓更無恥。
在我們心目中最理想的軟件架構(gòu)要像搭積木一樣簡單,并且快捷,而且高效。但是現(xiàn)實(shí)往往比996更殘酷,多數(shù)的系統(tǒng)在初期為了配合業(yè)務(wù)快速上線,擴(kuò)展性這個(gè)指標(biāo)并不理想。別的不談,一個(gè)系統(tǒng)要完美的做到“對修改封閉,對擴(kuò)展開放”其實(shí)一點(diǎn)也不簡單,不知道你有沒有遇到過修改一個(gè)bug蹦出另外一個(gè)bug的痛苦經(jīng)歷?
為了做到系統(tǒng)的高擴(kuò)展性,其實(shí)有很多借鑒的案例,尤其是設(shè)計(jì)模式。但是今天我還是要說一說我自己的看法。無論什么樣的系統(tǒng),抽象起來其實(shí)都是模塊和模塊之間的交互,這里模塊的含義是廣義的,即可以代表函數(shù),也可以代表進(jìn)程,甚至可以代表目前流行的微服務(wù),如下圖所示

圖是不是很簡單?但是要想把A和B之間的交互做到高擴(kuò)展其實(shí)并不容易,這要求系統(tǒng)的設(shè)計(jì)者必須要想辦法在滿足A和B正常交互的情況下盡量解耦A(yù)和B,只有正確的解耦,才能從容的應(yīng)對A和B獨(dú)立擴(kuò)展的業(yè)務(wù)需求
同一進(jìn)程內(nèi)
在同一進(jìn)程內(nèi)的情況是一種最常見的存在方式,對應(yīng)到我們平時(shí)的代碼,表現(xiàn)為函數(shù)的調(diào)用,而這里的函數(shù)調(diào)用可以是同一模塊內(nèi)的函數(shù)調(diào)用,比如最典型的三層架構(gòu)中,業(yè)務(wù)層調(diào)用持久化層來進(jìn)行數(shù)據(jù)的操作,如下代碼:
//user?業(yè)務(wù)層
????public?class?UserBLL
????{
????????UserDAL?dal?=?new?UserDAL();
????????public?int?AddUser(User?user)
????????{
????????????//其他業(yè)務(wù)
????????????return?dal.AddUser(user);
????????}
???????
????}
????//user持久化層
????public?class?UserDAL
????{
????????public?int?AddUser(User?user)
????????{
????????????//進(jìn)行數(shù)據(jù)庫操作
????????????return?0;
????????}
????}
我真的希望實(shí)際項(xiàng)目中的代碼能像以上代碼這么簡單,畢竟代碼就和項(xiàng)目一樣,簡單即是美。這段代碼排除業(yè)務(wù)之外,從架構(gòu)來講也有很多問題,用開頭的A和B的方式來表示,A代表的是UserBLL,B代表的是UserDAL,這里最容易看出的就是強(qiáng)耦合,即:A嚴(yán)重依賴于B,如果B有什么風(fēng)吹草動(dòng),勢必會(huì)影響A的執(zhí)行。
怎么辦呢?所以有了B的抽象層,對應(yīng)到代碼上是IDAL接口層,當(dāng)然這個(gè)抽象層應(yīng)該是穩(wěn)定的,如果三天兩頭修改抽象層,那說明抽象的有問題。A在執(zhí)行上改為依賴IDAL,這是系統(tǒng)內(nèi)設(shè)計(jì)最常見的面向接口設(shè)計(jì)模式,其實(shí)更準(zhǔn)確的說,應(yīng)該是面向抽象設(shè)計(jì)模式。由于引入了穩(wěn)定的抽象層,不再穩(wěn)定的實(shí)現(xiàn)層就可以根據(jù)實(shí)際的業(yè)務(wù)去修改,這里體現(xiàn)的是系統(tǒng)設(shè)計(jì)中依賴倒置的原則,當(dāng)然為了實(shí)現(xiàn)依賴倒置,你可能需要使用IOC等技術(shù)來實(shí)現(xiàn)項(xiàng)目落地。

//user?業(yè)務(wù)層
????public?class?UserBLL
????{
????????IUserDAL?dal?=?"依賴注入";
????????public?int?AddUser(User?user)
????????{
????????????//其他業(yè)務(wù)
????????????return?dal.AddUser(user);
????????}
???????
????}
????//user的持久化層抽象
????public?interface?IUserDAL
????{
????????int?AddUser(User?user);
????}
????//user持久化層
????public?class?UserDAL:?IUserDAL
????{
????????public?int?AddUser(User?user)
????????{
????????????//進(jìn)行數(shù)據(jù)庫操作
????????????return?0;
????????}
????}
不同進(jìn)程間
不同的進(jìn)程之間互相協(xié)作是目前分布式模式下主要的交互方式,例如之前的SOA,現(xiàn)在的微服務(wù),都是在利用分散在不同位置的模塊來組裝系統(tǒng),這些模塊之間的通信是一個(gè)分布式系統(tǒng)必備的條件。
和進(jìn)程內(nèi)函數(shù)調(diào)用類似,分布式系統(tǒng)也可以抽象為A和B的關(guān)系模型,我們要解決的也是A和B能夠獨(dú)立變化的問題?,F(xiàn)在假設(shè)A服務(wù)依賴于B服務(wù),B服務(wù)由于壓力大需要擴(kuò)容,會(huì)有哪些影響呢?
B自己內(nèi)部的狀態(tài)變化,如果B服務(wù)是有狀態(tài)的,擴(kuò)展起來可能會(huì)設(shè)計(jì)到數(shù)據(jù)的遷移等操作,如果B是無狀態(tài)的,理論來說可以很方便的橫向擴(kuò)展 B的擴(kuò)容對A或者其他依賴于B的系統(tǒng)有什么影響,依賴方能否做到自動(dòng)適配,而不必修改任何配置
和進(jìn)程內(nèi)函數(shù)調(diào)用不同,進(jìn)程間的通信需要通訊協(xié)議的支持,最常見的RPC調(diào)用都是基于TCP協(xié)議,Restfull基于http協(xié)議,使用這些協(xié)議底層都需要指定明確的IP和端口。所以需要某種解決方案在被依賴方擴(kuò)展的時(shí)候,依賴方能夠得到感知。聰明的你可能想到了“注冊中心”,不錯(cuò),這也是注冊中心最主要的職責(zé)。
解決方案2
用注冊中心的方式,理論上屬于通知依賴方的方案,在依賴方感知被依賴方有擴(kuò)展變動(dòng)的時(shí)候,需要作出對應(yīng)的變化。與之對應(yīng)的其實(shí)我們也可以把變動(dòng)封裝在被依賴方,這個(gè)時(shí)候就引入了以下代理模式,最常見的就是網(wǎng)關(guān)模式。
分布式系統(tǒng)使用網(wǎng)關(guān)到底是好還是壞?
其實(shí)代理模式非常常見,比如Nginx做反向代理,數(shù)據(jù)庫的中間件。這些設(shè)施都是對依賴方透明的,依賴方不會(huì)因?yàn)楸灰蕾嚪綄?shí)施了擴(kuò)展而受影響。
解決方案3
目前很多業(yè)務(wù)下有一種很常見的場景,依賴方和被依賴方通信并不需要知道執(zhí)行結(jié)果,最典型的場景像:新用戶注冊給用戶發(fā)歡迎郵件或者短信歡迎語。如果業(yè)務(wù)代碼中冗余了發(fā)郵件或者短信的代碼的話,一旦要添加新的歡迎方式就必須要修改業(yè)務(wù)代碼,無論你是否有抽象層,為了不影響主要的業(yè)務(wù)又最大化解耦系統(tǒng),一般都會(huì)把這種非主要業(yè)務(wù)通過消息的方式分離出來。最常見的解決方案就是MQ。這也是典型發(fā)布訂閱模式,但是這種模式如上所說,調(diào)用方并不能實(shí)時(shí)的得到業(yè)務(wù)處理結(jié)果。
利用MQ來進(jìn)行系統(tǒng)的解耦,來實(shí)現(xiàn)系統(tǒng)的高可擴(kuò)展是一種非常常見的方式,優(yōu)勢有很多,我不再闡述,但是需要注意消息的可靠性,因?yàn)橄⒔?jīng)過了幾個(gè)環(huán)節(jié)之后,難保某個(gè)環(huán)節(jié)出現(xiàn)問題而丟失消息。具體的詳細(xì)介紹可以查看
真的可以用版本號(hào)的方式來保證MQ消費(fèi)消息的冪等性?
寫在最后
A和B之間的通信如果只是單向的話,可以理解為上下級(jí)關(guān)系,但是在微服務(wù)情況下,A和B很多時(shí)候是平行的互相調(diào)用的兄弟關(guān)系。有的架構(gòu)師不贊成平行關(guān)系的微服務(wù)互相調(diào)用,這是有一定道理的,因?yàn)檫@很容易造成復(fù)雜的網(wǎng)絡(luò)調(diào)用模式,如果是符合MQ消息的形式通信,我也推薦首推利用MQ來解耦服務(wù)間的依賴。
高可擴(kuò)展性系統(tǒng)的最終目標(biāo)是在應(yīng)對業(yè)務(wù)變化的時(shí)候,用最小的代價(jià)去實(shí)現(xiàn)。而如何實(shí)現(xiàn)系統(tǒng)的擴(kuò)展性,并非只有以上所說的“面向接口編程”,利用MQ這些方式,你還知道哪些可以幫助系統(tǒng)擴(kuò)展的解決方案嗎?歡迎你給我留言!!
“只要一提到解耦,有的“高手”一上來就說利用MQ,真的對嗎?如果調(diào)用方需要實(shí)時(shí)的業(yè)務(wù)處理結(jié)果呢?

更多精彩文章
