負載平衡(Load Balancing)是現代高可用性架構中不可或缺的技術。本文將詳細介紹如何在 Ubuntu 22.04 上使用 Nginx 設定負載平衡,包含 Upstream 設定、負載平衡演算法、健康檢查及 Session 持久性等進階功能。
負載平衡簡介
什麼是負載平衡?
負載平衡是一種將網路流量分散到多個伺服器的技術,目的是確保沒有單一伺服器承擔過多的請求負載。透過負載平衡,可以提高應用程式的可用性、可靠性和效能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| ┌─────────────────┐
│ 後端伺服器 1 │
│ 192.168.1.10 │
└─────────────────┘
▲
│
用戶端 ──────► Nginx LB ──────┼──────► 後端伺服器 2
│ 192.168.1.11
│
▼
┌─────────────────┐
│ 後端伺服器 3 │
│ 192.168.1.12 │
└─────────────────┘
|
負載平衡的優點
| 優點 | 說明 |
|---|
| 高可用性 | 當一台伺服器故障時,流量自動導向其他正常伺服器 |
| 擴展性 | 可輕鬆新增或移除後端伺服器以應對流量變化 |
| 效能提升 | 將請求分散到多台伺服器,提高整體處理能力 |
| 彈性部署 | 可進行滾動更新,不影響服務運作 |
| 資源優化 | 根據伺服器能力分配適當的流量比例 |
前置需求
確保已安裝 Nginx:
1
2
| sudo apt update
sudo apt install nginx -y
|
確認 Nginx 已啟動:
1
2
3
| sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx
|
Upstream 設定
Upstream 是 Nginx 中用於定義後端伺服器群組的區塊。所有負載平衡的設定都基於 upstream 區塊進行配置。
基本 Upstream 設定
建立負載平衡設定檔:
1
| sudo nano /etc/nginx/sites-available/load-balancer
|
基本的 upstream 設定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| upstream backend_cluster {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_cluster;
proxy_http_version 1.1;
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;
}
}
|
Upstream 伺服器參數
每個 server 指令都可以配置多種參數:
1
2
3
4
5
6
7
| upstream backend_cluster {
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
server 192.168.1.13:8080 backup;
server 192.168.1.14:8080 down;
}
|
| 參數 | 說明 |
|---|
weight=n | 設定伺服器權重,預設為 1 |
max_conns=n | 限制與該伺服器的最大同時連線數 |
max_fails=n | 允許的最大失敗次數,預設為 1 |
fail_timeout=time | 失敗後暫停使用的時間,預設為 10 秒 |
backup | 標記為備援伺服器,僅在主要伺服器都失敗時使用 |
down | 標記伺服器為永久離線 |
使用 Unix Socket
除了 TCP 連線,也可以使用 Unix Socket:
1
2
3
4
5
6
7
| upstream php_backend {
server unix:/var/run/php/php8.1-fpm.sock;
}
upstream node_backend {
server unix:/var/run/node/app.sock;
}
|
負載平衡演算法
Nginx 提供多種負載平衡演算法,可根據不同場景選擇適合的策略。
Round Robin(輪詢)
預設的負載平衡演算法,依序將請求分配到每個伺服器:
1
2
3
4
5
6
| upstream backend_cluster {
# Round Robin 是預設演算法,不需要額外指令
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
特點:
- 簡單且公平
- 適合伺服器效能相近的情況
- 不考慮伺服器當前負載
Weighted Round Robin(加權輪詢)
根據伺服器效能差異分配不同權重:
1
2
3
4
5
| upstream backend_cluster {
server 192.168.1.10:8080 weight=5; # 高效能伺服器
server 192.168.1.11:8080 weight=3; # 中等效能伺服器
server 192.168.1.12:8080 weight=1; # 低效能伺服器
}
|
特點:
- 適合伺服器效能不一致的情況
- 權重越高,分配到的請求越多
- 可根據伺服器規格調整權重
Least Connections(最少連線)
將請求分配給當前連線數最少的伺服器:
1
2
3
4
5
6
| upstream backend_cluster {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
特點:
- 適合請求處理時間差異較大的場景
- 動態平衡伺服器負載
- 避免單一伺服器過載
Weighted Least Connections(加權最少連線)
結合權重和最少連線:
1
2
3
4
5
6
| upstream backend_cluster {
least_conn;
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
}
|
IP Hash
根據用戶端 IP 位址決定分配到哪台伺服器:
1
2
3
4
5
6
| upstream backend_cluster {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
特點:
- 同一用戶端 IP 總是連接到同一台伺服器
- 提供基本的 Session 持久性
- 注意:如果通過 CDN 或代理,所有請求可能來自相同 IP
Generic Hash
使用自定義的 key 進行 hash:
1
2
3
4
5
6
| upstream backend_cluster {
hash $request_uri consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
特點:
- 可使用任意變數作為 hash key
consistent 參數啟用一致性 hash,減少伺服器變動時的影響
Random(隨機)
隨機選擇伺服器:
1
2
3
4
5
6
| upstream backend_cluster {
random two least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
特點:
two 參數:隨機選擇兩台伺服器,然後根據指定方法選擇其一- 可結合
least_conn 或 least_time 使用
演算法比較表
| 演算法 | 適用場景 | 優點 | 缺點 |
|---|
| Round Robin | 伺服器效能相近 | 簡單公平 | 不考慮伺服器負載 |
| Weighted Round Robin | 伺服器效能不一 | 可調整流量比例 | 需要手動設定權重 |
| Least Connections | 請求處理時間不一 | 動態平衡負載 | 可能造成頻繁切換 |
| IP Hash | 需要 Session 黏著 | 簡單的持久性 | 可能分配不均 |
| Generic Hash | 特定路由需求 | 靈活可自定義 | 配置較複雜 |
健康檢查
健康檢查確保流量只會被導向正常運作的伺服器。
被動健康檢查
Nginx 開源版本提供被動健康檢查,透過監控實際請求的回應來判斷伺服器狀態:
1
2
3
4
5
| upstream backend_cluster {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}
|
參數說明:
| 參數 | 說明 |
|---|
max_fails=3 | 在 fail_timeout 時間內允許的最大失敗次數 |
fail_timeout=30s | 失敗計數的時間窗口,以及伺服器被標記為不可用後的等待時間 |
運作邏輯:
- 當伺服器在 30 秒內失敗 3 次,則標記為不可用
- 等待 30 秒後,Nginx 會再次嘗試向該伺服器發送請求
- 如果請求成功,伺服器恢復為可用狀態
設定失敗的判斷條件
可以自定義什麼情況算作「失敗」:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| upstream backend_cluster {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_cluster;
# 定義哪些錯誤需要嘗試下一台伺服器
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
# 嘗試下一台伺服器的超時時間
proxy_next_upstream_timeout 10s;
# 最多嘗試的伺服器數量
proxy_next_upstream_tries 3;
# 連線超時設定
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
}
|
proxy_next_upstream 參數:
| 參數 | 說明 |
|---|
error | 與伺服器建立連線、傳送請求或讀取回應時發生錯誤 |
timeout | 連線超時 |
invalid_header | 伺服器回傳空白或無效的回應 |
http_500 | 伺服器回傳 500 錯誤 |
http_502 | 伺服器回傳 502 錯誤 |
http_503 | 伺服器回傳 503 錯誤 |
http_504 | 伺服器回傳 504 錯誤 |
http_403 | 伺服器回傳 403 錯誤 |
http_404 | 伺服器回傳 404 錯誤 |
non_idempotent | 允許對非冪等請求(POST、LOCK、PATCH)重試 |
off | 禁用重試功能 |
慢啟動(Slow Start)
防止剛恢復的伺服器立即承受大量流量(需要 Nginx Plus):
1
2
3
4
5
| upstream backend_cluster {
server 192.168.1.10:8080 slow_start=30s;
server 192.168.1.11:8080 slow_start=30s;
server 192.168.1.12:8080;
}
|
使用第三方模組進行主動健康檢查
開源版本可以使用 nginx_upstream_check_module 模組:
編譯安裝包含健康檢查模組的 Nginx:
1
2
3
4
| # 下載並編譯(僅供參考)
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
./configure --add-module=/path/to/nginx_upstream_check_module
make && sudo make install
|
配置主動健康檢查:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| upstream backend_cluster {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
listen 80;
# 健康檢查狀態頁面
location /upstream_status {
check_status;
access_log off;
allow 127.0.0.1;
deny all;
}
}
|
Session 持久性
Session 持久性(也稱為 Session 黏著或 Sticky Session)確保同一用戶的請求總是被導向同一台後端伺服器。
為什麼需要 Session 持久性?
在以下情況需要 Session 持久性:
- 應用程式將 Session 資料儲存在本地記憶體
- 購物車、登入狀態等需要保持一致
- 長時間連線的應用(如 WebSocket)
方法一:IP Hash
最簡單的 Session 持久性方法:
1
2
3
4
5
6
| upstream backend_cluster {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
優點:
缺點:
- 通過 NAT 或代理的用戶可能都被分配到同一台伺服器
- 無法在伺服器故障時保持 Session
方法二:Cookie-based Hash
使用 Cookie 進行 hash:
1
2
3
4
5
6
| upstream backend_cluster {
hash $cookie_jsessionid consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
方法三:Sticky Cookie(Nginx Plus)
Nginx Plus 提供更強大的 Sticky Session 功能:
1
2
3
4
5
6
7
| upstream backend_cluster {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
|
方法四:使用外部 Session 存儲
最推薦的方式是使用共享 Session 存儲(如 Redis):
1
2
3
4
5
| 用戶端 ──► Nginx LB ──┬──► 後端伺服器 1 ──┐
│ │
├──► 後端伺服器 2 ──┼──► Redis Session Store
│ │
└──► 後端伺服器 3 ──┘
|
這樣就不需要 Session 持久性,因為所有伺服器都可以存取相同的 Session 資料。
開源 Nginx Sticky Module
可以使用 nginx-sticky-module-ng 來實現 Sticky Session:
1
2
3
4
| # 編譯安裝(僅供參考)
git clone https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng.git
./configure --add-module=/path/to/nginx-sticky-module-ng
make && sudo make install
|
配置範例:
1
2
3
4
5
6
| upstream backend_cluster {
sticky;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
或使用更詳細的設定:
1
2
3
4
5
6
| upstream backend_cluster {
sticky name=route expires=1h domain=.example.com path=/ secure httponly;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
|
完整設定範例
以下是一個包含所有功能的完整負載平衡設定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
| # 定義 upstream 群組
upstream backend_cluster {
least_conn;
server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.1.13:8080 backup;
keepalive 32;
}
# HTTP 重導向到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS 伺服器設定
server {
listen 443 ssl http2;
server_name example.com;
# SSL 設定
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 安全 Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
# 日誌設定
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
# 靜態檔案處理
location /static/ {
alias /var/www/example.com/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API 負載平衡
location /api/ {
proxy_pass http://backend_cluster;
proxy_http_version 1.1;
# Headers
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_set_header Connection "";
# 超時設定
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 緩衝設定
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# 健康檢查相關
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_next_upstream_timeout 10s;
proxy_next_upstream_tries 3;
}
# WebSocket 負載平衡
location /ws/ {
proxy_pass http://backend_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 86400;
proxy_send_timeout 86400;
}
# 預設位置
location / {
proxy_pass http://backend_cluster;
proxy_http_version 1.1;
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_set_header Connection "";
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
|
啟用設定
1
2
3
4
5
6
7
8
| # 建立符號連結
sudo ln -s /etc/nginx/sites-available/load-balancer /etc/nginx/sites-enabled/
# 測試設定
sudo nginx -t
# 重新載入 Nginx
sudo systemctl reload nginx
|
常見問題排解
檢查 Nginx 設定語法
查看錯誤日誌
1
| sudo tail -f /var/log/nginx/error.log
|
監控 upstream 狀態
可以使用 stub_status 模組監控基本狀態:
1
2
3
4
5
6
7
8
9
10
| server {
listen 8080;
server_name localhost;
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}
|
測試負載平衡
使用 curl 測試負載平衡是否正常運作:
1
2
| # 連續發送請求觀察分配情況
for i in {1..10}; do curl -s http://example.com/api/server-id; echo; done
|
常見錯誤及解決方案
| 錯誤 | 可能原因 | 解決方案 |
|---|
| 502 Bad Gateway | 後端伺服器未啟動 | 檢查後端服務狀態 |
| 503 Service Unavailable | 所有後端伺服器都不可用 | 檢查健康檢查設定和後端服務 |
| 504 Gateway Timeout | 後端回應超時 | 增加 proxy_read_timeout 值 |
| Connection refused | 無法連接到後端 | 檢查防火牆和後端服務埠 |
參考資料