System Design: 系統架構基礎 - 可靠、可擴展、可維護

Charlie Lee
Bucketing
Published in
10 min readMar 21, 2021

--

本文將介紹系統分析的三大要素,可靠、可擴展、可維護性

Photo by Elena Mozhvilo on Unsplash

簡介

再上一篇文章介紹了甚麼是系統後,此篇文章會接續探討系統架構的目標,也就是可靠、可擴展、可維護性,學習心智圖如下。

可靠性 (Reliabilty)

定義軟體可靠性

工程師如何思考可靠性?

  • 軟體表現符合用戶預期
  • 在預期的使用流量與數據量下滿足性能
  • 系統能阻擋未經授權的訪問和濫用

可靠性的重要,可靠性簡單來說就是公司賺錢的工具是不是可以運作,當然在創業初期時,快速開發曝光更加重要,但當穩定並且建立商譽後,可靠性可以說是公司可以走多遠的關鍵因素。

定義錯誤(Error)

可靠性是圍繞著預防Error發生以及當Error作出相對應處理行為的指標,首先先定義什麼是錯誤Error

  • 故障 Fault,代表系統其中一個組件(硬碟或其中一台電腦)不能使用,或是軟體跳出了一個Exception,預料故障並且對應則稱為容錯(fault-tolerant)或是韌性(resilient)
  • 失效 Failure,代表系統完全停止運作

Tolerating

阻止錯誤實際上是不可能的,就算可能代價也非常大,所以在系統設計中偏向容錯(fault-tolerant),設計一系列的容錯機制讓發生錯誤時傷害降到最低,並且快速修正

Hardware faults

硬體錯誤大多是獨立事件,一台電腦壞掉或是硬碟壞掉不代表其他也會跟著壞。

  • 硬碟壞掉
  • 記憶體出錯
  • 斷電
  • 網路線被踢到

硬碟壞掉為最大宗,因為硬碟的物理運作會造成磨損進而故障,硬碟平均壽命(MTTF mean time to failure)在DDIA書中提到為10–50年(這Range非常廣阿,可能會根據真實機房所在位置灰塵濕度有關係),根據平均壽命換算如果有10000個硬碟則每天會故障一個。

對應方法:

  • 增加冗於度(將資料寫入多個硬碟),組建RAID機制(寫入資料時不只寫入一個硬碟,RAID方法有許多種)
  • 透過系統軟體層面做容錯機制(若發現硬碟無法寫入則快速切換Request到其他系統或機器)

Software faults

軟體錯誤和硬體錯誤相比則恐怖許多,一個系統組件(Component)或是模組(Module)有Bug,代表有用到的系統資料都會跟著錯誤,這些錯誤可能還會導致整體軟體系統失效(failure)需要關閉,進而影響公司的商譽跟使用者體驗。

  • 沒有阻擋到非預期的Request,導致軟體寫入資料時掛掉
  • 失控的Process耗盡整個機器的CPU時間Memory和I/O頻寬
  • 因為軟體都是關聯的,所以一個小模組壞掉可能會引起波瀾,整個系統不可用

對應方法

  • 徹底測試
  • 隔離Process
  • 允許崩潰時自我啟動
  • 監控與測量,(當CPU或記憶體使用達到XX%時進行一系列SOP)
  • 運行中自我驗證機制 (例如MQ發出去的訊息與接收的訊息,需要透過其他的Application Code進行比對確保消息沒有丟失)

Human error

根據DDIA書中所說,維運配置(軟體的Config、網路iptable route NAT)是服務中斷的主要原因,如何避免或是包容這樣的error有以下參考

  • 最小犯錯機會的方式設計系統 (更容易理解的API名稱),但如果限制過多會引發人類的惰性想要繞開限制,如何在限制與開發拿到平衡需要經驗
  • 將容易壞掉的服務組件模組解偶(decouple),並且提供與生產環境相似的sandbox進行完全測試。(以訂票系統為例,訂票功能需要與其他功能特別切開,這樣可以在有阿妹演唱會時針對訂票功能做水平擴增(增加機器),也可以真對訂票功能在沙盒(sandbox)裡做大量的壓力測試)
  • 允許快速回復 (可以設計更新時的備份機制,當維運人員更新發生例外(truncate table…)時雖然一定會造成一些影響,但至少可以返回之前穩定的狀態
  • Telemetry(遙測),此詞彙是源自工程科學,當火箭起飛時,科學家不會只觀看火箭在外太空的數據,而是從倒數就開始不斷追蹤一系列數據,軟體開發也可以如此,設定系統一系列指標定時追蹤與告警

可擴展性 (Scalability)

如第一篇文章所說,架構設計的前提是保證效能與功能持續變動下的設計,而擴展性就是架構對效能變動的評估定義。

在開始討論可擴展性(Scalability),需要先知道兩個詞彙負載與效能,負載和效能息息相關,當一套系統沒有太多使用者時效能完全可以符合我們需要,但是當使用者開始上升(負載上升)就會連帶的影響效能,讓效能快速下降。

負載 Measuring load

負載可以使用負載參數(load parameters)來描述,像是

  • 資料庫中讀和寫的比率
  • 每秒Server接收與發出的數量
  • 目前活動的使用者數量
  • 緩存命中數量

而且這些評估參數除了要在單模組或單系統思考之外,還需要從更高的層面去觀察,因為模組和系統會關聯其他的模組與系統,卡住了其中一個可能會造成連環效應剩下的執行鍊也跟著卡住。而這樣的關聯行為也可以稱為扇出(fan-out: 意旨服務執行時需要關聯其他系統的行為)。

效能 Performance

負載與效能是息息相關的,以連線請求為例子,當使用者越多對Server的連線也會跟著變多,那會導致以下情況

  • 使用Thread per Message維護連線導致CPU過多Context Switch降速
  • 使用執行緒池,穩住CPU的Context Switch但是儲存Task的Queue暴增導致Memory裝不下
  • 使用者資料變多DB每次搜尋和寫入速度下降

而系統架構設計對於Scalability的目標就在於,當負載上升(使用者變多)而性能還可以保持使用者體驗的能力。

系統想要保持使用者體驗排除業務功能的數據包含兩個重點,吞吐量(throuput)和響應時間response time。講白了吞吐量代表系統每秒可以回復多少使用者的Request(如果超出了使用者需要排隊,也有可能接收到錯誤的狀態碼),而Response time就是使用者觸發了這個功能,多久會得到解果(當然也包含了使用者本身的網路和電腦延遲,但系統能改變的只有自己的回應時間)。言簡意賅系統可以在此吞吐量做到多少Response time就是效能評估的依據。

如何評估Response Time要素:

  • 算數平均值(arithmetic mean)
  • 百分點位分析(Percentiles)(50%/95%/99%/99.9%)

目前大多的評估都是以第二個Percentiles為主,因為有時平均數不一定代表大多數。

而百分點位分析(Percentiles),位居高位的響應時間(使用者等待回應比較長,例如99.9%)也稱為尾部延遲(tail latencies),有時候是非常關鍵的,因為通常需要等待系統響應這麼長時間的使用者會牽涉到的業務邏輯比較複雜,也可以說是系統的VIP用戶,所以需要將他們當作效能優化的主要目標,而這樣的目標也可以稱為

  • 服務級別目標 (SLO,Service Level Objectives)
  • 或服務級別協議 (SLA,Service Level Agreements)

在壓力測試時可以以99.99作為SLO達成系統才能正式上線。

如何擴增負載(loading)並保持效能(performance)

當系統使用量上升到達系統的極限時,工程師可以透過

  • 縱向擴展(scaling up)(也稱為垂直擴展vertical scaling),代表直接增加記憶體硬碟或是直接換一台更好的Server
  • 橫向擴展(scaling out)(也稱為水平擴展horizontal scaling),使用演算法將目前的負載導向其他機器

正常情況下,縱向擴展都有一定的極限(越好的機器價位越恐怖,兩台B效能加起來跟一台A一樣時,兩台B的合併的價位可能不到一台A的二分之一),所以通常架構設計都是以scaling out為主,可以透過多個便宜機器消化運算。

如何決定要縱向還是橫向?

通常系統需要擴展時也代表公司的商業模式運轉順暢,使用者越來越多。在第一時間如果預算許可之下一定事先考慮縱向擴展,因為橫向擴展牽涉到未來要維護更多機器還有如何非配流量演算法,非常麻煩!(此系列文主要討論的就是這樣的分散系統),當縱向擴展預算突破天際時在開始思考橫向設計。當然在現在雲端共應商這麼多的情況下,應該沒有太多工程師有機會設計了,但工程師求道之路這是必經的過程,或許哪一天世界爆發拋棄雲端(偷偷收集平台上其他人資訊或天災多個資料中心被隕石打到…)就是自己發揮光芒的時刻。

橫向擴展考慮要素

橫向擴展大致分為

  • 業務邏輯Server
  • 儲存Server
  • 快取Server
  • 消息對列(MQ Server)

其中業務邏輯Server是最容易做到橫向擴展的,這個前提是這些機器需要是無狀態的,或是有一定的Request分配演算法可以確保每次都會發到同一台機器上。而其他三個會在後續的系列文介紹,因為牽涉太多再次先不討論。

在來說說彈性,擴展的彈性就是系統可以自動判斷現在的流量與效能,自動的增加機器。其實就是現在雲端供應商的Elastic系列,到達一定的閥值(CPU 使用率80%,記憶體快不夠了)就會自動擴張,雖然現在有這些Cloud Provider Carry我們但如果如果是自己設計的話難度會非常大,遠比工程師們手動自己擴增。

可維護性 (Maintainability)

本文一直強調系統架構設計,就是讓系統可以在效能與功能不斷變動下保持可靠、可擴展、可維護,效能主要是與可擴展相關,而功能就是和可維護性相關了。

相信大家都非常討厭維護上一代撰寫的程式碼,原因就在於害怕、不了解甚至是看不懂,可維護性討論的就是以下三點

可操作性 Operability

系統是不是可以讓維運團隊保持穩定運行,包含

  • 系統是不是可以被監控,並且提供有效的監控數據 (執行緒監控/GC監控)
  • 有沒有自動化工具,可以做一系列的操作
  • 有沒有撰寫真的是給人看的文件
  • 有沒有辦法自我修復,若不行的時候會有方法讓維運團隊手動更改
  • 預測行為,減少意外

簡單性 Simplicity

各位有沒有看過就算用IDE,程式碼還是會跳到自己頭昏腦脹的程式碼,簡單性討論的就是這個,讓系統更簡單包含:

  • 利用抽象的概念,抽象讓人類容易理解程式碼
  • 把龐大的系統切割成再小一點子系統(微服務)

可演化性 Evolvability

使用者的需求永遠不構,每天都需要增加新的功能,設計可演化的程式碼架構非常重要,現在大多公司都透過agile與refactoring增加系統的可演化性。

結論

快速總結本文討論的系統目標三大要素,可靠性(Reliability)、可擴展性(Scalability)與可維護性(Maintainability)

  • 可靠性: 代表系統無法避免故障,系統是否有對應的容錯機制(Tolerating)可以讓使用者保持使用體驗
  • 可擴展性: 包含垂直擴展與水平擴展,垂直就是直接升級機器硬體,而水平擴展則是將流量導向其他機器
  • 可維護性: 系統需要透過不斷的迭代與重構,才不會有一天加功能時造成全面崩潰

這篇文章內容非常多,大多是DDIA的內容(甚至幾乎全部都是),非常建議各位讀者可以去看原文,原文還包含許多精彩的實際案例。

--

--