最近購買了極客時間推出的李運華的課程——《從0開始學架構》,本人通過聽音頻和文字閱讀,整理出相關筆記,目的是方便今后再次閱讀,再次感謝李運華的講解,購買鏈接:從0開始學架構 資深技術專家的實戰架構心法
開篇詞 | 照著做,你也能成為架構師
想成為架構師,夢想是美好的,但道路是曲折的,這應該不是個人天資的問題,而是架構設計本身的一些特性所致,
- 架構設計的關鍵思維是判斷和取舍,程式設計的關鍵思維是邏輯和實作,
- 架構設計沒有體系化的培訓和訓練機制
- 程式員對架構設計的理解存在很多舞曲(比如,架構一定具備高可用、高性能等)
這個專欄涵蓋:
- 架構基礎
- 高性能架構模式
- 高可用架構模式
- 可擴展架構模式
- 架構實戰
通過本專欄的學習,你會識訓:
- 清楚地理解架構的相關的概念、本質、目的
- 掌握通用的架構設計原則
- 掌握架構標準的設計流程
- 深入理解已有的架構模式
- 掌握架構演進和開源系統使用的一些技巧
只要你努力,技術的夢想一定會實作,
精彩留言:


一、架構基礎
01 | 架構到底是指什么?
梳理幾個有關系而又相似的概念:
A. 系統與子系統
系統泛指由一群有關聯的個體組成,根據某種規則運作,能完成個別元件不能單獨完成的作業的群體,它的意思是“總體”“整體”或“聯盟”,(維基百科)
(注意:這里的“能”指的是“能力”,系統能力與個體能力又本質差別,系統能力不是個體同理之和,而是生產了新的能力,比如,汽車能夠載重前行,發動機不能,)
B. 模塊與組件
軟體模塊(Module)是一套一致而互相有緊密關連的軟體組織,它分別包含了程式和資料結構兩部分,現代軟體開發往往利用模塊作為合成的單位,模塊的介面表達了由該模塊提供的功能和呼叫它時所需的元素,模塊是可能分開被撰寫的單位,這使它們可再用和允許人員同時協作、撰寫及研究不同的模塊,軟體組件定義為自包含的、可編程的、可重用的、與語言無關的軟體單元,軟體組件可以很容易被用于組裝應用程式中,(維基百科)
總結:模塊和組件都是系統的組成部分,只是從不同的角度拆分系統而已,
C. 框架與架構
軟體框架(Software framework)通常指的是為了實作某個業界標準或完成特定基本任務的軟體組件規范,也指為了實作某個軟體組件規范時,提供規范所要求之基礎功能的軟體產品,
提煉一下其中關鍵部分:
- 框架是組件規范:例如,MVC 就是一種最常見的開發規范,類似的還有 MVP、MVVM、J2EE 等框架,
- 框架提供基礎功能的產品:例如,Spring MVC 是 MVC 的開發框架,除了滿足 MVC 的規范,Spring 提供了很多基礎功能來幫助我們實作功能,包括注解(@Controller 等)、Spring Security、Spring JPA 等很多基礎功能,
軟體架構指軟體系統的“基礎結構”,創造這些基礎結構的準則,以及對這些結構的描述,
單純從定義的角度來看,框架和架構的區別還是比較明顯的,框架關注的是“規范”,架構關注的是“結構”,框架的英文是 Framework,架構的英文是 Architecture,Spring MVC 的英文檔案標題就是“Web MVC framework”,
“從業務邏輯的角度分解,“學生管理系統”的機構是:

從物理部署的角度分解,“學生管理系統”的架構是:

從開發規范的角度分解,“學生管理系統”可以采用標準的 MVC 框架來開發,因此架構又變成了 MVC 架構:

這些“架構”,都是“學生管理系統”正確的架構,只是從不同的角度來分解而已,這也是 IBM 的 RUP 將軟體架構視圖分為著名的“4+1 視圖”的原因,
重新定義架構
“軟體架構指軟體系統的頂層結構”——李運華
首先,“系統是一群關聯個體組成”,這些“個體”可以是“子系統”“模塊”“組件”等;架構需要明確系統包含哪些“個體”,
其次,系統中的個體需要“根據某種規則”運作,架構需要明確個體運作和協作的規則,
第三,維基百科定義的架構用到了“基礎結構”這個說法,我改為“頂層結構”,可以更好地區分系統和子系統,避免將系統架構和子系統架構混淆在一起導致架構層次混亂,
精選留言:




02 | 架構設計的歷史背景
機器語言(1940 年之前)1.機器語言(1940 年之前)
101100000000000000000011 000001010000000000110000 001011010000000000000101匯編語言(20 世紀 40 年代)
2.匯編語言(20 世紀 40 年代)
為了解決機器語言撰寫、閱讀、修改復雜的問題,匯編語言應運而生,匯編語言又叫“符號語言”,用助記符代替機器指令的操作碼,用地址符號(Symbol)或標號(Label)代替指令或運算元的地址,為了解決機器語言撰寫、閱讀、修改復雜的問題,匯編語言應運而生,匯編語言又叫“符號語言”,用助記符代替機器指令的操作碼,用地址符號(Symbol)或標號(Label)代替指令或運算元的地址,
機器語言:1000100111011000 匯編語言:mov ax,bx
.section .data a: .int 10 b: .int 20 format: .asciz "%d\n" .section .text .global _start _start: movl a, %edx addl b, %edx pushl %edx pushl $format call printf movl $0, (%esp) call exit除了撰寫本身復雜,還有另外一個復雜的地方在于:不同 CPU 的匯編指令和結構是不同的,
除了撰寫本身復雜,還有另外一個復雜的地方在于:不同 CPU 的匯編指令和結構是不同的,
高級語言(20 世紀 50 年代)3.高級語言(20 世紀 50 年代)
- Fortran:1955 年,名稱取自”FORmula TRANslator”,即公式翻譯器,由約翰·巴科斯(John Backus)等人發明,
- LISP:1958 年,名稱取自”LISt Processor”,即列舉處理器,由約翰·麥卡錫(John McCarthy)等人發明,
- Cobol:1959 年,名稱取自”Common Business Oriented Language”,即通用商業導向語言,由葛麗絲·霍普(Grace Hopper)發明,
這些語言讓程式員不需要關注機器底層的低級結構和邏輯,而只要關注具體的問題和業務即可,
4.第一次軟體危機與結構化程式設計(20 世紀 60 年代~20 世紀 70 年代)
20 世紀 60 年代中期開始爆發了第一次軟體危機,典型表現有軟體質量低下、專案無法如期完成、專案嚴重超支等,因為軟體而導致的重大事故時有發生,例如,1963 年美國(http://en.wikipedia.org/wiki/Mariner_1)的水手一號火箭發射失敗事故,就是因為一行 FORTRAN 代碼錯誤導致的,
結構化程式方法成為了 20 世紀 70 年代軟體開發的潮流,
5.第二次軟體危機與面向物件(20 世紀 80 年代)
結構化編程的風靡在一定程度上緩解了軟體危機,然而隨著硬體的快速發展,業務需求越來越復雜,以及編程應用領域越來越廣泛,第二次軟體危機很快就到來了,第二次軟體危機的根本原因還是在于軟體生產力遠遠跟不上硬體和業務的發展,
第一次軟體危機的根源在于軟體的“邏輯”變得非常復雜,而第二次軟體危機主要體現在軟體的“擴展”變得非常復雜,
結構化程式設計雖然能夠解決(也許用“緩解”更合適)軟體邏輯的復雜性,但是對于業務變化帶來的軟體擴展卻無能為力,軟體領域迫切希望找到新的銀彈來解決軟體危機,在這種背景下,面向物件的思想開始流行起來,
軟體架構的歷史背景
雖然早在 20 世紀 60 年代,戴克斯特拉這位上古大神就已經涉及軟體架構這個概念了,但軟體架構真正流行卻是從 20 世紀 90 年代開始的,由于在 Rational 和 Microsoft 內部的相關活動,軟體架構的概念開始越來越流行了,
軟體架構的出現有其歷史必然性,
20 世紀 60 年代第一次軟體危機引出了“結構化編程”,創造了“模塊”概念;
20 世紀 80 年代第二次軟體危機引出了“面向物件編程”,創造了“物件”概念;
20 世紀 90 年代“軟體架構”開始流行,創造了“組件”概念,
我們可以看到,“模塊”“物件”“組件”本質上都是對達到一定規模的軟體進行拆分,差別只是在于隨著軟體的復雜度不斷增加,拆分的粒度越來越粗,拆分的層次越來越高,《人月神話》中提到的 IBM 360 大型系統,開發時間是 1964 年,那個時候結構化編程都還沒有提出來,更不用說軟體架構了,如果 IBM 360 系統放在 20 世紀 90 年代開發,不管是質量還是效率、成本,都會比 1964 年開始做要好得多,當然,這樣的話我們可能就看不到《人月神話》了,



03 | 架構設計的目的
架構設計的真正目的究竟是什么?
架構設計的主要目的是為了解決軟體系統復雜度帶來的問題,
架構設計并不是要面面俱到,不需要每個架構都具備高性能、高可用、高擴展等特點,而是要識別出復雜點然后有針對性地解決問題,
簡單的復雜度分析案例:
假設我們需要設計一個學生管理系統:
- 性能:一個學校大約1~2萬人,學生管理系統的訪問率并不高,因此性能要求并不高,存盤用MySQL完全能勝任,快取可以不用,Web服務器用Nginx綽綽有余,
- 可擴展性:學生管理系統的功比較穩定,可擴展性不強,
- 高可用:宕機2小時對學生影響可能不大,可以不做負載均衡,不用考慮異地多活這類復雜的方案,學生資訊的存盤比較重要,因此需要考慮存盤高可靠,還要考慮:機器故障、機房故障,針對機器故障可設計MySQL同機房主備方案;針對機房故障,可考慮設計MySQL跨機房同步方案,
- 安全性:基本滿足:Nginx提供ACL控制、用戶賬號密碼管理、資料庫訪問權限控制,
- 成本:服務器使用數量不多,

精選留言:



04 | 復雜度來源:高性能
軟體系統中高性能帶來的復雜度主要體現在兩個方面:
- 單臺計算機內部為了高性能帶來的復雜度
- 多太計算機集群為了高性能帶來的復雜度
單機復雜度
計算機內部復雜度最關鍵的地方就是作業系統,計算機性能的發展本質上是悠硬體發展驅動的,尤其是CPU的性能發展,
作業系統和性能相關的就是行程和執行緒,
- 最早的計算機沒有作業系統,只有輸入、計算和輸出功能,這樣的處理性能效率很低,
- 為解決手工操作帶來的低效,批處理應運而生,性能就有了很大的提升,(缺點:計算機一次只能執行一個任務,如果某個任務需要從I/O設備(例如磁帶)讀取大量的資料,在I/O操作的程序中,CPU其實是空閑的,浪費了部分資源)
- 為進一步提升性能,人們發明了“行程”,用行程來對應一個任務,每個任務都有自己的獨立記憶體空間,行程間互不相關,由作業系統來進行調度,(此時的CPU還沒有多核和多執行緒的概念,為了達到多行程并行的目的,采取了分時的方式,同時,行程間通信的各種方式被設計出來,包括管道、訊息佇列、信號量、共享存盤等,多行程讓多任務能夠并行處理,但本身缺點:單個行程內部只能串行處理,而實際上很多行程內部的子任務并不要求是嚴格按照時間順序來執行的,也需要并行處理,)
- 為解決行程的缺點,人們發明了執行緒,(同時,為保證資料的正確性,又發明了互斥鎖機制,有了多執行緒后,作業系統調度的最小單位就變成了執行緒,而行程變成了作業系統分配資源的最小單位,多行程多執行緒雖讓多任務并行處理的性能大大提升,但本質還是分時系統,并不能實作真正意義上的多任務并行)
- 多個CPU能夠同時執行計算任務,實作真正意義上的多任務并行:目前這樣的解決方案有 3 種:SMP(Symmetric Multi-Processor,對稱多處理器結構)、NUMA(Non-Uniform Memory Access,非一致存盤訪問結構)、MPP(Massive Parallel Processing,海量并行處理結構),
作業系統發展到現在,如果我們要完成一個高性能的軟體系統,需要考慮如多行程、多執行緒、行程間通信、多執行緒并發等技術點,而且這些技術并不是最新的就是最好的,也不是非此即彼的選擇,在做架構設計的時候,需要花費很大的精力來結合業務進行分析、判斷、選擇、組合,這個程序同樣很復雜,
雖然,計算機作業系統和硬體的發展已經很快了,但是在進入互聯網時代后,業務的發展速度遠遠更超前了,例如:
- 2016 年“雙 11”支付寶每秒峰值達 12 萬筆支付,
- 2017 年春節微信紅包收發紅包每秒達到 76 萬個
單機的性能無法支撐業務需求的增長,必須采用機器集群的方式來達到高性能,但是,通過大量的機器來提升性能,并不僅僅是增加機器這么簡單,下面是針對幾種方式的加單分析:
1.任務分配:
任務分配的意思是指,每臺機器都可以處理完整的業務任務,不同的任務分配到不同的機器上執行,
例如:從最簡單的一臺服務器變兩臺服務器:

此時架構上明顯要復雜多了,主要體現在:
- 需要增加一個任務分配器,這個分配器可能是硬體網路設備(例如,F5、交換機等),可能是軟體網路設備(例如,LVS),也可能是負載均衡軟體(例如,Nginx、HAProxy),還可能是自己開發的系統,選擇合適的任務分配器也是一件復雜的事情,需要綜合考慮性能、成本、可維護性、可用性等各方面的因素,
- 任務分配器和真正的業務服務器之間有連接和互動(即圖中任務分配器到業務服務器的連接線),需要選擇合適的連接方式,并且對連接進行管理,例如,連接建立、連接檢測、連接中斷后如何處理等,
- 任務分配器需要增加分配演算法,例如,是采用輪詢演算法,還是按權重分配,又或者按照負載進行分配,如果按照服務器的負載進行分配,則業務服務器還要能夠上報自己的狀態給任務分配器,
假設性能要求繼續提高,要求每秒提升到10萬次:

這個架構比 2 臺業務服務器的架構要復雜,主要體現在:
- 任務分配器從 1 臺變成了多臺(對應圖中的任務分配器 1 到任務分配器 M),這個變化帶來的復雜度就是需要將不同的用戶分配到不同的任務分配器上(即圖中的虛線“用戶分配”部分),常見的方法包括 DNS 輪詢、智能 DNS、CDN(Content Delivery Network,內容分發網路)、GSLB 設備(Global Server Load Balance,全域負載均衡)等,
- 任務分配器和業務服務器的連接從簡單的“1 對多”(1 臺任務分配器連接多臺業務服務器)變成了“多對多”(多臺任務分配器連接多臺業務服務器)的網狀結構,
- 機器數量從 3 臺擴展到 30 臺(一般任務分配器數量比業務服務器要少,這里我們假設業務服務器為 25 臺,任務分配器為 5 臺),狀態管理、故障處理復雜度也大大增加,
上面這兩個例子都是以業務處理為例,實際上“任務”涵蓋的范圍很廣,可以指完整的業務處理,也可以單指某個具體的任務,例如,“存盤”“運算”“快取”等都可以作為一項任務,因此存盤系統、運算系統、快取系統都可以按照任務分配的方式來搭建架構,此外,“任務分配器”也并不一定只能是物理上存在的機器或者者一個獨立運行的程式,也可以是嵌入在其他程式中的演算法,例如 Memcache 的集群架構,

2.任務分解
通過任務分配的方式,能夠突破單臺機器處理性能的瓶頸,通過增加更多的機器來滿足業務的性能需求,但如果業務本身也越來越復雜,單純只通過任務分配的方式來擴展性能,收益會越來越低,
為了能夠繼續提升性能,我們需要采取第二種方式:任務分解,

那為何通過任務分解就能夠提升性能呢?
1.簡單的系統更加容易做到高性能
系統的功能越簡單,影響性能的點就越少,就更加容易進行有針對性的優化,而系統很復雜的情況下,首先是比較難以找到關鍵性能點,因為需要考慮和驗證的點太多;其次是即使花費很大力氣找到了,修改起來也不容易,
2.可以針對單個任務進行擴展
當各個邏輯任務分解到獨立的子系統后,整個系統的性能瓶頸更加容易發現,而且發現后只需要針對有瓶頸的子系統進行性能優化或者提升,不需要改動整個系統,風隙訓小很多,

雖然系統拆分可能在某種程度上能提升業務處理性能,但提升性能也是有限的,理論上的性能是有一個上限的,系統拆分能夠讓性能逼近這個極限,但無法突破這個極限,因此,任務分解帶來的性能收益是有一個度的,并不是任務分解越細越好,而對于架構設計來說,如何把握這個粒度就非常關鍵了,
精選留言:



05 | 復雜度來源:高可用
高可用:系統無中斷地執行其功能的能力,代表系統的可用性程度,是進行系統設計時的準則之一,
“無中斷”的干擾因素有很多:硬體出現故障、軟體Bug、外部環境的不可控,不可避免性,地震水災等,所以,系統的高可用方案五花八門,但是本質都是通過“冗余”來實作高可用,
通俗點來講,就是一臺機器不夠就兩臺,兩臺不夠就四臺;一個機房可能斷電,那就部署兩個機房;一條通道可能故障,那就用兩條,兩條不夠那就用三條(移動、電信、聯通一起上),
高可用的“冗余”解決方案,單純從形式上來看,和之前講的高性能是一樣的,都是通過增加更多機器來達到目的,但其實本質上是有根本區別的:高性能增加機器目的在于“擴展”處理性能;高可用增加機器目的在于“冗余”處理單元,
1.計算高可用
這里的“計算”指的是業務的邏輯處理,計算有一個特點就是無論在哪臺機器上進行計算,同樣的演算法和輸入資料,產出的結果都是一樣的,所以將計算從一臺機器遷移到另外一臺機器,對業務并沒有什么影響,
單機變雙機的簡單架構示意圖:

這個雙機的架構圖和上期“高性能”講到的雙機架構圖是一樣的,因此復雜度也是類似的,具體表現為:
- 需要增加一個任務分配器,選擇合適的任務分配器也是一件復雜的事情,需要綜合考慮性能、成本、可維護性、可用性等各方面因素,
- 任務分配器和真正的業務服務器之間有連接和互動,需要選擇合適的連接方式,并且對連接進行管理,例如,連接建立、連接檢測、連接中斷后如何處理等,
- 任務分配器需要增加分配演算法,例如,常見的雙機演算法有主備、主主,主備方案又可以細分為冷備、溫備、熱備,
上面這個示意圖只是簡單的雙機架構,再看一個復雜一點的高可用集群架構:

這個高可用集群相比雙機來說,分配演算法更加復雜,可以是 1 主 3 備、2 主 2 備、3 主 1 備、4 主 0 備,具體應該采用哪種方式,需要結合實際業務需求來分析和判斷,并不存在某種演算法就一定優于另外的演算法,例如,ZooKeeper 采用的就是 1 主多備,而 Memcached 采用的就是全主 0 備,
2.存盤高可用
存盤與計算相比,有一個本質上的區別:將資料從一臺機器搬到到另一臺機器,需要經過線路進行傳輸,
- 正常情況下的傳輸延遲:線路傳輸的速度是毫秒級別,同一機房內部能夠做到幾毫秒;分布在不同地方的機房,傳輸耗時需要幾十甚至上百毫秒,(例如,從廣州機房到北京機房,穩定情況下 ping 延時大約是 50ms,不穩定情況下可能達到 1s 甚至更多,)
- 例外情況下的傳輸中斷:傳輸線路可能中斷、可能擁塞、可能例外(錯包、丟包),并且傳輸線路的故障時間一般都特別長,短的十幾分鐘,長的幾個小時都是可能的,
綜合分析,以上兩點都會導致系統的資料在某個時間點或者時間段是不一致的,而資料的不一致又會導致業務問題;但如果完全不做冗余,系統的整體高可用又無法保證,所以存盤高可用的難點不在于如何備份資料,而在于如何減少或者規避資料不一致對業務造成的影響,分布式領域里面有一個著名的 CAP 定理,從理論上論證了存盤高可用的復雜度,也就是說,存盤高可用不可能同時滿足“一致性、可用性、磁區容錯性”,最多滿足其中兩個,這就要求我們在做架構設計時結合業務進行取舍,
高可用狀態決策
一個本質的矛盾:通過冗余來實作的高可用系統,狀態決策本質上就不可能做到完全正確,下面我基于幾種常見的決策方式進行詳細分析,
1. 獨裁式

獨裁式的決策方式:
- 優點:不會出現決策混亂的問題,因為只有一個決策者,
- 缺點:當決策者本身故障時,整個系統就無法實作準確的狀態決策,如果決策者本身又做一套狀態決策,那就陷入一個遞回的死回圈了,
2. 協商式
協商式決策指的是兩個獨立的個體通過交流資訊,然后根據規則進行決策,最常用的協商式決策就是主備決策,

這個架構的基本協商規則可以設計成:
- 2 臺服務器啟動時都是備機,
- 2 臺服務器建立連接,
- 2 臺服務器交換狀態資訊,
- 某 1 臺服務器做出決策,成為主機;另一臺服務器繼續保持備機身份,
協商式決策的架構不復雜,規則也不復雜,其難點在于,如果兩者的資訊交換出現問題(比如主備連接中斷),此時狀態決策應該怎么做,如果備機在連接中斷的情況下認為主機故障,那么備機需要升級為主機,
下面分為三種情況:
第一種情況:如果備機在連接中斷的情況下,實際上主機并沒有故障,那么系統就出現了兩個主機,這與設計初衷(1 主 1 備)是不符合的,

第二種情況:如果備機在連接中斷的情況下不認為主機故障,則此時如果主機真的發生故障,那么系統就沒有主機了,這同樣與設計初衷(1 主 1 備)是不符合的,

第三種情況:如果為了規避連接中斷對狀態決策帶來的影響,可以增加更多的連接,
例如,雙連接、三連接,這樣雖然能夠降低連接中斷對狀態帶來的影響(注意:只能降低,不能徹底解決),但同時又引入了這幾條連接之間資訊取舍的問題,即如果不同連接傳遞的資訊不同,應該以哪個連接為準?實際上這也是一個無解的答案,無論以哪個連接為準,在特定場景下都可能存在問題,

綜合分析,協商式狀態決策在某些場景總是存在一些問題的,
3. 民主式
民主式決策指的是多個獨立的個體通過投票的方式來進行狀態決策,例如,ZooKeeper 集群在選舉 leader 時就是采用這種方式,

民主式決策和協商式決策比較類似,其基礎都是獨立的個體之間交換資訊,每個個體做出自己的決策,然后按照“多數取勝”的規則來確定最終的狀態,不同點在于民主式決策比協商式決策要復雜得多,ZooKeeper 的選舉演算法 Paxos,絕大部分人都看得云里霧里,更不用說用代碼來實作這套演算法了,
除了演算法復雜,民主式決策還有一個固有的缺陷:腦裂,

從圖中可以看到,正常狀態的時候,節點 5 作為主節點,其他節點作為備節點;當連接發生故障時,節點 1、節點 2、節點 3 形成了一個子集群,節點 4、節點 5 形成了另外一個子集群,這兩個子集群的連接已經中斷,無法進行資訊交換,按照民主決策的規則和演算法,兩個子集群分別選出了節點 2 和節點 5 作為主節點,此時整個系統就出現了兩個主節點,這個狀態違背了系統設計的初衷,兩個主節點會各自做出自己的決策,整個系統的狀態就混亂了,
為了解決腦裂問題,民主式決策的系統一般都采用“投票節點數必須超過系統總節點數一半”規則來處理,
如圖中那種情況,節點 4 和節點 5 形成的子集群總節點數只有 2 個,沒有達到總節點數 5 個的一半,因此這個子集群不會進行選舉,這種方式雖然解決了腦裂問題,但同時降低了系統整體的可用性,即如果系統不是因為腦裂問題導致投票節點數過少,而真的是因為節點故障(例如,節點 1、節點 2、節點 3 真的發生了故障),此時系統也不會選出主節點,整個系統就相當于宕機了,盡管此時還有節點 4 和節點 5 是正常的,
綜合分析,無論采取什么樣的方案,狀態決策都不可能做到任何場景下都沒有問題,但完全不做高可用方案又會產生更大的問題,如何選取適合系統的高可用方案,也是一個復雜的分析、判斷和選擇的程序,
精選留言:



06 | 復雜度來源:可擴展性
可擴展性指系統為了應對將來需求變化而提供的一種擴展能力,當有新的需求出現時,系統不需要或者僅需要少量修改就可以支持,無須整個系統重構或者重建,
- 軟體系統固有的多變性,新的需求總會不斷提出來,因此可擴展性顯得尤其重要,
- 在軟體開發領域,面向物件思想的提出,就是為了解決可擴展性帶來的問題,
- 設計模式,更是將可擴展性做到了極致,
設計具備良好可擴展性的系統,有兩個基本條件:正確預測變化、完美封裝變化,
預測變化的復雜性在于:
- 不能每個設計點都考慮可擴展性,
- 不能完全不考慮可擴展性,
- 所有的預測都存在出錯的可能性,
對于架構師來說,如何把握預測的程度和提升預測結果的準確性,是一件很復雜的事情,而且沒有通用的標準可以簡單套上去,更多是靠自己的經驗、直覺,沒有明確標準,不同的人理解和判斷有偏差,而最終又只能選擇一個判斷,
應對變化
即使是經驗豐富的架構師,在預測到所有的變化的可能性,也不能保證可擴展性就很容易得到實作,預測準確,方案不適合,也是一件很麻煩的事情,

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

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

無論采取哪種形式,通過剝離變化層和穩定層的方式應對變化,都會帶來兩個主要的復雜性相關的問題,
- 系統需要拆分出變化層和穩定層
- 需要設計變化層和穩定層之間的介面
第二種常見的應對變化的方案是提煉出一個“抽象層”和一個“實作層”,抽象層是穩定的,實作層可以根據具體業務需要定制開發,當加入新的功能時,只需要增加新的實作,無須修改抽象層,這種方案典型的實踐就是設計模式和規則引擎,考慮到絕大部分技術人員對設計模式都非常熟悉,我以設計模式為例來說明這種方案的復雜性,以設計模式的“裝飾者”模式來分析,下面是裝飾者模式的類關系圖,

圖中的 Component 和 Decorator 就是抽象出來的規則,這個規則包括幾部分:
- Component 和 Decorator 類,
- Decorator 類繼承 Component 類,
- Decorator 類聚合了 Component 類,
這個規則一旦抽象出來后就固定了,不能輕易修改,例如,把規則 3 去掉,就無法實作裝飾者模式的目的了,裝飾者模式相比傳統的繼承來實作功能,確實靈活很多,
例如,《設計模式》中裝飾者模式的樣例“TextView”類的實作,用了裝飾者之后,能夠靈活地給 TextView 增加額外更多功能,比如可以增加邊框、滾動條、背景圖片等,這些功能上的組合不影響規則,只需要按照規則實作即可,但裝飾者模式相對普通的類實作模式,明顯要復雜多了,本來一個函式或者一個類就能搞定的事情,現在要拆分成多個類,而且多個類之間必須按照裝飾者模式來設計和呼叫,
精選留言:




07 | 復雜度來源:低成本、安全、規模
前面已經講了高性能、高可用和可擴展性,今天我來聊聊復雜度另外三個來源低成本、安全和規模,
1.低成本
當我們設計“高性能”“高可用”的架構時,通用的手段都是增加更多服務器來滿足“高性能”和“高可用”的要求;而低成本正好與此相反,我們需要減少服務器的數量才能達成低成本的目標,因此,低成本本質上是與高性能和高可用沖突的,所以低成本很多時候不會是架構設計的首要目標,而是架構設計的附加約束,
低成本給架構設計帶來的主要復雜度體現在,往往只有“創新”才能達到低成本目標,這里的“創新”既包括開創一個全新的技術領域(這個要求對絕大部分公司太高),也包括引入新技術,如果沒有找到能夠解決自己問題的新技術,那么就真的需要自己創造新技術了,
類似的新技術例子很多:
- NoSQL(Memcache、Redis 等)的出現是為了解決關系型資料庫無法應對高并發訪問帶來的訪問壓力,
- 全文搜索引擎(Sphinx、Elasticsearch、Solr)的出現是為了解決關系型資料庫 like 搜索的低效的問題,
- Hadoop 的出現是為了解決傳統檔案系統無法應對海量資料存盤和計算的問題,
再來舉幾個業界類似的例子:
- Facebook 為了解決 PHP 的低效問題,剛開始的解決方案是 HipHop PHP,可以將 PHP 語言翻譯為 C++ 語言執行,后來改為 HHVM,將 PHP 翻譯為位元組碼然后由虛擬機執行,和 Java 的 JVM 類似,
- 新浪微博將傳統的 Redis/MC + MySQL 方式,擴展為 Redis/MC + SSD Cache + MySQL 方式,SSD Cache 作為 L2 快取使用,既解決了 MC/Redis 成本過高,容量小的問題,也解決了穿透 DB 帶來的資料庫訪問壓力(來源:http://www.infoq.com/cn/articles/weibo-platform-archieture ),
- Linkedin 為了處理每天 5 千億的事件,開發了高效的 Kafka 訊息系統,
- 其他類似將 Ruby on Rails 改為 Java、Lua + redis 改為 Go 語言實作的例子還有很多,
無論是引入新技術,還是自己創造新技術,都是一件復雜的事情,引入新技術的主要復雜度在于需要去熟悉新技術,并且將新技術與已有技術結合起來;創造新技術的主要復雜度在于需要自己去創造全新的理念和技術,并且新技術跟舊技術相比,需要有質的飛躍,
2.安全
安全本身是一個龐大而又復雜的技術領域,并且一旦出問題,對業務和企業形象影響非常大,例如:
- 2016 年雅虎爆出史上最大規模資訊泄露事件,逾 5 億用戶資料在 2014 年被竊取,2
- 016 年 10 月美國遭史上最大規模 DDoS 攻擊,東海岸網站集體癱瘓,
- 2013 年 10 月,為全國 4500 多家酒店提供網路服務的浙江慧達驛站網路有限公司,因安全漏洞問題,致 2 千萬條入住酒店的客戶資訊泄露,由此導致很多敲詐、家庭破裂的后續事件,
正因為經常能夠看到或者聽到各類安全事件,所以大部分技術人員和架構師,對安全這部分會多一些了解和考慮,
從技術的角度來講,安全可以分為兩類:
1.功能上的安全:
例如,常見的 XSS 攻擊、CSRF 攻擊、SQL 注入、Windows 漏洞、密碼破解等,本質上是因為系統實作有漏洞,黑客有了可乘之機,
從實作的角度來看,功能安全更多地是和具體的編碼相關,與架構關系不大,現在很多開發框架都內嵌了常見的安全功能,能夠大大減少安全相關功能的重復開發,但框架只能預防常見的安全漏洞和風險(常見的 XSS 攻擊、CSRF 攻擊、SQL 注入等),無法預知新的安全問題,而且框架本身很多時候也存在漏洞(例如,流行的 Apache Struts2 就多次爆出了呼叫遠程代碼執行的高危漏洞,給整個互聯網都造成了一定的恐慌),
所以功能安全是一個逐步完善的程序,而且往往都是在問題出現后才能有針對性的提出解決方案,我們永遠無法預測系統下一個漏洞在哪里,也不敢說自己的系統肯定沒有任何問題,換句話講,功能安全其實也是一個“攻”與“防”的矛盾,只能在這種攻防大戰中逐步完善,不可能在系統架構設計的時候一勞永逸地解決,
2.架構上的安全:
架構設計時需要特別關注架構安全,尤其是互聯網時代,理論上來說系統部署在互聯網上時,全球任何地方都可以發起攻擊,
傳統的架構安全主要依靠防火墻,防火墻最基本的功能就是隔離網路,通過將網路劃分成不同的區域,制定出不同區域之間的訪問控制策略來控制不同信任程度區域間傳送的資料流,例如,下圖是一個典型的銀行系統的安全架構,

從圖中你可以看到,整個系統根據不同的磁區部署了多個防火墻來保證系統的安全,
防火墻的功能雖然強大,但性能一般,所以在傳統的銀行和企業應用領域應用較多,但在互聯網領域,防火墻的應用場景并不多,因為互聯網的業務具有海量用戶訪問和高并發的特點,防火墻的性能不足以支撐;尤其是互聯網領域的 DDoS 攻擊,輕則幾 GB,重則幾十 GB,2016 年知名安全研究人員布萊恩·克萊布斯(Brian Krebs)的安全博客網站遭遇 DDoS 攻擊,攻擊帶寬達 665Gbps,是目前在網路犯罪領域已知的最大的拒絕服務攻擊,這種規模的攻擊,如果用防火墻來防,則需要部署大量的防火墻,成本會很高,例如,中高端一些的防火墻價格 10 萬元,每秒能抗住大約 25GB 流量,那么應對這種攻擊就需要將近 30 臺防火墻,成本將近 300 萬元,這還不包括維護成本,而這些防火墻設備在沒有發生攻擊的時候又沒有什么作用,也就是說,如果花費幾百萬元來買這么一套設備,有可能幾年都發揮不了任何作用,就算是公司對錢不在乎,一般也不會堆防火墻來防 DDoS 攻擊,因為 DDoS 攻擊最大的影響是大量消耗機房的出口總帶寬,不管防火墻處理能力有多強,當出口帶寬被耗盡時,整個業務在用戶看來就是不可用的,因為用戶的正常請求已經無法到達系統了,防火墻能夠保證內部系統不受沖擊,但用戶也是進不來的,對于用戶來說,業務都已經受到影響了,至于是因為用戶自己進不去,還是因為系統出故障,用戶其實根本不會關心,
基于上述原因,互聯網系統的架構安全目前并沒有太好的設計手段來實作,更多地是依靠運營商或者云服務商強大的帶寬和流量清洗的能力,較少自己來設計和實作,
3.規模
很多企業級的系統,既沒有高性能要求,也沒有雙中心高可用要求,也不需要什么擴展性,但往往我們一說到這樣的系統,很多人都會脫口而出:這個系統好復雜!為什么這樣說呢?關鍵就在于這樣的系統往往功能特別多,邏輯分支特別多,特別是有的系統,發展時間比較長,不斷地往上面疊加功能,后來的人由于不熟悉整個發展歷史,可能連很多功能的應用場景都不清楚,或者細節根本無法掌握,面對的就是一個黑盒系統,看不懂、改不動、不敢改、修不了,復雜度自然就感覺很高了,
規模帶來復雜度的主要原因就是“量變引起質變”,當數量超過一定的閾值后,復雜度會發生質的變化,常見的規模帶來的復雜度有:
1. 功能越來越多,導致系統復雜度指數級上升
例如,某個系統開始只有 3 大功能,后來不斷增加到 8 大功能,雖然還是同一個系統,但復雜度已經相差很大了,具體相差多大呢?我以一個簡單的抽象模型來計算一下,假設系統間的功能都是兩兩相關的,系統的復雜度 = 功能數量 + 功能之間的連接數量,通過計算我們可以看出:
- 3 個功能的系統復雜度 = 3 + 3 = 6
- 8 個功能的系統復雜度 = 8 + 28 = 36
可以看出,具備 8 個功能的系統的復雜度不是比具備 3 個功能的系統的復雜度多 5,而是多了 30,基本是指數級增長的,主要原因在于隨著系統功能數量增多,功能之間的連接呈指數級增長,下圖形象地展示了功能數量的增多帶來了復雜度,


通過肉眼就可以很直觀地看出,具備 8 個功能的系統復雜度要高得多,
2. 資料越來越多,系統復雜度發生質變
與功能類似,系統資料越來越多時,也會由量變帶來質變,最近幾年火熱的“大資料”就是在這種背景下誕生的,大資料單獨成為了一個熱門的技術領域,主要原因就是資料太多以后,傳統的資料收集、加工、存盤、分析的手段和工具已經無法適應,必須應用新的技術才能解決,目前的大資料理論基礎是 Google 發表的三篇大資料相關論文,其中 Google File System 是大資料檔案存盤的技術理論,Google Bigtable 是列式資料存盤的技術理論,Google MapReduce 是大資料運算的技術理論,這三篇技術論文各自開創了一個新的技術領域,
即使我們的資料沒有達到大資料規模,資料的增長也可能給系統帶來復雜性,最典型的例子莫過于使用關系資料庫存盤資料,我以 MySQL 為例,MySQL 單表的資料因不同的業務和應用場景會有不同的最優值,但不管怎樣都肯定是有一定的限度的,一般推薦在 5000 萬行左右,如果因為業務的發展,單表資料達到了 10 億行,就會產生很多問題,例如:
- 添加索引會很慢,可能需要幾個小時,這幾個小時內資料庫表是無法插入資料的,相當于業務停機了,
- 修改表結構和添加索引存在類似的問題,耗時可能會很長,
- 即使有索引,索引的性能也可能會很低,因為資料量太大,
- 資料庫備份耗時很長,
- ……
因此,當 MySQL 單表資料量太大時,我們必須考慮將單表拆分為多表,這個拆分程序也會引入更多復雜性,例如:
拆表的規則是什么?以用戶表為例:是按照用戶 id 拆分表,還是按照用戶注冊時間拆表?拆完表后查詢如何處理?以用戶表為例:假設按照用戶 id 拆表,當業務需要查詢學歷為“本科”以上的用戶時,要去很多表查詢才能得到最終結果,怎么保證性能?



轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/12524.html
標籤:架構設計
上一篇:猴子都能懂的資料庫避坑指南
