1. 負載均衡演算法
負載均衡是將流量分配到多台伺服器的技術,以最佳化資源使用率、提升吞吐量、降低延遲並確保高可用性。
1.1. Round-Robin(預設)
Round-robin 依序將請求輪流分配給每台伺服器。
設定:
upstream backend { # Round-robin 為預設值,無需宣告 server backend1.example.com; server backend2.example.com; server backend3.example.com; }server { listen 80; server_name example.com;
location / { proxy_pass http://backend; }
}
運作方式:
請求1 → backend1
請求2 → backend2
請求3 → backend3
請求4 → backend1(重複)
請求5 → backend2
請求6 → backend3
...
優點:
- 簡單,易於實作
- 均勻分配請求
- 無需狀態/Session 追蹤
缺點:
- 不考慮伺服器當前負載
- 伺服器處理能力不同時不適用
- 不維護 Session 親和性
使用場景:
- 無狀態應用程式
- 配置相同的伺服器群
- 簡單的負載分配
詳細範例:
upstream web_backend { server web1.example.com:8080; server web2.example.com:8080; server web3.example.com:8080; server web4.example.com:8080; }server { listen 80; server_name www.example.com;
access_log /var/log/nginx/loadbalancer.log; location / { proxy_pass http://web_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; add_header X-Upstream-Server $upstream_addr always; }
}
1.2. 最少連線(least_conn)
最少連線將請求路由到當前活躍連線數最少的伺服器。
設定:
upstream backend { least_conn; # 啟用最少連線演算法server backend1.example.com; server backend2.example.com; server backend3.example.com;}
server { listen 80;
location / { proxy_pass http://backend; }
}
運作方式:
初始狀態: backend1: 0 連線 backend2: 0 連線 backend3: 0 連線請求1 → backend1(0連線)→ backend1: 1連線 請求2 → backend2(0連線)→ backend2: 1連線 請求3 → backend3(0連線)→ backend3: 1連線
backend1 完成 → backend1: 0連線 請求4 → backend1(0連線,最少)
優點:
- 請求處理時間不一時,負載分配更好
- 自動調整忙碌/閒置伺服器
- 適合長時間連線
缺點:
- 比 round-robin 追蹤開銷更大
- 不維護 Session 親和性
使用場景:
- 請求處理時間不定的應用程式
- 長時間連線(串流、下載)
- 處理能力不同的伺服器群
含監控的範例:
upstream api_backend { least_conn;server api1.example.com:3000 max_fails=3 fail_timeout=30s; server api2.example.com:3000 max_fails=3 fail_timeout=30s; server api3.example.com:3000 max_fails=3 fail_timeout=30s; keepalive 32; keepalive_timeout 60s;}
server { listen 80; server_name api.example.com;
location / { proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
}
1.3. IP 雜湊(ip_hash)
IP 雜湊將來自相同客戶端 IP 的請求路由到同一台伺服器(黏性連線)。
設定:
upstream backend { ip_hash; # 啟用基於 IP 的黏性連線server backend1.example.com; server backend2.example.com; server backend3.example.com;}
server { listen 80;
location / { proxy_pass http://backend; }
}
運作方式:
客戶端 IP: 192.168.1.100 Hash(192.168.1.100) → backend2 → 192.168.1.100 的所有請求都到 backend2
客戶端 IP: 192.168.1.101 Hash(192.168.1.101) → backend1 → 192.168.1.101 的所有請求都到 backend1
優點:
- Session 親和性(相同客戶端 → 相同伺服器)
- 不需要共享 Session 儲存
- 簡單且高效
缺點:
- 客戶端數量少時分配不均
- 在 NAT/代理後面效果不佳
- 伺服器故障時 Session 遺失
ip_hash 的重要注意事項:
upstream backend { ip_hash;server backend1.example.com; server backend2.example.com; server backend3.example.com; # 不要在 ip_hash 中使用 weight # server backend1.example.com weight=3; # ← 錯誤! # 可以使用 backup server backend4.example.com backup; # 可以標記為 down server backend5.example.com down;
}
1.4. 通用雜湊(hash)
通用雜湊允許基於任意變數進行雜湊計算。
基本設定:
upstream backend { hash $request_uri consistent;server backend1.example.com; server backend2.example.com; server backend3.example.com;}
相同 URI → 相同伺服器
/api/users → 永遠是 backend2
/api/products → 永遠是 backend1
基於不同變數的雜湊:
# 1. URI 雜湊(快取友好) upstream cache_backend { hash $request_uri consistent;server cache1.example.com; server cache2.example.com; server cache3.example.com;}
2. Cookie 雜湊
upstream cookie_backend { hash $cookie_sessionid consistent;
server app1.example.com; server app2.example.com;}
3. 自訂標頭雜湊
upstream custom_backend { hash $http_x_tenant_id consistent;
server tenant1.example.com; server tenant2.example.com;}
4. 查詢參數雜湊
upstream param_backend { hash $arg_user_id consistent;
server user1.example.com; server user2.example.com;
}
優點:
- 彈性高(可基於任意變數雜湊)
- 一致性雜湊減少快取失效
- 適合快取策略
缺點:
- 比其他方法複雜
- 需要了解雜湊鍵的分佈
使用場景:
- 快取層(CDN、代理快取)
- 多租戶應用程式
- 分片策略
1.5. 隨機
隨機方法隨機選擇伺服器(可選擇性地帶有權重)。
設定:
upstream backend { random; # 或: random two least_conn;server backend1.example.com; server backend2.example.com; server backend3.example.com;
}
優點:
- 簡單
- 伺服器數量多時分配良好
- 低開銷
缺點:
- 不可預測
- 不維護 Session 親和性
2. upstream 區塊詳細設定
2.1. 基本 upstream 設定
upstream backend { server backend1.example.com:8080; server backend2.example.com:8080; server backend3.example.com:8080; }server { listen 80;
location / { proxy_pass http://backend; }
}
2.2. 多個連接埠的伺服器
upstream multi_port_backend {
server backend.example.com:8080;
server backend.example.com:8081;
server backend.example.com:8082;
}
2.3. Unix Socket 連線
upstream socket_backend { server unix:/var/run/app1.sock; server unix:/var/run/app2.sock; server unix:/var/run/app3.sock; }server { listen 80;
location / { proxy_pass http://socket_backend; }
}
2.4. IPv6 支援
upstream ipv6_backend {
server [2001:db8::1]:8080;
server [2001:db8::2]:8080;
server [2001:db8::3]:8080;
}
2.5. 混合設定
upstream mixed_backend { # TCP 伺服器 server backend1.example.com:8080; server 192.168.1.100:8080;# Unix sockets server unix:/var/run/app.sock; # IPv6 server [2001:db8::1]:8080;
}
3. 備援伺服器與權重
3.1. 權重(Weight)
權重決定每台伺服器接收請求的比例。
基本設定:
upstream backend { server backend1.example.com weight=3; # 60% 流量 server backend2.example.com weight=1; # 20% 流量 server backend3.example.com weight=1; # 20% 流量 }總權重 = 3 + 1 + 1 = 5
backend1: 3/5 = 60%
backend2: 1/5 = 20%
backend3: 1/5 = 20%
使用案例1:處理能力不同的伺服器
upstream capacity_backend { # 大型伺服器 - 處理50%流量 server large.example.com weight=5;# 中型伺服器 - 各25% server medium1.example.com weight=2.5; server medium2.example.com weight=2.5;
}
使用案例2:金絲雀部署
upstream canary_backend { # 生產伺服器 - 90% 流量 server prod1.example.com weight=45; server prod2.example.com weight=45;# 金絲雀伺服器 - 10% 流量 server canary.example.com weight=10;}
server { listen 80; server_name app.example.com;
location / { proxy_pass http://canary_backend; add_header X-Upstream-Server $upstream_addr always; }
}
使用案例3:藍綠部署
upstream bluegreen_backend { # Blue(當前)- 初始 100% server blue.example.com weight=10;# Green(新版)- 初始 0% server green.example.com weight=0;}
逐步遷移:
步驟1: weight=10 / weight=0 (100% blue)
步驟2: weight=9 / weight=1 (90% blue, 10% green)
步驟3: weight=5 / weight=5 (各 50%)
步驟4: weight=1 / weight=9 (10% blue, 90% green)
步驟5: weight=0 / weight=10 (100% green)
重要:ip_hash 中 weight 無效
upstream bad_config { ip_hash;# ip_hash 中 weight 會被忽略! server backend1.example.com weight=3; # ← 無效! server backend2.example.com weight=1;
}
3.2. 備援伺服器
備援伺服器只在所有主要伺服器都故障時才接收流量。
基本設定:
upstream backend { server backend1.example.com; server backend2.example.com; server backend3.example.com;# 備援伺服器 server backup1.example.com backup; server backup2.example.com backup;
}
運作方式:
正常運作時:
- backend1、backend2、backend3 處理流量
- backup1、backup2 閒置
backend1 故障:
- backend2、backend3 處理流量
- backup1、backup2 仍閒置
所有主要伺服器故障:
- backup1、backup2 開始處理流量
backend1 恢復:
-
流量回到 backend1
backup1、backup2 再次閒置
維護模式範例:
upstream maintenance_backend { server prod1.example.com max_fails=3 fail_timeout=30s; server prod2.example.com max_fails=3 fail_timeout=30s; server prod3.example.com max_fails=3 fail_timeout=30s;維護頁面伺服器
server maintenance.example.com:8080 backup; }
server { listen 80; server_name app.example.com;
location / { proxy_pass http://maintenance_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
}
3.3. 權重與備援的組合
情境:生產 + 測試 + 緊急
upstream prod_staging_emergency { # 生產伺服器 - 主要流量 server prod1.example.com weight=5 max_fails=3 fail_timeout=30s; server prod2.example.com weight=5 max_fails=3 fail_timeout=30s;# 測試伺服器 - 備援 server staging.example.com weight=2 backup max_fails=5; # 緊急靜態伺服器 - 最後手段 server emergency.example.com backup;
}
4. 黏性連線(Sticky Sessions)
4.1. IP 雜湊方式(內建)
upstream backend { ip_hash;server backend1.example.com; server backend2.example.com; server backend3.example.com;
}
ip_hash 的限制:
- 在 NAT 後面效果不佳
- 分配不均
- 彈性不足
4.2. Cookie 雜湊(推薦)
upstream backend { hash $cookie_sessionid consistent;server backend1.example.com; server backend2.example.com; server backend3.example.com;}
server { listen 80;
location / { proxy_pass http://backend; proxy_set_header Cookie $http_cookie; }
}
4.3. 自訂 Session 管理
後端建立 Session Cookie:
// Node.js 範例
app.use((req, res, next) => {
if (!req.cookies.server_id) {
// 用伺服器識別碼設定 Cookie
res.cookie('server_id', process.env.SERVER_ID, {
maxAge: 3600000,
httpOnly: true
});
}
next();
});
基於 Cookie 的 Nginx 路由:
map $cookie_server_id $backend_server { "server1" "backend1.example.com:8080"; "server2" "backend2.example.com:8080"; "server3" "backend3.example.com:8080"; default "backend1.example.com:8080"; }server { listen 80;
location / { proxy_pass http://$backend_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Cookie $http_cookie; }
}
4.4. 標頭 Session 親和性
upstream backend { hash $http_x_session_id consistent;server backend1.example.com; server backend2.example.com; server backend3.example.com;}
server { listen 80;
location / { proxy_pass http://backend; proxy_set_header X-Session-ID $http_x_session_id; }
}
5. 健康檢查
5.1. 被動健康檢查(開源版)
upstream backend { server backend1.example.com max_fails=3 fail_timeout=30s; server backend2.example.com max_fails=3 fail_timeout=30s; server backend3.example.com max_fails=3 fail_timeout=30s; }max_fails=3: 3次失敗後標記為下線
fail_timeout=30s: 30秒後重試
詳細參數:
upstream detailed_health { server backend1.example.com max_fails=5 # 標記為下線的失敗次數 fail_timeout=60s # 重試前的等待時間 max_conns=1000; # 最大並發連線數server backend2.example.com max_fails=3 fail_timeout=30s; server backend3.example.com backup;
}
5.2. 健康檢查端點
後端健康端點(Node.js):
const express = require('express'); const app = express();app.get('/health', (req, res) => { const dbOk = checkDatabase(); const memUsage = process.memoryUsage(); const memOk = memUsage.heapUsed < 500 * 1024 * 1024; const depsOk = checkDependencies();
if (dbOk && memOk && depsOk) { res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime() }); } else { res.status(503).json({ status: 'unhealthy', database: dbOk, memory: memOk, dependencies: depsOk }); }});
app.listen(3000);
Nginx 健康檢查路由:
upstream backend { server backend1.example.com:3000 max_fails=3 fail_timeout=30s; server backend2.example.com:3000 max_fails=3 fail_timeout=30s; server backend3.example.com:3000 max_fails=3 fail_timeout=30s; }server { listen 80; server_name example.com;
location / { proxy_pass http://backend; } # 健康檢查端點(僅內部使用) location /health { access_log off; proxy_pass http://backend/health; allow 127.0.0.1; allow 10.0.0.0/8; deny all; }
}
5.3. 外部健康檢查腳本
監控腳本(Python):
#!/usr/bin/env python3health_monitor.py
import requests import time from datetime import datetime
BACKENDS = [ 'http://backend1.example.com:3000/health', 'http://backend2.example.com:3000/health', 'http://backend3.example.com:3000/health', ]
CHECK_INTERVAL = 10 # 秒 UNHEALTHY_THRESHOLD = 3
backend_fail_counts = {url: 0 for url in BACKENDS}
def check_health(url): try: response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() return data.get('status') == 'healthy' return False except Exception as e: print(f"檢查 {url} 時發生錯誤: {e}") return False
def main(): while True: for url in BACKENDS: healthy = check_health(url)
if healthy: if backend_fail_counts[url] > 0: print(f"{datetime.now()} - {url} 已恢復") backend_fail_counts[url] = 0 else: backend_fail_counts[url] += 1 print(f"{datetime.now()} - {url} 不健康 " f"(連續失敗 {backend_fail_counts[url]} 次)") if backend_fail_counts[url] >= UNHEALTHY_THRESHOLD: print(f"將 {url} 標記為下線") time.sleep(CHECK_INTERVAL)
if name == 'main': main()
6. 實際應用範例
6.1. 高流量 Web 應用程式
upstream web_app { least_conn;server app1.example.com:8080 weight=5 max_fails=3 fail_timeout=30s max_conns=1000; server app2.example.com:8080 weight=5 max_fails=3 fail_timeout=30s max_conns=1000; server app3.example.com:8080 weight=3 max_fails=3 fail_timeout=30s max_conns=800; server backup.example.com:8080 backup; keepalive 128; keepalive_timeout 90s; keepalive_requests 1000;}
server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name www.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; location / { proxy_pass http://web_app; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering on; proxy_buffer_size 8k; proxy_buffers 16 8k; add_header X-Upstream-Server $upstream_addr always; add_header X-Upstream-Response-Time $upstream_response_time always; } location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ { root /var/www/static; expires 1y; add_header Cache-Control "public, immutable"; access_log off; }
}
6.2. 微服務的 API 閘道
# 使用者服務 upstream user_service { least_conn; server user1.internal:8001 max_fails=2 fail_timeout=20s; server user2.internal:8001 max_fails=2 fail_timeout=20s; keepalive 32; }商品服務
upstream product_service { least_conn; server product1.internal:8002 max_fails=2 fail_timeout=20s; server product2.internal:8002 max_fails=2 fail_timeout=20s; keepalive 32; }
訂單服務(購物車使用黏性連線)
upstream order_service { ip_hash; server order1.internal:8003 max_fails=2 fail_timeout=20s; server order2.internal:8003 max_fails=2 fail_timeout=20s; keepalive 32; }
支付服務(關鍵 - 高冗餘)
upstream payment_service { least_conn; server payment1.internal:8004 weight=3 max_fails=1 fail_timeout=10s; server payment2.internal:8004 weight=3 max_fails=1 fail_timeout=10s; server payment3.internal:8004 weight=2 max_fails=1 fail_timeout=10s; server payment_backup.internal:8004 backup; keepalive 64; }
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s; limit_req_zone $binary_remote_addr zone=payment_limit:10m rate=10r/s;
server { listen 443 ssl http2; server_name api.example.com;
proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; location /api/v1/users { limit_req zone=api_limit burst=50 nodelay; proxy_pass http://user_service; proxy_read_timeout 30s; } location /api/v1/products { limit_req zone=api_limit burst=50 nodelay; proxy_pass http://product_service; proxy_read_timeout 30s; } location /api/v1/orders { limit_req zone=api_limit burst=30 nodelay; proxy_pass http://order_service; proxy_read_timeout 60s; } location /api/v1/payments { limit_req zone=payment_limit burst=5 nodelay; proxy_pass http://payment_service; proxy_read_timeout 90s; proxy_connect_timeout 10s; }
}
7. 練習題
練習1:基本負載均衡
- 在連接埠 3000、3001、3002 執行應用程式的 3 個實例
- 使用 round-robin 設定 Nginx
- 產生流量並驗證分配情況(查看日誌)
練習2:加權負載均衡
- 設定 3 台權重分別為 5、3、2 的後端伺服器
- 產生 100 個請求
- 計算每台伺服器的請求數並驗證比例(約 50%、30%、20%)
練習3:黏性連線
- 設定 ip_hash 負載均衡
- 從同一客戶端發送多個請求
- 驗證所有請求都到達同一個後端
練習4:備援伺服器
- 設定 2 台主要伺服器和 1 台備援伺服器
- 停止兩台主要伺服器
- 驗證流量切換到備援伺服器
- 重新啟動主要伺服器並驗證流量回流
練習5:健康檢查
- 設定 max_fails=2 fail_timeout=30s 的後端
- 設定 /health 端點
- 模擬後端故障(停止服務)
- 監控日誌並驗證容錯移轉
- 重啟服務並驗證恢復
練習6:金絲雀部署
- 設定生產伺服器(各 weight=45)
- 新增金絲雀伺服器(weight=10)
- 驗證 10% 的流量流向金絲雀
- 逐漸增加金絲雀的權重
- 完成遷移至金絲雀
8. 故障排除
8.1. 分配不均
問題:
伺服器1: 1000 個請求
伺服器2: 500 個請求
伺服器3: 300 個請求
診斷:
# 檢查權重 grep -A10 "upstream" /etc/nginx/nginx.conf確認是否少數客戶端使用了 ip_hash
確認伺服器健康/效能狀況
解決方案:
# 用 least_conn 取代 round-robin
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
8.2. 黏性連線無效
解決方案:
# 用 Cookie 雜湊取代 ip_hash
upstream backend {
hash $cookie_sessionid consistent;
server backend1.example.com;
server backend2.example.com;
}
8.3. 健康檢查未偵測到故障
解決方案:
# 降低閾值
upstream backend {
server backend1.example.com max_fails=2 fail_timeout=10s;
server backend2.example.com max_fails=2 fail_timeout=10s;
}
8.4. 後端逾時問題
問題: 504 Gateway Timeout 錯誤
解決方案:
location / { proxy_pass http://backend;proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 120s;
}
8.5. 連線池耗盡
問題: 後端連線過多,高負載時出現 502 錯誤
解決方案:
upstream backend { server backend1.example.com max_conns=1000; server backend2.example.com max_conns=1000;keepalive 128; keepalive_timeout 75s; keepalive_requests 1000;}
server { location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; } }
9. 最佳實踐
9.1. 設定
# 1. 使用描述性的 upstream 名稱 upstream user_api_backend { # 良好範例 # ... }2. 為設定加上文件說明
upstream payment_service { # 使用 least_conn 處理不定的處理時間 # 為關鍵路徑設置嚴格的健康檢查 least_conn;
server payment1.internal:8080 weight=3 max_fails=1 fail_timeout=10s; server payment2.internal:8080 weight=3 max_fails=1 fail_timeout=10s; server payment_backup.internal:8080 backup; keepalive 32;}
3. 分離關注點
include /etc/nginx/conf.d/upstreams/.conf; include /etc/nginx/conf.d/servers/.conf;
9.2. 效能
upstream optimized_backend { # 1. 使用 least_conn 獲得更好的分配 least_conn;# 2. 啟用 keepalive keepalive 128; keepalive_timeout 75s; keepalive_requests 1000; # 3. 設置適當的 max_conns server backend1.example.com max_conns=1000; server backend2.example.com max_conns=1000;}
server { location / { proxy_pass http://optimized_backend;
# 4. 使用 HTTP/1.1 搭配 keepalive proxy_http_version 1.1; proxy_set_header Connection ""; # 5. 啟用緩衝 proxy_buffering on; proxy_buffer_size 8k; proxy_buffers 16 8k; }
}
9.3. 監控
# 記錄 upstream 資訊 log_format upstream_log '$remote_addr - [$time_local] ' '"$request" $status ' 'upstream: $upstream_addr ' 'response_time: $upstream_response_time ' 'connect_time: $upstream_connect_time';access_log /var/log/nginx/upstream.log upstream_log;
新增除錯標頭(非生產環境)
add_header X-Upstream-Server $upstream_addr always; add_header X-Upstream-Response-Time $upstream_response_time always;
啟用 stub_status
server { listen 8080; location /nginx_status { stub_status; allow 127.0.0.1; deny all; } }
9.4. 安全性
# 1. 限制每台伺服器的最大連線數 upstream backend { server backend1.example.com max_conns=1000; }2. 設定逾時
proxy_connect_timeout 10s; proxy_send_timeout 60s; proxy_read_timeout 60s;
3. 隱藏 upstream 錯誤
proxy_intercept_errors on; error_page 502 503 504 /50x.html;
4. 速率限制
limit_req_zone $binary_remote_addr zone=backend_limit:10m rate=100r/s;
location / { limit_req zone=backend_limit burst=50 nodelay; proxy_pass http://backend; }
總結
本課學到了:
- ✅ 負載均衡演算法(round-robin、least_conn、ip_hash、hash、random)
- ✅ upstream 區塊詳細設定
- ✅ 備援伺服器與權重分配
- ✅ 黏性連線策略
- ✅ 主動與被動健康檢查
- ✅ 實際應用範例與最佳實踐
下一課: 快取 — 如何快取靜態內容、API 回應,以及使用代理快取最佳化效能。