以前在做專案時,我比較在意的是「功能有沒有做出來」。
例如:
但當我開始接觸比較大型的系統後,發現事情沒有這麼簡單。
一個服務只有幾十個使用者時,可能一台 Server 加一個 Database 就能正常運作;但如果使用者變成幾萬、幾百萬甚至更多,問題就會開始出現:
所以我開始理解,System Design 真正要處理的不是「怎麼把功能寫出來」,而是:
如何讓一個系統在流量、資料量與機器數量不斷增加之後,依然能維持合理的效能、可靠性與可用性。
而 System Design 最核心的一件事,就是 Trade-off。
很多時候並不存在一個所有面向都最好的架構,而是根據需求選擇最適合的方案。
這兩個概念很容易混在一起,但實際上是在看不同的問題。
Performance 關心的是:
系統在目前的工作量下,執行得快不快?
例如:
GET /users/123
平均花:
50 ms
代表目前這個 API 的 Performance 不錯。
Scalability 關心的是:
當 Workload 增加時,系統還能不能維持合理的效能?
例如:
1 user
→ 50 ms
1,000 users
→ 80 ms
10,000 users
→ 150 ms
100,000 users
→ timeout
前面 Performance 可能很好,但流量增加後就開始出問題,代表系統的 Scalability 不夠好。
可以簡單記成:
| 概念 | 問的問題 |
|---|---|
| Performance | 現在跑得快不快? |
| Scalability | 流量變大後還撐不撐得住? |
當系統資源不夠時,通常有兩種擴展方式。
把原本的機器升級成更強的硬體。
例如:
Before
4 CPU
8 GB RAM
變成:
After
32 CPU
128 GB RAM
不是把一台 Server 變強,而是增加更多 Server。
┌── Server A
├── Server B
User ─────┼── Server C
└── Server D
開始需要處理:
所以:
Scale-out 解決了資源不足的問題,但同時也把系統帶進 Distributed Systems 的世界。
假設系統只有:
User
|
v
Server A
如果:
Server A ❌
整個服務就會掛掉。
這就是:
Single Point of Failure,SPOF
Redundancy 可以理解成:
額外準備一份可以替代原本資源的備援。
例如:
┌── Server A
User ── LB ────┼── Server B
└── Server C
其中一台 Server 掛掉:
Server A ❌
Server B ✅
Server C ✅
服務仍然可以繼續。
Server A
Server B
Server C
Primary Database
├── Replica A
└── Replica B
Taiwan Region
Japan Region
US Region
Redundancy 的主要目的:
但它也有成本:
Scale-out 久了,另一個常見問題就是:
Heterogeneity(異質性)
意思是:
Distributed System 裡面的機器不一定具有相同的能力。
例如一開始:
Node A:8 CPU / 32 GB RAM
Node B:8 CPU / 32 GB RAM
Node C:8 CPU / 32 GB RAM
幾年後新增:
Node D:32 CPU / 128 GB RAM
Node E:64 CPU / 256 GB RAM
現在每台 Node 的能力已經不同。
例如:
Node A:Taipei
Node B:Tokyo
Node C:New York
即使硬體規格完全相同,Network Latency 也不一樣。
假設三台機器的 Capacity:
Node A → 100 req/s
Node B → 100 req/s
Node C → 1000 req/s
如果平均分配:
300 Requests
A → 100
B → 100
C → 100
結果:
A → 100% utilization
B → 100% utilization
C → 10% utilization
Node C 的能力就被浪費了。
因此 Scheduler 或 Load Balancer 不能永遠假設:
All Nodes Have Equal Capacity
而可能要考慮:
這兩個也是 System Design 很常看到的 Metric。
Latency 是:
完成一個操作需要多久時間。
例如:
Request
|
| 100 ms
v
Response
代表:
Latency = 100 ms
Throughput 是:
單位時間可以處理多少工作。
例如:
10,000 requests / second
代表:
Throughput = 10,000 RPS
| Metric | 關心的事情 |
|---|---|
| Latency | 一個 Request 要等多久 |
| Throughput | 一秒可以處理多少 Request |
可以用餐廳來理解:
大型系統通常希望:
在 Latency 保持在可接受範圍內的前提下,提高 Throughput。
假設我們有兩份 Database:
┌── DB A
User ─────┤
└── DB B
User 把名稱從:
Brian
改成:
Chih-Hsi
DB A 已經更新:
DB A → Chih-Hsi
但 DB B 還沒同步:
DB B → Brian
此時另一個 Request 查詢 DB B,就可能拿到舊資料。
Consistency 關心:
使用者讀到的資料是不是最新且一致的?
Availability 關心:
使用者發送 Request 時,系統能不能持續提供 Response?
兩者在 Distributed System 發生 Network Failure 時,可能會互相衝突。
CAP 分別代表:
C = Consistency
A = Availability
P = Partition Tolerance
每次 Read:
不應該讀到不一致的版本。
每個 Request 都能獲得 Response。
但:
當 Distributed System 裡的 Node 因為 Network Failure 無法互相溝通時,系統仍然能運作。
例如:
Taiwan DB ───── X ───── US DB
↑
Network Partition
兩台 Database 都還活著。
但彼此不能同步。
這就是 Partition。
Distributed System 通常不能假設 Network 永遠正常。
因此 Network Partition 發生時,需要考慮:
Consistency
VS
Availability
Network Partition 發生時:
DB A ─── X ─── DB B
如果無法確認資料是否一致:
Request
↓
Error / Timeout
也不願意回傳可能錯誤的資料。
適合:
即使 Network Partition 發生:
Request
↓
仍然 Response
但可能拿到:
舊資料
例如:
DB A → followers = 1000
DB B → followers = 998
在部分場景中,短時間顯示 998 並不會造成重大問題。
Network 恢復後:
DB A
↕
Sync
↕
DB B
最後再恢復一致。
這類設計常會搭配:
Eventual Consistency
今天整理完後,我覺得最重要的不是把所有名詞背起來,而是理解每個設計都有代價。
例如:
| 選擇 | 好處 | 代價 |
|---|---|---|
| Scale-up | 簡單 | 有硬體上限 |
| Scale-out | Capacity 高 | 系統複雜 |
| Replication | Reliability 高 | Consistency 更難 |
| Strong Consistency | 資料可靠 | Availability / Latency 可能較差 |
| Eventual Consistency | Availability 高 | 短時間可能讀到舊資料 |
| 更多 Node | Throughput 提高 | Coordination Cost 增加 |
因此 System Design 不太是在問:
哪個 Architecture 最好?
而是在問:
在目前的 Requirements 下,哪一種 Trade-off 最合理?
遇到:
Design Twitter
不應該一開始就直接畫:
Load Balancer
↓
Redis
↓
Kafka
↓
Database
而是先確認需求。
先確認:
粗估:
先畫出主要元件:
Client
↓
Load Balancer
↓
Application Server
↓
Database
檢查:
根據 Bottleneck 再加入:
而不是一開始把所有技術全部塞進架構。