1. 程式人生 > >剛哥談架構 (三)軟體架構的道與術

剛哥談架構 (三)軟體架構的道與術

秦孝公在位期間致力於恢復秦國的霸業,他因此頒佈著名的求賢令, 商鞅3次遊說秦孝公,第一次講的是堯、舜、禹、湯的帝道。上古時期,百姓安居樂業。秦孝公聽著聽著睡著了。商鞅離開之後,秦孝公向景監發火,說商鞅自大。景監把這個反饋給商鞅,商鞅沒有氣餒,請求再給他一次機會。5天后景監給他安排第二次朝見。這一次商鞅講的是周文王、周武王的王道,講的是禮治天下。秦孝公有點興趣,覺得商鞅可以一起談論,但沒有打算重用商鞅。景監把秦孝公的意思回覆給商鞅後,商鞅說:"我已經知道怎麼遊說他了,請再給我一次機會"。第三次商鞅講的是春秋五霸的霸道。秦孝公這次聽得津津有味,並幾天幾夜促膝長談,不斷向商鞅請教,並重用商鞅,才有了後來的商鞅變法,秦國一統天下。

在軟體和程式設計領域有很多書冠之以‘某某之道’,‘某某之美’,‘某某之禪’。軟體領域也有自己的一套哲學思想。我今天想聊一聊我對軟體架構的哲學思考。這是一個超大的命題,即使我在軟體領域摸爬滾打了這麼多年,也不敢說我已經窺其一二,今日胡說一通,希望得到大家的指正。

‘道’ 暨道路或者方向,是自然規律,是原則和目的,道路和方向是錯的,無論你怎麼努力,都很難成功。

‘術’ 是方法,手段和技巧,好的方法和工具可以讓你在成功的路上事半功倍,前提是你走在正確的道路上。

‘道’ 是價值觀,是主觀的東西,也就是你認為什麼是正確的事, 不同的人對什麼是正確的事有著不同的認識。商鞅的霸道帶領秦國統一六國,但是註定不能長久,秦國也未能如始皇的願,千秋萬代。

‘術’ 是方法論,相對比較客觀,一個好的方法基本上是可以推廣的。

軟體架構之‘道’

我個人認為,軟體架構之道最核心的問題是解決複雜性的問題。如果說‘道’是方向,那麼軟體架構之路應該帶領碼農走向簡單。這裡的簡單應該包含:

  • 軟體分層應該簡單,不應該引入不必要的層級
  • 軟體的模組應該簡單,不應該引入不必要的功能
  • 軟體模組之間的關係應該簡單,不應該引入不必要的互動和依賴
  • 軟體程式碼應該簡單,應該容易理解和閱讀
  • 軟體構建應該簡單,應該容易搭建構建環境
  • 軟體測試應該簡單,應該容易找出軟體中的錯誤
  • 軟體除錯應該簡單,出錯時應該容易定位錯誤源
  • 軟體運維應該簡單,應該容易監控和管理
  • 最重要的,軟體的功能應該簡單,應該容易讓使用者找到自己想用的功能,並且輕鬆的使用該功能,達成使用者想要的目標。

那麼我們想一想,軟體發展到今天,我們上面所說的這些有變簡單了麼?似乎沒有。那麼是不是軟體架構在背道而馳,變得越來越複雜了呢?我不這麼認為。為了理解這個問題,我們來看看軟體中的複雜性的元凶,是什麼帶來了軟體的複雜性。

首先,軟體的複雜性來自於功能。我們之前提到,軟體架構的中心問題是滿足功能要求。隨著人們希望軟體能夠提供越來越多的功能,軟體架構的設計必然會隨之變得複雜。而這個複雜度的增加和功能之間的關係並不是線性的,而是幾何級數甚至更高。因為軟體開發是一個動態過程,新加入的功能必然會和已有的功能產生互動,有些是依賴,有些是制約,有些是干擾。例如系統最初有一個功能A,運作良好;當加入功能B的時候,A功能會制約B功能,所以在設計B功能的時候,除了要完成B本身的功能外,還要設計如何和A功能互動或者如何遮蔽A功能對B的限制。當C功能到來的時候,A和B同時要影響C,甚至可能要考慮ABC的聯動,這樣系統就會越來越複雜了。所以複雜的功能是當前軟體複雜性的主要原因。

其次,軟體的複雜性的另一個元凶是人為的。也就是由於開發軟體的人和組織因為能力不足,或者因為懶惰,貪婪,傲慢等原罪,人為的使軟體變得複雜。

我認為軟體發展的大勢仍然是向著簡單性的方向在前進。因為對功能的要求越來越多,我們現在看到的複雜的軟體架構實際支撐了更為複雜的諸多功能,所以從這個角度來看,軟體架構實際上是向著簡單的趨勢發展。

我們所能做的是把複雜性封裝在更低的層次。例如作業系統封裝了對計算資源(記憶體,CPU,程序,檔案系統)的使用,AWS雲在基礎設定infrastructure層次對網路,主機的使用進行封裝,而Kubernetes利用容器叢集,封裝了應用的部署。作業系統,雲和Kubernetes都是很複雜的,但是通過良好的封裝和簡單的介面,他們給使用者帶來了便利。做為架構設計的一個原則,應該儘量把複雜性封裝在更低的層次。

“软件架构分层”的图片搜索结果

通常在分層架構中,越高的層次一般意味著更多的程式碼和更多的使用。當一個問題發生的時候,一般總是表現在最高層處(UI或者應用層),然而對這個問題的處理,可以在各個層次解決。例如為了提高訪問效率,我們可以在UI或者資料層加入快取,資料層的快取可以支援所有的UI使用者,而UI層的快取只能針對使用該型別的UI的使用者,例如web端和移動端的不用UI各自要實現自己的快取。當然實際中,所有的層次都可以利用自己的快取來解決問題,但是從解決問題的效率來看,在低層次上解決問題的效率要高於更高的層次。所以我們應該儘可能的把複雜問題的解決放在更低的層次上。

但是我們之前也提到過人是軟體架構的一個基本點之一,分層的架構往往也意味著分層的組織。當UI的團隊試圖解決效能問題的時候,他們往往希望在組織可控的範圍內解決問題。這並不是因為他們不明白在資料層解決問題的優勢,而是因為要和另一個組織,資料庫部門去溝通,帶來的額外成本比自己加一個快取可能還要高。做正確的事是有代價的。參見康威定律

愛因斯坦說: “Simple,but not simpler”。

建築大師路德維希·密斯·凡德羅說:“Less is more。”

奧卡姆說:“如無必要,勿增實體”。

老子說:“大道至簡”。

軟體架構之道在於找到設計的平衡點,使得架構足夠簡單,但是能夠滿足需要。 關於奧卡姆剃掉,請大家參考我之前的動圖,用視覺化來講故事

除了簡單性作為道之根本,軟體架構設計還有一些常見的通用原則,我認為這些原則都是和降低系統複雜度一致的:

KISS原則

Keep it simple and stupid 是我們之前的簡單性原則的一種說法。我想說的是簡單未必愚蠢,很多時候大智若愚。

最小代價(努力)原則

光總是走最短路徑,人也一樣。程式猿的惰性與生俱來,我們總是選擇最容易行走的路徑。這也是為什麼我們應該儘可能在一開始的時候,作出正確的選擇,因為一旦這個架構設計出現,後面的人很有可能不願意為了更好的架構而改進,而是遵循已有的設計。這個和簡單性原則一致,如果我們不能在一開始作出正確的選擇,因為最小代價原則,系統必然會走向複雜。

最小意外原則

以前讀過一本講UX設計的書《點石成金》,英文名叫《Dont make me think》。和這個原則一致,UX設計應該自然,符合使用者的常識和使用習慣。如果使用者需要通過思考才能理解如何互動,那麼一定是設計出了問題。同樣的,架構設計也一樣,好的設計應該避免意外,遵守通用的規範和習慣,程式碼也是。這些意外其實是軟體架構和設計中的複雜因素。最小意外也就意味著儘可能的簡單。

DRY原則 

重複是軟體的原罪之一,“Dont repeat yourself” 告訴我們應該儘可能的消滅重複和冗餘。重複使的軟體的閱讀,修改,測試變得複雜,消滅重複,是使軟體變得簡單的手段之一。

 

軟體架構之‘術’

我們再來聊聊軟體架構的‘術’。在軟體和軟體架構設計領域,有很多方法和工具,我們來看看其中最常見的一些。我們可以把他們歸為“術”。這些和軟體架構都有著直接或者間接的關係。

資料結構和演算法

資料結構和演算法是軟體程式設計領域裡最重要的方法。資料結構,是抽象的表示資料的方式;演算法,則是計算的一系列有效、通用的步驟。資料結構是以某種形式將資料組織在一起的集合,它不僅儲存資料,還支援訪問和處理資料的操作。演算法是為求解一個問題需要遵循的、被清楚指定的簡單指令的集合。 演算法與資料結構是程式設計中相輔相成的兩個方面,是計算機學科的重要基石。運用資料結構和演算法,可以有效的解決程式設計中遇到的一些常見的複雜問題。

每一個程式猿從一開始學習程式設計就接觸資料結構和演算法。有人說,程式等於資料結構加演算法。這有一定的道理,但是顯然不夠全面。資料結構和演算法作為程式設計的基礎方法,是各大軟體廠商招聘考核的標杆,不管你是程式猿還是架構師,都需要投入精力於此,資料結構和演算法是軟體和架構設計的基礎。

面向物件和設計模式

僅有資料結構和演算法還不足以應對複雜的軟體開發的需要。面向物件的設計成為了軟體開發領域裡最為流行的思想。面向物件是一種對現實世界理解和抽象的方法,利用的是隱喻(metaphor)的手段。隱喻其實是我們在軟體設計中常用的一種手段,為了便於理解,我們把現實世界中的概念用軟體中的概念的來模擬,這樣做的好處是便於我們去思考,因為我們的設計是基於我們對現實世界的理解,所以可以重用我們現現實世界積累的成功經驗。這樣做的缺點是,軟體世界有自己的特點,完全套用現實世界的偏見可能並非最為有效。現在雖然對面向物件的程式設計仍處於統治地位,但是它的聲音已經漸漸變弱。與之對應的有面向過程,函數語言程式設計,面向切面(Aspect Oriented)等思想。

設計模式(Design pattern)起源於《設計模式:可複用面向物件軟體的基礎》一書,提出和總結了對於一些常見軟體設計問題的標準解決方案,稱為軟體設計模式。該書作者後以“四人幫”著稱。設計模式是軟體開發人員在軟體開發過程中面臨的一般問題的解決方案。這些解決方案是眾多軟體開發人員經過相當長的一段時間的試驗和錯誤總結出來的。

軟體設計模式和麵向物件的設計都基於一些常見的原則:

  • 單一職責原則 Single Responsiblity
    一個程式碼元件(例如類或函式)應該只執行單一的預設的任務。
  • 開放封閉原則 Open Close
    程式裡的實體項(類,模組,函式等)應該對擴充套件行為開放,對修改行為關閉。換句話說,不要寫允許別人修改的類,應該寫能讓人們擴充套件的類
  • 里氏替換原則 Liskov Substiution
    里氏替換原則的內容可以描述為: “派生類(子類)物件可以在程式中代替其基類(超類)物件。”
  • 介面隔離原則 Interface Segregation
    指明客戶(client)應該不依賴於它不使用的方法。介面隔離原則(ISP)拆分非常龐大臃腫的介面成為更小的和更具體的介面,這樣客戶將會只需要知道他們感興趣的方法。這種縮小的介面也被稱為角色介面(role interfaces)。介面隔離原則(ISP)的目的是系統解開耦合,從而容易重構,更改和重新部署。
  • 依賴倒置原則 Dependency Inversion
    程式要依賴於抽象介面,不要依賴於具體實現。簡單的說就是要求對抽象進行程式設計,不要對實現進行程式設計,這樣就降低了客戶與實現模組間的耦合。

以上的五個原則構成了著名的SOLID,除此之外,還有一個著名的高內聚低耦合原則,是指具有相似功能的程式碼應該放在同一個程式碼元件裡。一個程式碼片段(程式碼塊,函式,類等)應該最小化它對其它程式碼的依賴。這個目標通過儘可能少的使用共享變數來實現。“低耦合是一個計算機系統結構合理、設計優秀的標誌,把它與高聚合特徵聯合起來,會對可讀性和可維護性等重要目標的實現具有重要的意義。”該原則和我們之前提到的把複雜性封裝在更低的層次上是一致的,對複雜性的封裝就是高內聚。

模組化和分層

模組化和分層是架構設計裡最基本的方法。

如上圖的一個典型的軟體架構圖中,不同的模組位於不同的層級,某些模組會跨一些層級。模組化和分層利用的是分而治之的方法,把複雜的軟體系統劃分為可控制,可管理的小單元。也即是說,它仍然是為了解決複雜性的問題。分層和模組化不是總能降低複雜度,過多的層次和冗餘的模組會使系統變得更為複雜。

微服務和SOA

隨著越來越多的軟體應用走向雲端,雲原生的設計越來越多的被提起。微服務隨之大行其道。Martin Fowler對微服務的定義是“微服務架構是一種架構模式,它提倡將單一應用程式劃分成一組小的服務,服務之間相互協調、互相配合,為使用者提供最終價值。每個服務執行在其獨立的程序中,服務和服務之間採用輕量級的通訊機制相互溝通(通常是基於HTTP的Restful API).每個服務都圍繞著具體的業務進行構建,並且能夠被獨立的部署到生產環境、類生產環境等。另外,應儘量避免統一的、集中的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構"

“microservice software architecture”的图片搜索结果

和之前的模組化和分層相比較,分層更多是關注在垂直方向上的責任劃分,而微服務增加了水平方向的擴張,並實現去中心化的網路架構。關於去中心化的網路架構推薦閱讀凱文·凱利的《失控》

優點

  1. 提升開發交流,每個服務足夠內聚,足夠小,程式碼容易理解;
  2. 服務獨立測試、部署、升級、釋出;
  3. 按需定製的DFX,資源利用率,每個服務可以各自進行x擴充套件和z擴充套件,而且,每個服務可以根據自己的需要部署到合適的硬體伺服器上;每個服務按
  4. 需要選擇HA的模式,選擇接受服務的例項個數;
  5. 容易擴大開發團隊,可以針對每個服務(service)元件開發團隊;
  6. 提高容錯性(fault isolation),一個服務的記憶體洩露並不會讓整個系統癱瘓;
  7. 新技術的應用,系統不會被長期限制在某個技術棧上;

缺點

  1. 沒有銀彈,微服務提高了系統的複雜度;
  2. 開發人員要處理分散式系統的複雜性;
  3. 服務之間的分散式通訊問題;
  4. 服務的註冊與發現問題;
  5. 服務之間的分散式事務問題;
  6. 資料隔離再來的報表處理問題;
  7. 服務之間的分散式一致性問題;
  8. 服務管理的複雜性,服務的編排;
  9. 不同服務例項的管理。

我們看到,微服務在某種程度上提高了系統的複雜度,天下沒有免費的午餐,作為一個架構師,需要理解微服務帶來的好處是否能夠抵消複雜性提升的代價。

當今的微服務大多基於容器設計,關於微服務中容器設計的原則,請參考我的部落格基於容器應用設計的原則,模式和反模式

無服務設計 (Serverless)

Serverless 架構是指大量依賴第三方服務(也叫做後端即服務,即“BaaS”)或暫存容器中執行的自定義程式碼(函式即服務,即“FaaS”)的應用程式,函式是無伺服器架構中抽象語言執行時的最小單位。在這種架構中,我們並不看重執行一個函式需要多少 CPU 或 RAM 或任何其他資源,而是更看重執行函式所需的時間,我們也只為這些函式的執行時間付費。

“serverless”的图片搜索结果

無服務的設計帶來的優點有:

  • 降低運營成本:
    Serverless是非常簡單的外包解決方案。它可以讓您委託服務提供商管理伺服器、資料庫和應用程式甚至邏輯,否則您就不得不自己來維護。由於這個服務使用者的數量會非常龐大,於是就會產生規模經濟效應。在降低成本上包含了兩個方面,即基礎設施的成本和人員(運營/開發)的成本。
  • 降低開發成本:
    IaaS和PaaS存在的前提是,伺服器和作業系統管理可以商品化。Serverless作為另一種服務的結果是整個應用程式元件被商品化。
  • 擴充套件能力:
    Serverless架構一個顯而易見的優點即“橫向擴充套件是完全自動的、有彈性的、且由服務提供者所管理”。從基本的基礎設施方面受益最大的好處是,您只需支付您所需要的計算能力。
  • 更簡單的管理:
    Serverless架構明顯比其他架構更簡單。更少的元件,就意味著您的管理開銷會更少。
  • “綠色”的計算:
    按照《福布斯》雜誌的統計,在商業和企業資料中心的典型伺服器僅提供5%~15%的平均最大處理能力的輸出。這無疑是一種資源的巨大浪費。隨著Serverless架構的出現,讓服務提供商提供我們的計算能力最大限度滿足實時需求。這將使我們更有效地利用計算資源。

當然無服務設計也有一些限制,它更適用於大量,頻繁而簡單的,無狀態的操作,如果服務之間的關係比較複雜,狀態比較多,呼叫時間比較長,無服務設計並不是非常適合。

軟體架構設計還有很多方法,這裡不可能一一討論,例如領域驅動的設計(DDD),重構技術,敏捷(更多是軟體工程,但是和架構也會有密切地關係),希望以後是時間再和大家分享,討論。

行走於軟體江湖

如果軟體行業是個江湖,那麼我們程式猿就是行走於江湖的武林人士。每個人要做的就是找尋“道”,研習“術”,磨練“器”。

“道”是價值觀,是你的江湖理想,是你的追求。就像《神鵰俠侶》時期守襄陽,信奉“俠之大者,為國為民”的郭靖,。

“術”是你的內功心法和拳藝招數。資料結構和演算法就像是內功,幫助你提高對戰的效率。而各種其它的方法就像是招數。你可以像是學了獨孤九劍的令狐沖,僅憑招數就可以殺敵無數,但是要成為武林盟主,內功修養也是不可或缺。

“器”是工具,各種語言,IDE,可以劃分到“器”,“工欲善其事,必先利其器”。各位大俠行走江湖,免不了要選幾樣趁手的兵器,有人喜歡劍走偏鋒,有人喜歡暴力砍殺。什麼,你說你喜歡九齒釘耙?我只能贊你一聲神仙威武!參考如果程式語言是一種武器

江湖苦修非一日之功,希望各位大俠在軟體江湖早日得道,成為武林至尊或者一方豪傑。

參考