合適原則、簡單原則、演化原則,架構設計時遵循這幾個原則,有助于做出最好的選擇。
合適原則
合適原則宣言:“合適優于業界領先”。
優秀的技術人員都有很強的技術情結,當他們做方案或者架構時,總想不斷地挑戰自己,想達到甚至優于業界領先水平是其中一個典型表現,因為這樣才能夠展現自己的優秀,才能在年終 KPI 績效總結里面驕傲地寫上“設計了 XX 方案,達到了和 Google 相同的技術水平”“XX 方案的性能測試結果大大優于阿里集團的 YY 方案”。
但現實是,大部分這樣想和這樣做的架構,最后可能都以失敗告終!我在互聯網行業見過“億級用戶平臺”的失敗案例,2011 年的時候,某個幾個人規模的業務團隊,雄心勃勃的提出要做一個和騰訊 QQ(那時候微信還沒起來)一拼高下的“億級用戶平臺”,最后結果當然是不出所料的失敗了。
為什么會這樣呢?
再好的夢想,也需要腳踏實地實現!這里的“腳踏實地”主要體現在下面幾個方面。
1. 將軍難打無兵之仗
大公司的分工比較細,一個小系統可能就是一個小組負責,比如說某個通信大廠,做一個 OM 管理系統就有十幾個人,阿里的中間件團隊有幾十個人,而大部分公司,整個研發團隊可能就 100 多人,某個業務團隊可能就十幾個人。十幾個人的團隊,想做幾十個人的團隊的事情,而且還要做得更好,不能說絕對不可能,但難度是可想而知的。
沒那么多人,卻想干那么多活,是失敗的第一個主要原因。
2. 羅馬不是一天建成的
業界領先的很多方案,其實并不是一堆天才某個時期靈機一動,然后加班加點就做出來的,而是經過幾年時間的發展才逐步完善和初具規模的。阿里中間件團隊 2008 年成立,發展到現在已經有十年了。我們只知道他們抗住了多少次“雙 11”,做了多少優秀的系統,但經歷了什么樣的挑戰、踩了什么樣的坑,只有他們自己知道!這些挑戰和踩坑,都是架構設計非常關鍵的促進因素,單純靠拍腦袋或者頭腦風暴,是不可能和真正實戰相比的。
沒有那么多積累,卻想一步登天,是失敗的第二個主要原因。
3. 冰山下面才是關鍵
可能有人認為,業界領先的方案都是天才創造出來的,所以自己也要造一個業界領先的方案,以此來證明自己也是天才。確實有這樣的天才,但更多的時候,業界領先的方案其實都是“逼”出來的!簡單來說,“業務”發展到一定階段,量變導致了質變,出現了新的問題,已有的方式已經不能應對這些問題,需要用一種新的方案來解決,通過創新和嘗試,才有了業界領先的方案。GFS 為何在 Google 誕生,而不是在 Microsoft 誕生?我認為 Google 有那么龐大的數據是一個主要的因素,而不是因為 Google 的工程師比 Microsoft 的工程師更加聰明。
沒有那么卓越的業務場景,卻幻想靈光一閃成為天才,是失敗的第三個主要原因。
回到我前面提到的“億級用戶平臺”失敗的例子,分析一下原因。沒有騰訊那么多的人(當然錢差得更多),沒有 QQ 那樣海量用戶的積累,沒有 QQ 那樣的業務,這個項目失敗其實是在一開始就注定的。注意這里的失敗不是說系統做不出來,而是系統沒有按照最初的目標來實現,上面提到的 3 個失敗原因也全占了。
所以,真正優秀的架構都是在企業當前人力、條件、業務等各種約束下設計出來的,能夠合理地將資源整合在一起并發揮出最大功效,并且能夠快速落地。這也是很多 BAT 出來的架構師到了小公司或者創業團隊反而做不出成績的原因,因為沒有了大公司的平臺、資源、積累,只是生搬硬套大公司的做法,失敗的概率非常高。
簡單原則
簡單原則宣言:“簡單優于復雜”。
軟件架構設計是一門技術活。所謂技術活,從歷史上看,無論是瑞士的鐘表,還是瓦特的蒸汽機;無論是萊特兄弟發明的飛機,還是摩托羅拉發明的手機,無一不是越來越精細、越來越復雜。因此當我們進行架構設計時,會自然而然地想把架構做精美、做復雜,這樣才能體現我們的技術實力,也才能夠將架構做成一件藝術品。
由于軟件架構和建筑架構表面上的相似性,我們也會潛意識地將對建筑的審美觀點移植到軟件架構上面。我們驚嘆于長城的宏偉、泰姬陵的精美、悉尼歌劇院的藝術感、迪拜帆船酒店的豪華感,因此,對于我們自己親手打造的軟件架構,我們也希望它宏偉、精美、藝術、豪華……總之就是不能寒酸、不能簡單。
團隊的壓力有時也會有意無意地促進我們走向復雜的方向,因為大部分人在評價一個方案水平高低的時候,復雜性是其中一個重要的參考指標。例如設計一個主備方案,如果你用心跳來實現,可能大家都認為這太簡單了。但如果你引入 ZooKeeper 來做主備決策,可能很多人會認為這個方案更加“高大上”一些,畢竟 ZooKeeper 使用的是 ZAB 協議,而 ZAB 協議本身就很復雜。其實,真正理解 ZAB 協議的人很少(我也不懂),但并不妨礙我們都知道 ZAB 協議很優秀。
剛才我聊的這些原因,會在潛意識層面促使初出茅廬的架構師,不自覺地追求架構的復雜性。然而,“復雜”在制造領域代表先進,在建筑領域代表領先,但在軟件領域,卻恰恰相反,代表的是“問題”。
軟件領域的復雜性體現在兩個方面:
1. 結構的復雜性
結構復雜的系統幾乎毫無例外具備兩個特點:
組成復雜系統的組件數量更多;
同時這些組件之間的關系也更加復雜。
我以圖形的方式來說明復雜性:
2 個組件組成的系統:
3 個組件組成的系統:
4 個組件組成的系統:
5 個組件組成的系統:
結構上的復雜性存在的第一個問題是,組件越多,就越有可能其中某個組件出現故障,從而導致系統故障。這個概率可以算出來,假設組件的故障率是 10%(有 10% 的時間不可用),那么有 3 個組件的系統可用性是(1-10%)×(1-10%)×(1-10%)= 72.9%,有 5 個組件的系統可用性是(1-10%)×(1-10%)×(1-10%)×(1-10%)×(1-10%)=59%,兩者的可用性相差 13%。
結構上的復雜性存在的第二個問題是,某個組件改動,會影響關聯的所有組件,這些被影響的組件同樣會繼續遞歸影響更多的組件。還以上面圖中 5 個組件組成的系統為例,組件 A 修改或者異常時,會影響組件 B/C/E,D 又會影響 E。這個問題會影響整個系統的開發效率,因為一旦變更涉及外部系統,需要協調各方統一進行方案評估、資源協調、上線配合。
結構上的復雜性存在的第三個問題是,定位一個復雜系統中的問題總是比簡單系統更加困難。首先是組件多,每個組件都有嫌疑,因此要逐一排查;其次組件間的關系復雜,有可能表現故障的組件并不是真正問題的根源。
2. 邏輯的復雜性
意識到結構的復雜性后,我們的第一反應可能就是“降低組件數量”,畢竟組件數量越少,系統結構越簡。最簡單的結構當然就是整個系統只有一個組件,即系統本身,所有的功能和邏輯都在這一個組件中實現。
不幸的是,這樣做是行不通的,原因在于除了結構的復雜性,還有邏輯的復雜性,即如果某個組件的邏輯太復雜,一樣會帶來各種問題。
邏輯復雜的組件,一個典型特征就是單個組件承擔了太多的功能。以電商業務為例,常見的功能有:商品管理、商品搜索、商品展示、訂單管理、用戶管理、支付、發貨、客服……把這些功能全部在一個組件中實現,就是典型的邏輯復雜性。
邏輯復雜幾乎會導致軟件工程的每個環節都有問題,假設現在淘寶將這些功能全部在單一的組件中實現,可以想象一下這個恐怖的場景:
系統會很龐大,可能是上百萬、上千萬的代碼規模,“clone”一次代碼要 30 分鐘。
幾十、上百人維護這一套代碼,某個“菜鳥”不小心改了一行代碼,導致整站崩潰。
需求像雪片般飛來,為了應對,開幾十個代碼分支,然后各種分支合并、各種分支覆蓋。
產品、研發、測試、項目管理不停地開會討論版本計劃,協調資源,解決沖突。
版本太多,每天都要上線幾十個版本,系統每隔 1 個小時重啟一次。
線上運行出現故障,幾十個人撲上去定位和處理,一間小黑屋都裝不下所有人,整個辦公區鬧翻天。
……
不用多說,肯定誰都無法忍受這樣的場景。
但是,為什么復雜的電路就意味更強大的功能,而復雜的架構卻有很多問題呢?根本原因在于電路一旦設計好后進入生產,就不會再變,復雜性只是在設計時帶來影響;而一個軟件系統在投入使用后,后續還有源源不斷的需求要實現,因此要不斷地修改系統,復雜性在整個系統生命周期中都有很大影響。
功能復雜的組件,另外一個典型特征就是采用了復雜的算法。復雜算法導致的問題主要是難以理解,進而導致難以實現、難以修改,并且出了問題難以快速解決。
以 ZooKeeper 為例,ZooKeeper 本身的功能主要就是選舉,為了實現分布式下的選舉,采用了 ZAB 協議,所以 ZooKeeper 功能雖然相對簡單,但系統實現卻比較復雜。相比之下,etcd 就要簡單一些,因為 etcd 采用的是 Raft 算法,相比 ZAB 協議,Raft 算法更加容易理解,更加容易實現。
綜合前面的分析,我們可以看到,無論是結構的復雜性,還是邏輯的復雜性,都會存在各種問題,所以架構設計時如果簡單的方案和復雜的方案都可以滿足需求,最好選擇簡單的方案。《UNIX 編程藝術》總結的 KISS(Keep It Simple, Stupid!)原則一樣適應于架構設計。
演化原則
演化原則宣言:“演化優于一步到位”。
軟件架構從字面意思理解和建筑結構非常類似,事實上“架構”這個詞就是建筑領域的專業名詞,維基百科對“軟件架構”的定義中有一段話描述了這種相似性:
從和目的、主題、材料和結構的聯系上來說,軟件架構可以和建筑物的架構相比擬。
例如,軟件架構描述的是一個軟件系統的結構,包括各個模塊,以及這些模塊的關系;建筑架構描述的是一幢建筑的結構,包括各個部件,以及這些部件如何有機地組成成一幢完美的建筑。
然而,字面意思上的相似性卻掩蓋了一個本質上的差異:建筑一旦完成(甚至一旦開建)就不可再變,而軟件卻需要根據業務的發展不斷地變化!
古埃及的吉薩大金字塔,4000 多年前完成的,到現在還是當初的架構。
中國的明長城,600 多年前完成的,現在保存下來的長城還是當年的結構。
美國白宮,1800 年建成,200 年來進行了幾次擴展,但整體結構并無變化,只是在旁邊的空地擴建或者改造內部的布局。
對比一下,我們來看看軟件架構。
Windows 系統的發展歷史:
如果對比 Windows 8 的架構和 Windows 1.0 的架構,就會發現它們其實是兩個不同的系統了!
Android 的發展歷史:
同樣,Android 6.0 和 Android 1.6 的差異也很大。
對于建筑來說,永恒是主題;而對于軟件來說,變化才是主題。軟件架構需要根據業務的發展而不斷變化。設計 Windows 和 Android 的人都是頂尖的天才,即便如此,他們也不可能在 1985 年設計出 Windows 8,不可能在 2009 年設計出 Android 6.0。
如果沒有把握“軟件架構需要根據業務發展不斷變化”這個本質,在做架構設計的時候就很容易陷入一個誤區:試圖一步到位設計一個軟件架構,期望不管業務如何變化,架構都穩如磐石。
為了實現這樣的目標,要么照搬業界大公司公開發表的方案;要么投入龐大的資源和時間來做各種各樣的預測、分析、設計。無論哪種做法,后果都很明顯:投入巨大,落地遙遙無期。更讓人沮喪的是,就算跌跌撞撞拼死拼活終于落地,卻發現很多預測和分析都是不靠譜的。
考慮到軟件架構需要根據業務發展不斷變化這個本質特點,軟件架構設計其實更加類似于大自然“設計”一個生物,通過演化讓生物適應環境,逐步變得更加強大:
首先,生物要適應當時的環境。
其次,生物需要不斷地繁殖,將有利的基因傳遞下去,將不利的基因剔除或者修復。
第三,當環境變化時,生物要能夠快速改變以適應環境變化;如果生物無法調整就被自然淘汰;新的生物會保留一部分原來被淘汰生物的基因。
軟件架構設計同樣是類似的過程:
首先,設計出來的架構要滿足當時的業務需要。
其次,架構要不斷地在實際應用過程中迭代,保留優秀的設計,修復有缺陷的設計,改正錯誤的設計,去掉無用的設計,使得架構逐漸完善。
第三,當業務發生變化時,架構要擴展、重構,甚至重寫;代碼也許會重寫,但有價值的經驗、教訓、邏輯、設計等(類似生物體內的基因)卻可以在新架構中延續。
架構師在進行架構設計時需要牢記這個原則,時刻提醒自己不要貪大求全,或者盲目照搬大公司的做法。應該認真分析當前業務的特點,明確業務面臨的主要問題,設計合理的架構,快速落地以滿足業務需要,然后在運行過程中不斷完善架構,不斷隨著業務演化架構。
即使是大公司的團隊,在設計一個新系統的架構時,也需要遵循演化的原則,而不應該認為團隊人員多、資源多,不管什么系統上來就要一步到位,因為業務的發展和變化是很快的,不管多牛的團隊,也不可能完美預測所有的業務發展和變化路徑。
本節總結
架構即決策。架構需要面向業務需求,并在各種資源(人、財、物、時、事)約束條件下去做權衡、取舍。而決策就會存在不確定性。采用一些高屋建瓴的設計原則有助于去消除不確定,去逼近解決問題的最優解。
1 合適原則
架構無優劣,但存合適性。“汝之蜜糖,吾之砒霜”;架構一定要匹配企業所在的業務階段;不要面向簡歷去設計架構,高大上的架構不等于適用;削足適履與打腫充胖都不符合合適原則;所謂合適,一定要匹配業務所處階段,能夠合理地將資源整合在一起并發揮出最大功效,并能夠快速落地。
2 簡單原則
"我沒有時間寫一封短信,所以只好寫一封長信"。其實,簡單比復雜更加困難。面對系統結構、業務邏輯和復雜性,我們可以編寫出復雜的系統,但在軟件領域,復雜代表的是“問題”。架構設計時如果簡單的方案和復雜的方案都可以滿足需求,最好選擇簡單的方案。但是,事實上,當軟件系統變得太復雜后,就會有人換一個思路進行重構、升級,將它重新變得簡單,這也是軟件開發的大趨勢。 簡單原則是一個樸素且偉大的原則,Google的MapReduce系統就采用了分而治之的思想,而背后就是將復雜問題轉化為簡單問題的典型案例。
3 演化原則
大到人類社會、自然生物,小到一個細胞,似乎都遵循這一普世原則,軟件架構也不例外。業務在發展、技術在創新、外部環境在變化,這一切都是在告誡架構師不要貪大求全,或者盲目照搬大公司的做法。應該認真分析當前業務的特點,明確業務面臨的主要問題,設計合理的架構,快速落地以滿足業務需要,然后在運行過程中不斷完善架構,不斷隨著業務演化架構。懷胎需要十月,早一月或晚一月都很危險。