垂直擴充與水平擴充
換一台更大的機器,還是加更多台機器?垂直擴充最簡單但有上限、價格越來越陡;水平擴充幾乎沒有上限,但服務必須無狀態,資料也要能切開。
5,000
程式碼
垂直擴充:換一台更大的
規格
32 核
每小時
$1.60
忙碌度
63%
壞一台後的容量
0
水平擴充:多台 4 核小機器
台數(含備用)
5
每小時
$1.03
忙碌度
55%
壞一台後的容量
80%
垂直擴充:一台更大的機器水平擴充:更多台小機器+負載平衡
每秒 5,000 個請求:一台 32 核、63% 忙碌,每小時 $1.60;或 5 台 4 核小機器加負載平衡,每小時 $1.03。單台換規格每次要停機 5 分鐘,加一台小機器則不用。
前提:水平擴充的伺服器必須無狀態
登入狀態存在
已登入使用者
1,000
壞一台時被登出
205 (21%)
每個請求多查幾次
0
使用者被黏在存著自己 session 的那台(sticky session)。那台一壞,黏在它上面的 205 位使用者就在使用途中被登出。
亮起來的是這一步執行的程式碼
type Instance = { cores: number; qps: number; pricePerHour: number }; // Scale up: the smallest machine that carries the load below the target.function pickInstance(qps: number, sizes: Instance[], target = 0.7): Instance | null { for (const size of sizes) { if (size.qps * target >= qps) return size; } // Past the biggest machine there is nothing to buy. return null;} // Scale out: enough small servers for the load, plus one spare.function serversNeeded(qps: number, perServerQps: number, target = 0.7): number { return Math.ceil(qps / (perServerQps * target)) + 1;} // Stateless: the session is looked up in a shared store on every request,// so any server can answer and losing one signs nobody out.class Handler { constructor(private sessions: Map<string, string>) {} handle(sessionId: string): { status: number; user?: string } { const user = this.sessions.get(sessionId); if (user === undefined) return { status: 401 }; return { status: 200, user }; }}模型假設與範圍
- 這是可重現的教學模型;延遲、容量、故障率與工作負載是設定或樣本,不能直接當作正式系統的效能承諾。
- 容量曲線由序列化、協調與每核心處理量的設定值計算;沒有硬體 benchmark、autoscaler 或部署成本實測。
什麼時候用
- 先垂直:流量還小、資料庫難切的時候,換大台最省事,不用改任何程式。
- 再水平:接近單機上限、需要撐過單台故障,或流量起伏大想隨時加減機器的時候。
- 應用伺服器容易水平擴充(只要無狀態);資料庫比較難,要靠讀取副本和分片。
和其他主題的關係
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 水平擴充的成本對負載 n 是負載;每台小機器扛固定的量,再加一台備用 | O(n) | O(n) |
| 垂直擴充的成本對負載 價格跟核心數成正比,吞吐量卻越來越不跟:128 核比 64 核貴一倍,只多 2% 的吞吐;超過最大台的上限就買不到了(∞) | ≥ O(n) | ∞ |
| 壞一台後剩下的容量:垂直 | 0 | 0 |
| 壞一台後剩下的容量:水平 | (N−1)/N | (N−1)/N |
| 無狀態伺服器每個請求多查 session 多一次網路往返,換來任何一台都能接請求 | O(1) | O(1) |
空間:O(n),台數隨負載線性增加
Big O 實測:n 變大時步數怎麼長
數的是:n 是每秒請求數:水平擴充需要的台數與每小時成本(都超過任何單台的上限)
| Big O | n = 10,000 | n = 100,000 | n = 1,000,000 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|
| 台數(含一台備用) | O(n) | 9 | 79 | 781 | ×87 (×100) |
| 每小時成本(美元) | O(n) | 1.8 | 15.8 | 156 | ×85 (×100) |
垂直擴充沒辦法放進這張表:這三種負載都超過最大台機器能扛的量。
和其他做法比
| 垂直:規格・每小時 | 垂直:每 1k QPS 每小時 | 水平:台數・每小時 | 水平:每 1k QPS 每小時 | 壞一台後剩下的容量 | |
|---|---|---|---|---|---|
| 每秒 1,000 | 4 核 · $0.20 | $0.20 | 2 · $0.43 | $0.43 | 0 對 50% |
| 每秒 5,000 | 32 核 · $1.60 | $0.32 | 5 · $1.03 | $0.21 | 0 對 80% |
| 每秒 10,000 | 沒有夠大的 | — | 9 · $1.83 | $0.18 | 0 對 89% |
| 每秒 50,000 | 沒有夠大的 | — | 40 · $8.03 | $0.16 | 0 對 98% |
表中的 QPS 是 Queries Per Second(每秒請求數)。假設:每核心每秒 500 個請求,依 USL(Universal Scalability Law)模型隨核心數遞減(序列化比例 0.03、一致性成本 0.0001);每核心每小時 $0.05;機器最多跑到 70% 忙;水平擴充多一台備用,負載平衡器每小時 $0.03。小負載時單台比較便宜,因為水平擴充要付備用機和負載平衡器的錢;負載一大,大機器每單位效率變差,水平擴充反而便宜,而且單台一壞就全停。
真實世界裡的它
- 雲端的自動擴展群組(AWS Auto Scaling、Kubernetes 的 HPA〔Horizontal Pod Autoscaler〕)依 CPU 或請求數自動加減台數。
- Stack Overflow 長年用少數幾台很大的 SQL Server 撐住整站,是垂直擴充走得很遠的例子。
- 把登入狀態放進 Redis 或用簽章的 token(JWT,JSON Web Token),是讓網站伺服器能水平擴充的標準做法。
取捨與陷阱
- 單台就是單點故障:機器一壞服務全停,換規格也要停機。
- 有狀態的伺服器不能隨便加減:session、上傳到一半的檔案、本機快取都會隨機器消失。
- 水平擴充把瓶頸推到下一層:應用伺服器加到一百台,資料庫還是只有一台。