Day 3 我們介紹了兩種基本的 Scaling 方法:
Vertical Scaling → Scale Up
Horizontal Scaling → Scale Out
當一台 Server 不夠用時,我們可以使用 Horizontal Scaling 增加更多 Server:
Server #1
Server #2
Server #3
但新的問題馬上出現:
User 的 Request 到底要送去哪一台 Server?
如果 10,000 個 Request 同時進來,我們不希望全部都跑到 Server #1,也不可能讓 Client 自己決定要連哪一台 Server。
因此,我們需要一個 Component 放在 Client 和 Backend Servers 中間:
┌── Server #1
│
Users → Load Balancer├── Server #2
│
└── Server #3
這就是今天的主題:
Load Balancer(負載平衡器)
Load Balancer 最基本的工作可以理解成:
接收 Client 的 Request,然後把 Request 分配到後面的多台 Server。
假設我們有三台 Backend Server:
Server #1
Server #2
Server #3
如果沒有 Load Balancer,Client 必須知道每一台 Server 的位置:
User #1 → Server #1
User #2 → Server #2
User #3 → Server #3
這顯然很難管理。
加入 Load Balancer 後:
Users
↓
Load Balancer
↓
┌───────────────┐
↓ ↓ ↓
Server Server Server
#1 #2 #3
Client 只需要知道:
Load Balancer
後面到底有幾台 Server,通常不需要由 Client 自己處理。
最直接的原因就是:
Horizontal Scaling 之後,我們需要有人負責分配 Traffic。
假設有:
9 Requests
以及:
3 Servers
理想情況可能是:
Server #1 → 3 Requests
Server #2 → 3 Requests
Server #3 → 3 Requests
而不是:
Server #1 → 9 Requests 😵
Server #2 → 0 Requests
Server #3 → 0 Requests
Load Balancer 的目的之一,就是盡量避免 Traffic 不合理地集中在單一 Server。
我們目前的架構是:
User
↓
Backend Server
↓
Database
Horizontal Scaling 後:
┌── Server #1
User ─────────┼── Server #2
└── Server #3
加入 Load Balancer:
┌── Server #1
│
User → Load Balancer ├── Server #2
│
└── Server #3
↓
Database
所以 Load Balancer 通常會位在:
Client
↓
Load Balancer
↓
Backend Servers
它就像 Backend Servers 前面的「Traffic Controller」。
現在問題變成:
Load Balancer 到底要怎麼決定 Request 要去哪一台 Server?
最簡單的方法之一就是:
Round Robin
假設有三台 Server:
Server #1
Server #2
Server #3
Request 依序進來:
Request #1
Request #2
Request #3
Request #4
Request #5
Request #6
Round Robin 可以依序分配:
Request #1 → Server #1
Request #2 → Server #2
Request #3 → Server #3
Request #4 → Server #1
Request #5 → Server #2
Request #6 → Server #3
也就是:
#1 → #2 → #3 → #1 → #2 → #3 → ...
概念非常簡單。
Round Robin 最大的優點就是:
簡單,而且容易實作。
如果每台 Server:
Round Robin 可以是一個很合理的方法。
例如:
Load Balancer
│
├──→ Server #1
├──→ Server #2
├──→ Server #3
├──→ Server #1
└──→ ...
但是現實世界中的 Request 不一定都一樣。
假設:
Request A → 10 ms
Request B → 20 ms
Request C → 5 seconds
Request C 可能需要做非常複雜的計算。
這時候 Server #1 可能已經很忙:
Server #1 → 100 active connections
Server #2 → 10 active connections
Server #3 → 5 active connections
如果 Load Balancer 還是單純按照順序:
#1 → #2 → #3 → #1
下一個 Request 還是有可能被送到已經非常忙的 Server #1。
所以我們可能需要更聰明的方法。
另一種常見策略叫做:
Least Connections
概念是:
把新的 Request / Connection 優先送給目前 Active Connections 較少的 Server。
假設:
Server #1 → 100 connections
Server #2 → 30 connections
Server #3 → 10 connections
下一個 Connection 可以優先送到:
Server #3
因為它目前的 Connection 最少。
Server #1 → 100
/
Load Balancer ─── Server #2 → 30
\
Server #3 → 10 ← New Connection
相較於單純 Round Robin,Least Connections 會考慮 Server 當下的 Connection Load。
還有另一種情況:
假設我們的 Server Hardware 不一樣。
Server #1
CPU: 16 cores
RAM: 64 GB
Server #2
CPU: 8 cores
RAM: 32 GB
Server #3
CPU: 4 cores
RAM: 16 GB
如果全部使用一樣的 Round Robin:
33% → Server #1
33% → Server #2
33% → Server #3
可能不太合理。
因為 Server #1 明顯比較強。
這時候可以使用:
Weighted Round Robin
例如設定:
Server #1 → Weight 4
Server #2 → Weight 2
Server #3 → Weight 1
比較強的 Server 就可以接收更多 Traffic。
概念上可能像:
Server #1 → ████
Server #2 → ██
Server #3 → █
所以 Load Balancing 並不是只有:
平均分配
還可以根據 Server 的 Capacity 做不同分配。
現在假設我們有:
Server #1 ✅
Server #2 ❌
Server #3 ✅
如果 Load Balancer 不知道 Server #2 已經掛掉,還是繼續把 Request 送過去:
Request
↓
Load Balancer
↓
Server #2 ❌
這些 Request 就會失敗。
所以 Load Balancer 還需要一個非常重要的功能:
Health Check
Load Balancer 可以定期確認 Backend Server 是否健康。
例如 Server 提供:
GET /health
正常時回傳:
HTTP/1.1 200 OK
Load Balancer 就知道:
Server #1 → Healthy ✅
如果 Server #2:
Load Balancer 可以暫時把它標記成:
Server #2 → Unhealthy ❌
然後停止把新的 Traffic 送給它。
Architecture:
┌── Server #1 ✅
│
User → Load Balancer ├── Server #2 ❌
│
└── Server #3 ✅
新的 Request 只會送到:
Server #1
Server #3
這也是 Horizontal Scaling 能改善 Availability 的重要原因之一。
假設 Server #2 維修完成:
Server #2 ❌
↓
Restart
↓
Server #2 ✅
Load Balancer 的 Health Check 再次成功後,可以把 Server #2 加回 Backend Pool:
┌── Server #1 ✅
│
User → Load Balancer ├── Server #2 ✅
│
└── Server #3 ✅
之後 Server #2 就可以重新接收 Traffic。
這讓系統具有一定程度的:
Fault Tolerance
也就是某個 Component 發生問題時,整個系統仍有能力繼續提供服務。
除了分配 Traffic,實際上的 Load Balancer 還可能負責很多事情,例如:
例如:
https://example.com/api/users
可能送到:
User Service
而:
https://example.com/api/orders
可能送到:
Order Service
不過 Day 4 先把最重要的核心概念掌握:
Load Balancer 最主要的角色,就是把 Traffic 分配到後面的多個 Server,並避免把 Request 送到不健康的 Server。
Load Balancer 還常常會看到:
Layer 4 Load Balancer
Layer 7 Load Balancer
這裡先建立基本概念。
Layer 4 主要根據 Transport Layer 的資訊進行 Load Balancing,例如:
IP Address
Port
TCP / UDP
它不一定需要理解 HTTP Request 裡面的 URL Path。
簡化理解:
Client
↓
IP + Port
↓
Layer 4 Load Balancer
↓
Server
Layer 7 Load Balancer 可以理解 Application Layer 的資訊,例如 HTTP:
URL
Path
Header
Host
Cookie
所以它可以做更進階的 Routing。
例如:
/api/users
↓
User Service
/api/orders
↓
Order Service
Architecture:
┌── /users → User Service
User → L7 Load Balancer│
└── /orders → Order Service
因此 Layer 7 Load Balancer 可以根據 Application-Level Information 做更細緻的 Routing。
Day 4 不需要先把 OSI Model 全部背起來。
先記:
Layer 4
→ IP / Port / TCP / UDP
Layer 7
→ HTTP / URL / Header / Cookie
Day 3 提到 Stateful Server 的問題。
假設 Alvin 登入後,Session 存在:
Server #1
第一次 Request:
Alvin
↓
Load Balancer
↓
Server #1
但第二次:
Alvin
↓
Load Balancer
↓
Server #2
如果 Session 只存在 Server #1,Server #2 可能找不到 Alvin 的 Session。
其中一種解法叫:
Sticky Session / Session Affinity
也就是:
Alvin → Server #1
之後 Alvin 的 Request 盡量都送到 Server #1。
例如:
Alvin ───→ Server #1
Bob ───→ Server #2
John ───→ Server #3
但 Sticky Session 也會產生新的問題。
例如 Server #1 掛掉:
Alvin → Server #1 ❌
原本存在 Server #1 的 State 可能就無法取得。
因此另一種常見思路是:
讓 Backend Server 盡量 Stateless。
把共享 State 放到:
Redis
Database
External Session Store
例如:
┌── Server #1 ──┐
│ │
User → Load Balancer ├── Server #2 ──┼── Redis / DB
│ │
└── Server #3 ──┘
這樣 Request 被分配到不同 Server 時,也能取得共享的 State。
看到這裡會發現一個新的問題。
原本我們想解決:
Server = Single Point of Failure
所以加入:
Server #1
Server #2
Server #3
但是所有 Request 現在都先經過:
Load Balancer
如果只有一台 Load Balancer:
Users
↓
Load Balancer ❌
↓
Servers
即使後面的 Server 全部正常:
Server #1 ✅
Server #2 ✅
Server #3 ✅
User 還是可能無法正常存取服務。
所以:
Load Balancer 自己也可能成為 Single Point of Failure。
因此 Production System 通常不會希望整個服務只依賴單一 Load Balancer。
概念上可以設計成:
┌── Load Balancer #1
Users ────────┤
└── Load Balancer #2
↓
┌────────┼────────┐
↓ ↓ ↓
Server Server Server
#1 #2 #3
當其中一個 Load Balancer 發生問題時:
Load Balancer #1 ❌
Load Balancer #2 ✅
另一個仍然可以繼續提供服務。
實際上 Cloud Provider 通常會幫我們處理很多這類 High Availability 細節。
但從 System Design 的角度,最重要的是理解:
每加入一個新的 Component,都要問:如果它掛掉會發生什麼?
這是一個非常重要的 System Design 思考方式。
在實際開發中,我們通常不會自己從零寫一個 Load Balancer。
Cloud Provider 已經提供 Managed Load Balancer。
例如在 AWS 裡,常見的 Load Balancer 包含:
Application Load Balancer (ALB)
Network Load Balancer (NLB)
可以先非常簡化地理解:
ALB
→ Layer 7
→ HTTP / HTTPS
→ 可以根據 Path / Host Routing
NLB
→ Layer 4
→ TCP / UDP / TLS
→ 適合需要高效能 Network Traffic 的情境
例如一個 Web Application:
Internet
↓
Application Load Balancer
↓
┌───────────────┐
↓ ↓ ↓
EC2 EC2 EC2
這就是非常常見的 Cloud Architecture。
回到最開始。
我們原本只有:
User
↓
Server
Traffic 增加:
Traffic ↑
↓
Server 😵
所以使用 Horizontal Scaling:
Server #1
Server #2
Server #3
但新的問題是:
Request → Which Server?
因此加入:
Load Balancer
最後變成:
┌── Server #1
│
User → Load Balancer ├── Server #2
│
└── Server #3
Load Balancer 幫助我們:
現在 Architecture 已經從:
User
↓
Server
↓
Database
進化成:
┌── Server #1
│
User → Load Balancer ├── Server #2
│
└── Server #3
↓
Database
Backend Server 的 Scalability 提高了。
但是仔細看:
Server #1 ─┐
Server #2 ─┼──→ Database
Server #3 ─┘
所有 Server 還是連到:
同一個 Database
如果 Backend Server 從:
3 Servers
增加到:
100 Servers
可能會變成:
100 Backend Servers
↓
Database
😵
Backend 不再是 Bottleneck。
新的 Bottleneck 可能變成:
Database
這又回到前幾天一直出現的 System Design Pattern:
找到 Bottleneck
↓
解決 Bottleneck
↓
系統成長
↓
出現新的 Bottleneck
↓
再次調整 Architecture
這就是為什麼 System Design 是一個不斷做 Trade-off 的過程。
Day 4 最重要的概念是:
當我們使用 Horizontal Scaling 增加多台 Server 後,需要 Load Balancer 幫忙分配 Traffic。
最基本的 Architecture:
┌── Server #1
│
User → Load Balancer ├── Server #2
│
└── Server #3
常見 Load Balancing Strategy:
Round Robin
Least Connections
Weighted Round Robin
Load Balancer 還需要:
Health Check
避免把 Traffic 送到:
Unhealthy Server ❌
另外也要記得:
Layer 4
→ IP / Port / TCP / UDP
Layer 7
→ HTTP / URL / Header / Cookie
最後最重要的 System Design 思考:
每加入一個新的 Component,都要問:它解決什麼問題?如果它自己掛掉又會發生什麼?
現在我們已經可以:
User
↓
Load Balancer
↓
Multiple Backend Servers
但 Backend Server 最後仍然需要取得資料。
下一個非常重要的問題就是:
資料到底應該怎麼存?
例如:
User
Order
Payment
Post
Message
這些資料應該使用:
SQL?
還是:
NoSQL?
下一篇:
Day 5|SQL vs NoSQL:System Design 到底該選哪一種 Database?
我們會開始了解:
Architecture 也會從:
User → Load Balancer → Servers
開始正式進入:
User
↓
Load Balancer
↓
Backend Servers
↓
Database
也就是 System Design 裡另一個非常重要的世界:Data Layer。