《從0開始學架構》學習筆記(一)

来源:https://www.cnblogs.com/1693977889zz/archive/2020/02/27/12375336.html
-Advertisement-
Play Games

最近購買了極客時間推出的李運華的課程——《從0開始學架構》,本人通過聽音頻和文字閱讀,整理出相關筆記,目的是方便今後再次閱讀。再次感謝李運華的講解,購買鏈接:從0開始學架構 資深技術專家的實戰架構心法 開篇詞 | 照著做,你也能成為架構師 想成為架構師,夢想是美好的,但道路是曲折的,這應該不是個人天 ...


最近購買了極客時間推出的李運華的課程——《從0開始學架構》,本人通過聽音頻和文字閱讀,整理出相關筆記,目的是方便今後再次閱讀。再次感謝李運華的講解,購買鏈接:從0開始學架構 資深技術專家的實戰架構心法

開篇詞 | 照著做,你也能成為架構師

想成為架構師,夢想是美好的,但道路是曲折的,這應該不是個人天資的問題,而是架構設計本身的一些特性所致。

  1. 架構設計的關鍵思維是判斷和取捨,程式設計的關鍵思維是邏輯和實現。
  2. 架構設計沒有體系化的培訓和訓練機制
  3. 程式員對架構設計的理解存在很多舞曲(比如,架構一定具備高可用、高性能等)

這個專欄涵蓋:

  • 架構基礎
  • 高性能架構模式
  • 高可用架構模式
  • 可擴展架構模式
  • 架構實戰

通過本專欄的學習,你會收穫:

  • 清楚地理解架構的相關的概念、本質、目的
  • 掌握通用的架構設計原則
  • 掌握架構標準的設計流程
  • 深入理解已有的架構模式
  • 掌握架構演進和開源系統使用的一些技巧

 只要你努力,技術的夢想一定會實現。

精彩留言:

 

 

 

一、架構基礎

01 | 架構到底是指什麼?

梳理幾個有關係而又相似的概念:

A. 系統與子系統

系統泛指由一群有關聯的個體組成,根據某種規則運作,完成個別元件不能單獨完成的工作的群體。它的意思是“總體”“整體”或“聯盟”。(維基百科)

(註意:這裡的“能”指的是“能力”,系統能力與個體能力又本質差別,系統能力不是個體同理之和,而是生產了新的能力。比如,汽車能夠載重前行,發動機不能。)

B. 模塊與組件

軟體模塊(Module)是一套一致而互相有緊密關連的軟體組織。它分別包含了程式和數據結構兩部分。現代軟體開發往往利用模塊作為合成的單位。模塊的介面表達了由該模塊提供的功能和調用它時所需的元素。模塊是可能分開被編寫的單位。這使它們可再用和允許人員同時協作、編寫及研究不同的模塊。軟體組件定義為自包含的、可編程的、可重用的、與語言無關的軟體單元,軟體組件可以很容易被用於組裝應用程式中。(維基百科)

總結:模塊和組件都是系統的組成部分,只是從不同的角度拆分系統而已。

C. 框架與架構

軟體框架(Software framework)通常指的是為了實現某個業界標準或完成特定基本任務的軟體組件規範,也指為了實現某個軟體組件規範時,提供規範所要求之基礎功能的軟體產品。

提煉一下其中關鍵部分:

  1. 框架是組件規範:例如,MVC 就是一種最常見的開發規範,類似的還有 MVP、MVVM、J2EE 等框架。
  2. 框架提供基礎功能的產品:例如,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 | 複雜度來源:高性能

軟體系統中高性能帶來的複雜度主要體現在兩個方面:

  1. 單台電腦內部為了高性能帶來的複雜度
  2. 多太電腦集群為了高性能帶來的複雜度

單機複雜度

電腦內部複雜度最關鍵的地方就是操作系統。電腦性能的發展本質上是悠硬體發展驅動的,尤其是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.存儲高可用

存儲與計算相比,有一個本質上的區別:將數據從一臺機器搬到到另一臺機器,需要經過線路進行傳輸。

  1. 正常情況下的傳輸延遲:線路傳輸的速度是毫秒級別,同一機房內部能夠做到幾毫秒;分佈在不同地方的機房,傳輸耗時需要幾十甚至上百毫秒。(例如,從廣州機房到北京機房,穩定情況下 ping 延時大約是 50ms,不穩定情況下可能達到 1s 甚至更多。)
  2. 異常情況下的傳輸中斷:傳輸線路可能中斷、可能擁塞、可能異常(錯包、丟包),並且傳輸線路的故障時間一般都特別長,短的十幾分鐘,長的幾個小時都是可能的。

綜合分析,以上兩點都會導致系統的數據在某個時間點或者時間段是不一致的,而數據的不一致又會導致業務問題;但如果完全不做冗餘,系統的整體高可用又無法保證,所以存儲高可用的難點不在於如何備份數據,而在於如何減少或者規避數據不一致對業務造成的影響。分散式領域裡面有一個著名的 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”的架構了,你可以看下麵這張圖。

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

  1. 系統需要拆分出變化層和穩定層
  2. 需要設計變化層和穩定層之間的介面

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

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

  1. Component 和 Decorator 類。
  2. Decorator 類繼承 Component 類。
  3. 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 拆表,當業務需要查詢學歷為“本科”以上的用戶時,要去很多表查詢才能得到最終結果,怎麼保證性能?

綜合分析,協商式狀態決策在某些場景總是存在一些問題的。 軟體架構的歷史背景 20 世紀 60 年代中期開始爆發了第一次軟體危機,典型表現有軟體質量低下、項目無法如期完成、項目嚴重超支等,因為軟體而導致的重大事故時有發生。例如,1963 年美國(http://en.wikipedia.org/wiki/Mariner_1)的水手一號火箭發射失敗事故,就是因為一行 FORTRAN 代碼錯誤導致的。 這些語言讓程式員不需要關註機器底層的低級結構和邏輯,而只要關註具體的問題和業務即可。 Fortran:1955 年,名稱取自”FORmula TRANslator”,即公式翻譯器,由約翰·巴科斯(John Backus)等人發明。LISP:1958 年,名稱取自”LISt Processor”,即枚舉處理器,由約翰·麥卡錫(John McCarthy)等人發明。Cobol:1959 年,名稱取自”Common Business Oriented Language”,即通用商業導向語言,由葛麗絲·霍普(Grace Hopper)發明。 彙編語言(20 世紀 40 年代
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 條件判斷使用 v-if 指令: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <script src="https://cdn.staticfile.org/vue/2. ...
  • 通過實例來看下 Vue 構造器中需要哪些內容 測試時這段代碼我直接寫在index.html中 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 測試實例 - 菜鳥教程(runoob.com)</title> <script ...
  • 給項目的入口文件做點小改變: 【補充:編輯器建議使用vscode,我還沒裝,暫時先用phpstorm】 打開 APP.vue 文件,代碼如下(解釋在註釋中) <!-- 展示模板 --> <template> <div id="app"> <img src="./assets/logo.png"> < ...
  • 我之前是有安裝過npm的 使用淘寶 NPM 鏡像 $ npm install -g cnpm --registry=https://registry.npm.taobao.org 查看nmp版本 $ npm -v 使用 NPM 安裝vue $ cnpm install vue 在命令行中快速搭建大型 ...
  • 為什麼要用flex 基於css3簡單方便,更優雅的實現,瀏覽器相容性好,傳統的css實現一個div居中佈局要寫一堆代碼,而現在幾行代碼就搞定了,沒有理由不用flex。 相容性: Base Browsers: IE8.0+, Firefox40.0+, Chrome40.0+, iOS8.0+, An ...
  • 視頻線上率統計——基於驅動匯流排設備的領域驅動設計方法落地 [toc] 1.應用背景 本司智能信息箱產品是管控攝像頭電源,監控攝像頭視頻線上率的一個有效運維工具。因為統計視頻線上率是業主十分關心的問題,所以如何有效地統計視頻線上率是工程師需要努力解決的問題。 2.各視頻線上率統計方法比較 |方案|是否 ...
  • 前言 隨著分散式架構微服務的興起,DDD(領域驅動設計)、CQRS(命令查詢職責分離)、EDA(事件驅動架構)、ES(事件溯源)等概念也一併成為時下的火熱概念,我也在早些時候閱讀了一些大佬的分析文,學習相關概念,不過一直有種霧裡看花、似懂非懂的感覺。經過一段時間的學習和研究大佬的代碼後,自己設計實現 ...
  • 圖解Java設計模式之UML類圖 3.1 UML基本介紹 UML圖 UML類圖 3.1 UML基本介紹 1)UML – Unified modeling language UML(統一建模語言),是一種用於軟體系統分析和設計的語言工具,它用於幫助軟體開發人員進行思考和記錄思路的結果2)UML本身是一 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...