從0開始學架構——06複雜度來源——可擴展性

本文為連載文章之第六講,可關注查看最新的更新的歷史講解!

複雜度來源前面已經講了高性能和高可用,今天來聊聊可擴展性。

可擴展性指系統為了應對將來需求變化而提供的一種擴展能力,當有新的需求出現時,系統不需要或者僅需要少量修改就可以支持,無須整個系統重構或者重建。

由於軟件系統固有的多變性,新的需求總會不斷提出來,因此可擴展性顯得尤其重要。在軟件開發領域,面向對象思想的提出,就是為了解決可擴展性帶來的問題;後來的設計模式,更是將可擴展性做到了極致。得益於設計模式的巨大影響力,幾乎所有的技術人員對於可擴展性都特別重視。

設計具備良好可擴展性的系統,有兩個基本條件:正確預測變化、完美封裝變化。但要達成這兩個條件,本身也是一件複雜的事情,我來具體分析一下。

預測變化

軟件系統與硬件或者建築相比,有一個很大的差異:軟件系統在發佈後還可以不斷地修改和演進,這就意味著不斷有新的需求需要實現。如果新需求能夠不改代碼甚至少改代碼就可以實現,那當然是皆大歡喜的,否則來一個需求就要求系統大改一次,成本會非常高,程序員心裡也不爽(改來改去),產品經理也不爽(做得那麼慢),老闆也不爽(那麼多人就只能幹這麼點事)。因此作為架構師,我們總是試圖去預測所有的變化,然後設計完美的方案來應對,當下一次需求真正來臨時,架構師可以自豪地說:這個我當時已經預測到了,架構已經完美地支持,只需要一兩天工作量就可以了!

然而理想是美好的,現實卻是複雜的。有一句諺語,“唯一不變的是變化”,如果按照這個標準去衡量,架構師每個設計方案都要考慮可擴展性。例如,架構師準備設計一個簡單的後臺管理系統,當架構師考慮用 MySQL 存儲數據時,是否要考慮後續需要用 Oracle 來存儲?當架構師設計用 HTTP 做接口協議時,是否要考慮要不要支持 ProtocolBuffer?甚至更離譜一點,架構師是否要考慮 VR 技術對架構的影響從而提前做好可擴展性?如果每個點都考慮可擴展性,架構師會不堪重負,架構設計也會異常龐大且最終無法落地。但架構師也不能完全不做預測,否則可能系統剛上線,馬上來新的需求就需要重構,這同樣意味著前期很多投入的工作量也白費了。同時,“預測”這個詞,本身就暗示了不可能每次預測都是準確的,如果預測的事情出錯,我們期望中的需求遲遲不來,甚至被明確否定,那麼基於預測做的架構設計就沒什麼作用,投入的工作量也就白費了。

綜合分析,預測變化的複雜性在於:

•不能每個設計點都考慮可擴展性。

•不能完全不考慮可擴展性。

•所有的預測都存在出錯的可能性。

對於架構師來說,如何把握預測的程度和提升預測結果的準確性,是一件很複雜的事情,而且沒有通用的標準可以簡單套上去,更多是靠自己的經驗、直覺,所以架構設計評審的時候經常會出現兩個設計師對某個判斷爭得面紅耳赤的情況,原因就在於沒有明確標準,不同的人理解和判斷有偏差,而最終又只能選擇一個判斷。

應對變化

假設架構師經驗非常豐富,目光非常敏銳,看問題非常準,所有的變化都能準確預測,是否意味著可擴展性就很容易實現了呢?也沒那麼理想!因為預測變化是一回事,採取什麼方案來應對變化,又是另外一個複雜的事情。即使預測很準確,如果方案不合適,則系統擴展一樣很麻煩。

從0開始學架構——06複雜度來源——可擴展性

第一種應對變化的常見方案是將“變化”封裝在一個“變化層”,將不變的部分封裝在一個獨立的“穩定層”。無論是變化層依賴穩定層,還是穩定層依賴變化層都是可以的,需要根據具體業務情況來設計。例如,如果系統需要支持 XML、JSON、ProtocolBuffer 三種接入方式,那麼最終的架構就是上面圖中的“形式 1”架構,也就是下面這樣。

從0開始學架構——06複雜度來源——可擴展性

如果系統需要支持 MySQL、Oracle、DB2 數據庫存儲,那麼最終的架構就變成了“形式 2”的架構了,你可以看下面這張圖。

從0開始學架構——06複雜度來源——可擴展性

無論採取哪種形式,通過剝離變化層和穩定層的方式應對變化,都會帶來兩個主要的複雜性相關的問題。

1.系統需要拆分出變化層和穩定層對於哪些屬於變化層,哪些屬於穩定層,很多時候並不是像前面的示例(不同接口協議或者不同數據庫)那樣明確,不同的人有不同的理解,導致架構設計評審的時候可能吵翻天。

2.需要設計變化層和穩定層之間的接口

接口設計同樣至關重要,對於穩定層來說,接口肯定是越穩定越好;但對於變化層來說,在有差異的多個實現方式中找出共同點,並且還要保證當加入新的

功能時原有的接口設計不需要太大修改,這是一件很複雜的事情。例如,

MySQL 的 REPLACE INTO 和 Oracle 的 MERGE INTO 語法和功能有一些差異,那存儲層如何向穩定層提供數據訪問接口呢?是採取 MySQL 的方式,還是採取 Oracle 的方式,還是自適應判斷?如果再考慮 DB2 的情況呢?相信你看到這裡就已經能夠大致體會到接口設計的複雜性了。

第二種常見的應對變化的方案是提煉出一個“抽象層”和一個“實現層”。抽象層是穩定的,實現層可以根據具體業務需要定製開發,當加入新的功能時,只需要增加新的實現,無須修改抽象層。這種方案典型的實踐就是設計模式和規則引擎。考慮到絕大部分技術人員對設計模式都非常熟悉,我以設計模式為例來說明這種方案的複雜性。

以設計模式的“裝飾者”模式來分析,下面是裝飾者模式的類關係圖。

從0開始學架構——06複雜度來源——可擴展性

圖中的 Component 和 Decorator 就是抽象出來的規則,這個規則包括幾部分:

1.Component 和 Decorator 類。 2.Decorator 類繼承 Component 類。

3.Decorator 類聚合了 Component 類。

這個規則一旦抽象出來後就固定了,不能輕易修改。例如,把規則 3 去掉,就無法實現裝飾者模式的目的了。

裝飾者模式相比傳統的繼承來實現功能,確實靈活很多。例如,《設計模式》中裝飾者模式的樣例“TextView”類的實現,用了裝飾者之後,能夠靈活地給

TextView 增加額外更多功能,比如可以增加邊框、滾動條、背景圖片等,這些功能上的組合不影響規則,只需要按照規則實現即可。但裝飾者模式相對普通的類實現模式,明顯要複雜多了。本來一個函數或者一個類就能搞定的事情,現在要拆分成多個類,而且多個類之間必須按照裝飾者模式來設計和調用。

規則引擎和設計模式類似,都是通過靈活的設計來達到可擴展的目的,但“靈活的設計”本身就是一件複雜的事情,不說別的,光是把 23 種設計模式全部理解和備註,都是一件很困難的事情。

小結

今天我從預測變化和應對變化這兩個設計可擴展性系統的條件,以及它們實現

起來本身的複雜性,為你講了複雜度來源之一的可擴展性,希望對你有所幫助。

這就是今天的全部內容,留一道思考題給你吧。你在具體代碼中使用過哪些可擴展的技術?最終的效果如何?

歡迎你把答案寫到留言區,和我一起討論。相信經過深度思考的回答,也會讓

你對知識的理解更加深刻。


分享到:


相關文章: