Designing Data-Intensive Applications - 第一章筆記
Reliable, Scalable, and Maintainable Applications
前面還有一些基礎內容略過
Describing Performance
延遲(Latency)與回應時間(Response time)
回應時間是客戶端看到的時間,包括處理請求的服務時間、網絡延遲和排隊延遲,而延遲則是指請求等待被處理的時間。即使是相同的請求,回應時間也會有變化,因此回應時間應被視為一個分佈值,而非單一數字。
百分位數
平均值往往不能反應普通的Response time
,因此使用中位數(50百分位,p50)來更好地表達普通用戶的體驗。而為了瞭解效能的極端情況,可以看更高的百分位數,例如95百分位(p95)和99百分位(p99),這些數值有助於分析最慢的request
。
高百分位數的重要性
例如,Amazon對內部服務的Response time
要求集中在99.9百分位,因為這影響到最有價值的客戶。即使高百分位數的請求量很少,這些客戶體驗的效能卻至關重要。
Amazon has also observed that a 100 ms increase in response time reduces sales by 1%
此外,書中還提到了服務水平目標(SLO)和服務水平協議(SLA)的使用,這些指標有助於設定服務的性能期望並確保達到目標。當服務未達標,客戶可以要求退款。
高百分位數在實務上
在處理單個用戶請求時,往往需要多個後端服務的回應。即使這些後端調用是並行進行的,但只要有一個調用變慢,整個用戶請求的回應時間也會受到影響,這種現象被稱為tail latency amplification
。
書中還提到,為了將回應時間的百分位數添加到monitoring dashboards
中,必須高效地計算這些百分位數。簡單的方法是每分鐘對過去10分鐘內的請求回應時間進行排序,計算中位數及其他百分位數,並將它們繪製成圖表。然而,這種方法可能效率不高,因此可以使用一些低CPU和內存需求的演算法來近似計算百分位數,如forward decay
、t-digest
或HdrHistogram
。
最後,作者提醒,百分位數不應通過簡單平均來合併數據,正確的做法是將直方圖數據相加來進行聚合。
Approaches for Coping with Load
垂直擴展與水平擴展:垂直擴展(提升單一機器性能)和水平擴展(將負載分佈到多個較小機器上)的取捨是常見的討論話題。雖然垂直擴展較為簡單,但高端機器成本高,無法應對大量負載,因此水平擴展(即shared-nothing architecture
)成為應對高負載的常見選擇。實際中,混合使用多台強大的機器可能既簡單又經濟。
彈性與手動擴展:某些系統具有彈性,可自動擴展資源以應對負載增長,而手動擴展則依賴人工分析資源需求。彈性系統適合應對不可預測的負載,但手動擴展更簡單,且運營風險較少。
有狀態系統的複雜性:無狀態服務的擴展相對簡單,而將有狀態數據系統從單節點擴展到分佈式系統會增加複雜性。過去的建議是將數據庫存在單節點,除非出現高成本或高可用性需求,但隨著分佈式系統工具的進步,這種觀點可能會改變。
無通用擴展架構:應用層級的擴展架構往往是特定的,根據讀取量、寫入量、數據量、回應時間和訪問模式等參數量身定制。一個適合特定應用的架構通常基於對常見操作的假設。如果假設錯誤,擴展的工程可能無效甚至適得其反。
儘管擴展架構是針對具體應用設計的,它們往往是通用構建模塊
的組合。本書將深入探討這些模塊與架構模式。
Maintainability(可維護性)
軟體的主要成本在於後續的維護,而非初期開發。維護工作包括修復bug、保持系統運行、調查故障、適應新平台、修改以支持新需求、償還技術債以及新增功能。
然而,許多從事軟體開發的人對於維護legacy systems
(遺留系統)抱持負面態度,因為這可能涉及修正他人的錯誤,或者處理過時的平台和系統。然而,儘管每個遺留系統都有其獨特的挑戰,我們仍應該以減少未來的維護痛點來設計軟體,避免我們的系統成為遺留系統。
為了達到這個目標,本文強調三個設計原則:
Operability(可操作性)
使運營團隊能夠輕鬆保持系統穩定運行。
Simplicity(簡單性)
盡可能減少系統的複雜性,使新工程師能夠容易理解系統(這與使用者介面的簡單性不同)。
Evolvability(可演進性)
讓工程師能夠輕鬆地對系統進行修改,以適應未來不可預期的需求變更。這也被稱為Extensibility(可擴展性)、Modifiability(可修改性)或Plasticity(可塑性)。
正如Reliability(可靠性)和Scalability(可擴展性),這些設計目標沒有簡單的解決方案,但我們應該在設計系統時,時刻考慮這些原則。
Operability: Making Life Easy for Operations
儘管部分運營工作可以自動化,仍需人員設置和監控這些自動化工具的運行。因此,運營團隊對於軟體系統的順利運行至關重要。
良好的運營團隊通常負責以下工作:
- 監控系統狀況,並迅速恢復服務
- 查找問題根源,如系統故障或性能下降
- 更新軟體與平台,確保安全性
- 追蹤系統之間的影響,避免有害變更
- 預測未來問題,並提前解決(例如容量規劃)
- 建立部署和配置管理的最佳實踐
- 執行複雜的維護任務,如遷移應用到不同平台
- 在配置變更中維持系統安全
- 定義流程以確保運營的可預測性並保持穩定的生產環境
- 保留系統的組織知識,即使團隊成員變動
良好的可操作性能夠簡化日常任務,使運營團隊可以專注於高價值的活動。數據系統可以通過多種方式提高可操作性:
- 提供對系統運行行為的可視性,並提供良好的監控
- 支持自動化並與標準工具集成
- 避免依賴單台機器,允許系統維護期間不間斷運行
- 提供良好的文檔及易於理解的操作模型
- 提供良好的默認行為,同時允許管理員在需要時覆蓋默認設置
- 適當地具備自我修復能力,但也給予管理員手動控制的權限
- 表現出可預測的行為,盡量減少意外
這些措施旨在讓運營團隊更輕鬆地管理系統,從而專注於長期改善和提升系統價值。
Simplicity: Managing Complexity
本段落討論了如何通過Simplicity(簡單性)來管理軟體系統的複雜性,特別是在隨著專案規模增大後,系統變得難以理解和維護的情況。複雜性通常會影響系統的維護,導致開發和運營成本增加,甚至有可能帶來潛在的風險和問題。簡化系統不僅能提高效率,還能減少意外錯誤的出現。
複雜性的症狀可能包括:狀態空間的爆炸、模塊間的緊密耦合、依賴關係的混亂、命名和術語的不一致、為解決性能問題而進行的臨時修補,以及為解決系統其他部分的問題而進行的特例處理。這些都會使系統變得難以理解和維護。
然而,簡化系統並不意味著削減功能,而是指移除accidental complexity
(偶然複雜性)。這種複雜性不是系統解決問題的本質,而是來自具體實現的過程。Moseley和Marks將其定義為不必要的複雜性,並強調移除這些複雜性的重要性。
一個減少偶然複雜性的重要工具是abstraction
(抽象)。一個好的抽象可以隱藏許多具體的實現細節,並且提供一個簡單易懂的介面,使開發者不必直接處理底層的細節。例如,高階程式語言抽象了機器語言、CPU暫存器和系統調用,而SQL則抽象了複雜的數據結構和處理並行請求的邏輯。透過這些抽象,我們得以專注於解決問題,而無需考慮過多的實現細節。
不過,找到好的抽象並不容易,尤其在分散式系統領域。雖然有很多優秀的算法,但如何將這些算法封裝成可以幫助我們管理複雜度的抽象仍是個挑戰。
Evolvability: Making Change Easy
系統的需求幾乎不可能永遠保持不變,反而更有可能隨著時間不斷變動:你會學到新知識,出現意想不到的使用案例,商業優先級改變,用戶要求新功能,新平台取代舊平台,法規要求改變,系統成長導致架構改變等等。
在組織流程中,敏捷工作模式提供了一個適應變化的框架。敏捷社群也發展了許多技術工具和模式,有助於在經常變動的環境中開發軟體,例如測試驅動開發 (TDD) 和重構。
修改資料系統的難易度,與其簡單性和抽象性息息相關:簡單且易於理解的系統通常比複雜的系統更容易修改。由於這個概念極為重要,我們將使用另一個詞來描述資料系統層面的敏捷性:演進性 (evolvability)。
總結
資料密集型應用的基本原則:介紹了應用的功能需求(functional requirements,如數據存儲、檢索等)及非功能需求(nonfunctional requirements,如安全性、安全性(security)、可靠性(reliability)、可擴展性(scalability)等)。
可靠性(Reliability):確保系統在故障(硬體、軟體或人為錯誤)發生時仍能正確運作,容錯技術(fault-tolerance techniques)能隱藏某些故障。
可擴展性(Scalability):維持高負載(load)下的良好性能,並通過增加處理能力來應對負載增長,使用量化的方式來描述負載和性能(performance)。
可維護性(Maintainability):減少系統複雜性,提升系統的修改和適應能力,並確保系統運作良好(operability)。
沒有萬能解法:雖然沒有簡單的解決方案來達成這些目標,但一些常見模式和技術在不同應用中反覆出現。
以上總結由ChatGPT輔助產生