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

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

開篇詞 | 照着作,你也能成爲架構師redis

想成爲架構師,夢想是美好的,但道路是曲折的,這應該不是我的天資的問題,而是架構設計自己的一些特性所致。算法

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

這個專欄涵蓋:數據庫

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

經過本專欄的學習,你會收穫:編程

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

 只要你努力,技術的夢想必定會實現。設計模式

精彩留言:緩存

 

 

 

1、架構基礎安全

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.任務分配:

任務分配的意思是指,每臺機器均可以處理完整的業務任務,不一樣的任務分配到不一樣的機器上執行。

例如:從最簡單的一臺服務器變兩臺服務器:

此時架構上明顯要複雜多了,主要體如今:

  • 須要增長一個任務分配器,這個分配器多是硬件網絡設備(例如,F五、交換機等),多是軟件網絡設備(例如,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 做爲主節點,其餘節點做爲備節點;當鏈接發生故障時,節點 一、節點 二、節點 3 造成了一個子集羣,節點 四、節點 5 造成了另一個子集羣,這兩個子集羣的鏈接已經中斷,沒法進行信息交換。按照民主決策的規則和算法,兩個子集羣分別選出了節點 2 和節點 5 做爲主節點,此時整個系統就出現了兩個主節點。這個狀態違背了系統設計的初衷,兩個主節點會各自作出本身的決策,整個系統的狀態就混亂了。

爲了解決腦裂問題,民主式決策的系統通常都採用「投票節點數必須超過系統總節點數一半」規則來處理。

如圖中那種狀況,節點 4 和節點 5 造成的子集羣總節點數只有 2 個,沒有達到總節點數 5 個的一半,所以這個子集羣不會進行選舉。這種方式雖然解決了腦裂問題,但同時下降了系統總體的可用性,即若是系統不是由於腦裂問題致使投票節點數過少,而真的是由於節點故障(例如,節點 一、節點 二、節點 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 年代
相關文章
相關標籤/搜索