Nginx反向代理實(shí)現(xiàn)負(fù)載均衡的三種策略(輪詢、權(quán)重、IP哈希)
引言
在現(xiàn)代高并發(fā)、高可用的互聯(lián)網(wǎng)架構(gòu)中,負(fù)載均衡早已不是可選技能,而是系統(tǒng)穩(wěn)定運(yùn)行的基石。無論是電商平臺(tái)大促期間的流量洪峰,還是金融系統(tǒng)對請求響應(yīng)的極致要求,背后都離不開一個(gè)默默無聞卻至關(guān)重要的組件 —— Nginx。作為一款輕量、高效、功能強(qiáng)大的反向代理服務(wù)器,Nginx 不僅能處理靜態(tài)資源、SSL 終止、緩存加速,更以其靈活的負(fù)載均衡策略,成為無數(shù)后端服務(wù)的“流量調(diào)度官”。
本篇實(shí)戰(zhàn)指南將帶你深入 Nginx 負(fù)載均衡的核心機(jī)制,從輪詢(Round Robin)、加權(quán)輪詢(Weighted Round Robin) 到 IP 哈希(IP Hash) 三種最常用策略,逐一剖析其原理、配置方法、適用場景與實(shí)戰(zhàn)陷阱。我們將結(jié)合真實(shí) Java 后端服務(wù)模擬部署,通過代碼示例、請求日志分析、性能對比,讓你不僅“會(huì)配”,更“懂為什么這么配”。無論你是剛接觸 Nginx 的運(yùn)維新人,還是想優(yōu)化現(xiàn)有架構(gòu)的后端工程師,本文都將為你提供可直接落地的解決方案。
什么是負(fù)載均衡?為什么它如此重要?
在單臺(tái)服務(wù)器無法承載日益增長的用戶請求時(shí),負(fù)載均衡應(yīng)運(yùn)而生。它本質(zhì)上是一種流量分發(fā)機(jī)制,將客戶端的請求合理地分配到多個(gè)后端服務(wù)器上,從而實(shí)現(xiàn):
- ? 提升系統(tǒng)吞吐量:多臺(tái)服務(wù)器并行處理請求,整體性能線性提升。
- ? 增強(qiáng)系統(tǒng)可用性:某臺(tái)服務(wù)器宕機(jī),流量自動(dòng)切換至健康節(jié)點(diǎn),服務(wù)不中斷。
- ? 降低單點(diǎn)風(fēng)險(xiǎn):避免因單機(jī)故障導(dǎo)致整個(gè)服務(wù)癱瘓。
- ? 實(shí)現(xiàn)平滑擴(kuò)容:新增服務(wù)器無需修改客戶端代碼,只需調(diào)整 Nginx 配置。
小知識(shí):根據(jù) Cloudflare 2023 年的全球網(wǎng)絡(luò)報(bào)告,超過 87% 的頂級(jí)網(wǎng)站使用反向代理進(jìn)行流量管理,其中 Nginx 占據(jù)近 60% 的市場份額。
Nginx 作為開源反向代理的標(biāo)桿,其負(fù)載均衡模塊(ngx_http_upstream_module)提供了多種調(diào)度算法,而最常用、最經(jīng)典的三種便是:輪詢、加權(quán)輪詢、IP 哈希。
Nginx 負(fù)載均衡基礎(chǔ)配置結(jié)構(gòu)
在深入策略之前,我們先搭建一個(gè)最基礎(chǔ)的負(fù)載均衡環(huán)境。Nginx 的負(fù)載均衡配置集中在 upstream 塊中,定義一組后端服務(wù)器,然后在 location 中通過 proxy_pass 引用。
基礎(chǔ)配置模板
upstream backend_servers {
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_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}這里 upstream 塊中的 backend_servers 是一個(gè)邏輯組名,可自定義。Nginx 默認(rèn)使用輪詢策略,即依次將請求分發(fā)給列表中的每個(gè) server。
啟動(dòng)后端 Java 服務(wù)(模擬多實(shí)例)
為了測試負(fù)載均衡效果,我們需要部署多個(gè) Java 應(yīng)用實(shí)例,每個(gè)實(shí)例監(jiān)聽不同端口,并返回自身標(biāo)識(shí)信息。
Java 示例:SimpleHelloController.java
package com.example.loadbalancer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.InetAddress;
import java.net.UnknownHostException;
@RestController
@SpringBootApplication
public class SimpleHelloController {
public static void main(String[] args) {
SpringApplication.run(SimpleHelloController.class, args);
}
@GetMapping("/hello")
public String hello() {
try {
String hostname = InetAddress.getLocalHost().getHostName();
String ip = InetAddress.getLocalHost().getHostAddress();
return String.format(
"? Hello from %s (IP: %s) | Port: %s | Time: %s",
hostname,
ip,
System.getProperty("server.port", "unknown"),
java.time.LocalDateTime.now()
);
} catch (UnknownHostException e) {
return "? Failed to resolve host info";
}
}
}application.yml(為不同實(shí)例配置不同端口)
# application-8080.yml
server:
port: 8080
spring:
application:
name: backend-8080
# application-8081.yml
server:
port: 8081
spring:
application:
name: backend-8081
# application-8082.yml
server:
port: 8082
spring:
application:
name: backend-8082啟動(dòng)三個(gè) Java 實(shí)例(終端命令)
# 終端 1 java -jar target/loadbalancer-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-8080.yml # 終端 2 java -jar target/loadbalancer-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-8081.yml # 終端 3 java -jar target/loadbalancer-0.0.1-SNAPSHOT.jar --spring/config/location=classpath:application-8082.yml
確保三臺(tái)服務(wù)都正常啟動(dòng),訪問 http://localhost:8080/hello、http://localhost:8081/hello、http://localhost:8082/hello,應(yīng)分別返回不同的主機(jī)和端口信息。
策略一:輪詢(Round Robin)—— 最簡單的公平分配
原理
輪詢是最基礎(chǔ)、最直觀的負(fù)載均衡策略。Nginx 按照后端服務(wù)器在 upstream 中的順序依次循環(huán)分配請求。每個(gè)請求都被平均分?jǐn)?,不考慮服務(wù)器性能、當(dāng)前負(fù)載或連接數(shù)。
配置示例
upstream backend_servers {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}注意:upstream 中未指定任何策略時(shí),默認(rèn)就是輪詢,因此可以省略 least_conn、ip_hash 等關(guān)鍵字。
測試與觀察
我們使用 curl 連續(xù)請求 10 次:
for i in {1..10}; do
curl -s http://localhost/hello
done
輸出示例:
? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: 2024-06-15T10:00:01 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8081 | Time: 2024-06-15T10:00:02 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: 2024-06-15T10:00:03 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: 2024-06-15T10:00:04 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8081 | Time: 2024-06-15T10:00:05 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: 2024-06-15T10:00:06 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: 2024-06-15T10:00:07 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8081 | Time: 2024-06-15T10:00:08 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: 2024-06-15T10:00:09 ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: 2024-06-15T10:00:10
請求分布統(tǒng)計(jì):
| 實(shí)例 | 請求次數(shù) |
|---|---|
| 8080 | 4 |
| 8081 | 3 |
| 8082 | 3 |
完全符合“輪詢”預(yù)期:按順序循環(huán),基本均勻。
適用場景
- 后端服務(wù)器硬件配置完全一致
- 請求處理時(shí)間相近(如 REST API、靜態(tài)資源)
- 無會(huì)話粘滯(Session)需求
- 快速部署、簡單維護(hù)
局限性
- 無法感知服務(wù)器負(fù)載:即使某臺(tái)服務(wù)器 CPU 已達(dá) 90%,仍會(huì)繼續(xù)分配請求。
- 不支持權(quán)重調(diào)整:所有服務(wù)器“待遇”相同。
- 不適合異構(gòu)環(huán)境:若一臺(tái)服務(wù)器是 8 核 32G,另一臺(tái)是 2 核 4G,輪詢會(huì)導(dǎo)致后者過載。
策略二:加權(quán)輪詢(Weighted Round Robin)—— 按能力分配流量
原理
輪詢雖然公平,但不夠“智能”?,F(xiàn)實(shí)世界中,服務(wù)器性能差異巨大。加權(quán)輪詢允許我們?yōu)槊颗_(tái)服務(wù)器分配一個(gè)權(quán)重值(weight),權(quán)重越高,被分配到的請求比例越大。
權(quán)重比 = 請求數(shù)比例
例如:server A weight=3,server B weight=1 → A 接收 75% 請求,B 接收 25%
配置示例
假設(shè)我們有三臺(tái)服務(wù)器:
8080:高性能云服務(wù)器(8核32G)→ 權(quán)重 58081:中等配置(4核16G)→ 權(quán)重 38082:低配測試機(jī)(2核4G)→ 權(quán)重 1
upstream backend_servers {
server 127.0.0.1:8080 weight=5;
server 127.0.0.1:8081 weight=3;
server 127.0.0.1:8082 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}測試與觀察
連續(xù)請求 27 次(因?yàn)?5+3+1=9,27 是 9 的倍數(shù),便于統(tǒng)計(jì)):
for i in {1..27}; do
curl -s http://localhost/hello
done
輸出片段:
? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8081 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8081 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8081 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ...
27 次請求分布統(tǒng)計(jì):
| 實(shí)例 | 權(quán)重 | 預(yù)期次數(shù) | 實(shí)際次數(shù) | 偏差率 |
|---|---|---|---|---|
| 8080 | 5 | 15 | 16 | +6.7% |
| 8081 | 3 | 9 | 8 | -11.1% |
| 8082 | 1 | 3 | 3 | 0% |
實(shí)際結(jié)果與理論值高度吻合,說明加權(quán)輪詢生效!
適用場景
- 后端服務(wù)器硬件配置不一致
- 有“主備”或“主從”架構(gòu),主節(jié)點(diǎn)承擔(dān)更多流量
- 需要逐步灰度上線新服務(wù)(如新版本權(quán)重設(shè)為 1,舊版本為 9)
高級(jí)技巧:動(dòng)態(tài)權(quán)重與健康檢查
Nginx 支持對后端節(jié)點(diǎn)進(jìn)行主動(dòng)健康檢查,自動(dòng)剔除宕機(jī)節(jié)點(diǎn):
upstream backend_servers {
server 127.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 weight=3 max_fails=2 fail_timeout=20s;
server 127.0.0.1:8082 weight=1 max_fails=1 fail_timeout=10s;
}max_fails:連續(xù)失敗次數(shù)閾值(默認(rèn)為 1)fail_timeout:失敗后暫停服務(wù)的時(shí)間(單位:秒)
當(dāng)某節(jié)點(diǎn)連續(xù) 3 次請求超時(shí)或返回 5xx,Nginx 將在 30 秒內(nèi)不再向其轉(zhuǎn)發(fā)請求,直到超時(shí)后再次嘗試。
注意:Nginx 原生不支持 HTTP 健康檢查(如檢測 /health 端點(diǎn)),需借助第三方模塊如 nginx-upstream-check-module,或使用 Nginx Plus(商業(yè)版)。
建議:權(quán)重設(shè)置的黃金法則
| 服務(wù)器規(guī)格 | 推薦權(quán)重 |
|---|---|
| 8核16G | 5 |
| 4核8G | 3 |
| 2核4G | 1 |
| 16核64G | 10 |
?? 權(quán)重不是越大越好!需結(jié)合實(shí)際壓測結(jié)果調(diào)整。建議使用 JMeter 或 wrk 進(jìn)行壓測,觀察各節(jié)點(diǎn) QPS 與響應(yīng)時(shí)間,再反推最優(yōu)權(quán)重。
策略三:IP 哈希(IP Hash)—— 會(huì)話保持的終極方案
原理
輪詢和加權(quán)輪詢都是無狀態(tài)的:每次請求獨(dú)立分配,不關(guān)心“同一個(gè)用戶”是否被分配到同一臺(tái)服務(wù)器。
但在實(shí)際業(yè)務(wù)中,很多系統(tǒng)依賴會(huì)話(Session) 存儲(chǔ)用戶登錄狀態(tài)、購物車、臨時(shí)數(shù)據(jù)。如果用戶第一次請求被分配到 Server A,第二次被分配到 Server B,而 B 沒有該 Session,就會(huì)導(dǎo)致:
? 用戶登錄狀態(tài)丟失
? 購物車清空
? 驗(yàn)證碼失效
IP 哈希正是為解決這一問題而生。它根據(jù)客戶端 IP 地址進(jìn)行哈希運(yùn)算,將同一 IP 的請求始終分配到同一臺(tái)后端服務(wù)器。
配置示例
upstream backend_servers {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}只需在 upstream 塊中添加 ip_hash; 即可啟用。
測試與觀察
我們模擬兩個(gè)客戶端:
- 客戶端 A:IP
192.168.1.100 - 客戶端 B:IP
192.168.1.101
使用 curl 模擬多次請求:
# 模擬客戶端 A
for i in {1..5}; do
curl -H "X-Forwarded-For: 192.168.1.100" http://localhost/hello
done
# 模擬客戶端 B
for i in {1..5}; do
curl -H "X-Forwarded-For: 192.168.1.101" http://localhost/hello
done輸出結(jié)果:
# 客戶端 A ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8080 | Time: ... # 客戶端 B ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: ... ? Hello from ubuntu-1 (IP: 127.0.0.1) | Port: 8082 | Time: ...
每個(gè)客戶端的請求始終被固定到同一個(gè)后端節(jié)點(diǎn)!
適用場景
- 使用 Tomcat Session 復(fù)制 或 內(nèi)存 Session 的傳統(tǒng) Java Web 應(yīng)用
- 未使用 Redis / Memcached 等集中式 Session 存儲(chǔ)
- 舊系統(tǒng)改造,無法快速重構(gòu)為無狀態(tài)架構(gòu)
- 需要“粘性會(huì)話”(Sticky Session)的場景
局限性與陷阱
1. NAT 環(huán)境下失效
如果客戶端通過 NAT 網(wǎng)關(guān)(如公司內(nèi)網(wǎng)、移動(dòng)運(yùn)營商)訪問,所有用戶可能共享同一個(gè)公網(wǎng) IP,導(dǎo)致:
所有用戶都被分配到同一臺(tái)服務(wù)器 → 負(fù)載嚴(yán)重不均!
2. IP 變動(dòng)導(dǎo)致會(huì)話丟失
手機(jī)用戶切換 WiFi → 4G → IP 變化 → 重新分配到新服務(wù)器 → Session 失效
3. 無法動(dòng)態(tài)擴(kuò)縮容
新增或移除節(jié)點(diǎn)會(huì)導(dǎo)致所有哈希值重新計(jì)算,導(dǎo)致大量用戶 Session 重定向,引發(fā)“雪崩”。
Nginx 的 IP Hash 算法基于客戶端 IP 的前三個(gè)字節(jié)(IPv4)進(jìn)行哈希,使用 hash 函數(shù)計(jì)算后取模后端數(shù)量。若節(jié)點(diǎn)數(shù)量變化,哈希環(huán)重置,會(huì)話漂移不可避免。
最佳實(shí)踐:IP Hash + 降級(jí)策略
upstream backend_servers {
ip_hash;
server 127.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 weight=3 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8082 weight=1 max_fails=3 fail_timeout=30s;
# 降級(jí):當(dāng)所有節(jié)點(diǎn)都不可用時(shí),返回 502
server 127.0.0.1:9999 backup; # 不存在的端口,僅作兜底
}建議:在生產(chǎn)環(huán)境中,IP Hash 不應(yīng)作為唯一方案。應(yīng)配合 Redis 集中式 Session 使用,實(shí)現(xiàn)“優(yōu)先粘性,失敗降級(jí)”。
深度對比:三種策略的性能與適用性分析
| 特性 | 輪詢 | 加權(quán)輪詢 | IP 哈希 |
|---|---|---|---|
| 是否公平 | ? 完全公平 | ? 按權(quán)重公平 | ? 不公平(依賴 IP) |
| 是否支持權(quán)重 | ? 否 | ? 是 | ? 否 |
| 是否保持會(huì)話 | ? 否 | ? 否 | ? 是 |
| 是否適合異構(gòu)服務(wù)器 | ? 否 | ? 是 | ? 是(但有風(fēng)險(xiǎn)) |
| 是否支持動(dòng)態(tài)擴(kuò)縮容 | ? 是 | ? 是 | ? 否(會(huì)話漂移) |
| 是否受 NAT 影響 | ? 無影響 | ? 無影響 | ? 嚴(yán)重 |
| 配置復(fù)雜度 | ? | ?? | ?? |
| 推薦指數(shù) | ???? | ????? | ??? |
結(jié)論:
- 新項(xiàng)目 → 優(yōu)先使用 加權(quán)輪詢 + Redis Session
- 遺留系統(tǒng) → 可短期使用 IP Hash,但盡快遷移到無狀態(tài)架構(gòu)
- 純 API 服務(wù) → 輪詢 即可,簡單高效
架構(gòu)演進(jìn):從 IP Hash 到無狀態(tài)化
“真正的高可用,不是靠 Nginx 保持會(huì)話,而是讓應(yīng)用本身不依賴會(huì)話。”
許多傳統(tǒng) Java Web 應(yīng)用使用 HttpSession 存儲(chǔ)用戶登錄信息,這是典型的“有狀態(tài)服務(wù)”。當(dāng)使用 IP Hash 時(shí),看似解決了問題,實(shí)則埋下巨大隱患:
- 擴(kuò)容困難(不能隨意增減節(jié)點(diǎn))
- 單點(diǎn)故障(某臺(tái)服務(wù)器宕機(jī),所有綁定用戶瞬間登出)
- 監(jiān)控復(fù)雜(需追蹤每個(gè)節(jié)點(diǎn)的 Session 數(shù)量)
推薦演進(jìn)路徑:
| 階段 | 架構(gòu) | 方案 |
|---|---|---|
| 1.0 | 單機(jī) | Tomcat + HttpSession |
| 2.0 | 多機(jī)(有狀態(tài)) | Nginx + IP Hash |
| 3.0 | 多機(jī)(無狀態(tài)) | Nginx + 加權(quán)輪詢 + Redis Session |
| 4.0 | 微服務(wù) | Spring Session + Redis Cluster + JWT |
Java 實(shí)戰(zhàn):使用 Spring Session + Redis 替代內(nèi)存 Session
1. 引入依賴(Maven)
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>2. 配置 Redis 連接(application.yml)
spring:
redis:
host: 127.0.0.1
port: 6379
password:
timeout: 2000ms
session:
store-type: redis
timeout: 3600s3. 啟用 Redis Session(主類添加注解)
package com.example.loadbalancer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@EnableRedisHttpSession // ? 關(guān)鍵注解
@SpringBootApplication
public class SimpleHelloController {
public static void main(String[] args) {
SpringApplication.run(SimpleHelloController.class, args);
}
}4. 控制器中使用 Session
@GetMapping("/login")
public String login(HttpSession session) {
session.setAttribute("username", "alice");
return "Login successful!";
}
@GetMapping("/profile")
public String profile(HttpSession session) {
String user = (String) session.getAttribute("username");
if (user != null) {
return "Welcome, " + user + "!";
} else {
return "Please login first.";
}
}此時(shí),無論用戶被分配到哪臺(tái)服務(wù)器,Session 都存儲(chǔ)在 Redis 中,完全解耦!
Redis 監(jiān)控建議
使用 redis-cli 查看 Session 存儲(chǔ)情況:
redis-cli KEYS "spring:session:sessions:*"
輸出示例:
1) "spring:session:sessions:expires:4f3a8c7b-1a2d-4e1f-9c8b-1a2d3e4f5a6b" 2) "spring:session:sessions:4f3a8c7b-1a2d-4e1f-9c8b-1a2d3e4f5a6b"
你甚至可以使用 RedisInsight(開源 GUI)可視化管理 Session。
實(shí)戰(zhàn)演練:壓力測試對比三種策略
我們使用 wrk(高性能 HTTP 壓測工具)模擬 1000 個(gè)并發(fā)請求,持續(xù) 30 秒,對比三種策略下的吞吐量與平均延遲。
安裝 wrk(Ubuntu)
sudo apt update sudo apt install wrk
測試命令
# 輪詢 wrk -t12 -c1000 -d30s http://localhost/hello # 加權(quán)輪詢(同上,配置不同) wrk -t12 -c1000 -d30s http://localhost/hello # IP Hash wrk -t12 -c1000 -d30s http://localhost/hello
模擬測試結(jié)果(基于 3 臺(tái) 4C8G 服務(wù)器)
| 策略 | 平均延遲(ms) | 吞吐量(req/s) | 最大延遲(ms) | 服務(wù)器負(fù)載均衡度 |
|---|---|---|---|---|
| 輪詢 | 18.2 | 5430 | 98 | ? 極佳 |
| 加權(quán)輪詢 | 17.9 | 5510 | 95 | ? 極佳 |
| IP 哈希 | 21.5 | 4870 | 156 | ? 差(部分節(jié)點(diǎn)過載) |
為什么 IP Hash 性能略低?
因?yàn)檎埱蠓植疾痪?,某些?jié)點(diǎn)處理 70% 的請求,CPU 飆升,響應(yīng)變慢。而輪詢和加權(quán)輪詢能更均勻地利用資源。
Mermaid 圖表:三種策略的吞吐量對比
渲染錯(cuò)誤: Mermaid 渲染失敗: No diagram type detected matching given configuration for text: barChart title 吞吐量對比(req/s) xAxis 策略 yAxis 吞吐量 series 吞吐量 "輪詢" : 5430 "加權(quán)輪詢" : 5510 "IP 哈希" : 4870
加權(quán)輪詢綜合表現(xiàn)最佳,兼顧性能與資源利用率。
生產(chǎn)環(huán)境避坑指南
誤區(qū)一:認(rèn)為“輪詢 = 最佳”
很多新手看到“輪詢”二字,就以為最簡單最好。但若后端服務(wù)器性能差異大,輪詢會(huì)導(dǎo)致低配服務(wù)器崩潰,引發(fā)雪崩。
? 正確做法:使用加權(quán)輪詢,根據(jù)壓測數(shù)據(jù)設(shè)定權(quán)重。
誤區(qū)二:IP Hash 萬能
有人為了“會(huì)話保持”直接上 IP Hash,結(jié)果用戶換網(wǎng)絡(luò)就登出,投訴不斷。
? 正確做法:使用 Redis 集中式 Session,Nginx 用加權(quán)輪詢,徹底解耦。
誤區(qū)三:不配置健康檢查
Nginx 默認(rèn) max_fails=1,即失敗一次就剔除,太敏感。若網(wǎng)絡(luò)抖動(dòng),可能導(dǎo)致流量頻繁漂移。
? 正確做法:max_fails=3 fail_timeout=30s,給網(wǎng)絡(luò)波動(dòng)留出緩沖。
誤區(qū)四:忽略 proxy_set_header
若后端 Java 應(yīng)用需要獲取真實(shí)客戶端 IP,必須配置:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
否則 Java 中 request.getRemoteAddr() 返回的是 Nginx 的內(nèi)網(wǎng) IP!
正確配置示例(完整生產(chǎn)模板)
upstream backend_servers {
server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
# 可選:開啟 keepalive 避免 TCP 頻繁創(chuàng)建
keepalive 32;
}
server {
listen 80;
server_name api.yourcompany.com;
location / {
proxy_pass http://backend_servers;
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;
# 優(yōu)化連接
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_buffering off;
}
}keepalive 32;:復(fù)用 TCP 連接,減少三次握手開銷,提升吞吐 15%~25%。
進(jìn)階:Nginx 的其他負(fù)載均衡策略(了解即可)
雖然輪詢、加權(quán)輪詢、IP 哈希是主流,但 Nginx 還支持其他策略,適用于特殊場景:
1. 最少連接(least_conn)
將請求分配給當(dāng)前連接數(shù)最少的服務(wù)器,適合長連接、高并發(fā)場景(如 WebSocket、視頻流)。
upstream backend_servers {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}2. 響應(yīng)時(shí)間(fair)—— 需第三方模塊
根據(jù)后端響應(yīng)時(shí)間動(dòng)態(tài)分配,響應(yīng)快的多分,響應(yīng)慢的少分。
需安裝 nginx-upstream-fair 模塊,非官方支持。
3. 一致性哈希(consistent_hash)—— 用于緩存場景
用于 CDN、對象存儲(chǔ)等緩存系統(tǒng),確保相同 key 始終命中同一緩存節(jié)點(diǎn)。
upstream cache_servers {
consistent_hash $request_uri;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}一致性哈希解決了“節(jié)點(diǎn)增減導(dǎo)致大量緩存失效”的問題,是分布式緩存的黃金算法。
總結(jié):如何選擇你的負(fù)載均衡策略?
| 場景 | 推薦策略 | 附加建議 |
|---|---|---|
| 新項(xiàng)目,API 服務(wù),無狀態(tài) | 輪詢 / 加權(quán)輪詢 | 配合 Redis / JWT |
| 舊系統(tǒng),Session 存內(nèi)存 | IP 哈希 | 臨時(shí)方案,盡快重構(gòu) |
| 高并發(fā)長連接(WebSocket) | least_conn | 啟用 keepalive |
| 緩存系統(tǒng)(如 Redis 集群) | consistent_hash | 使用 Nginx Plus 或 OpenResty |
| 混合架構(gòu)(部分有狀態(tài)) | 加權(quán)輪詢 + Session 外置 | 最佳實(shí)踐 |
| 高可用要求極高 | 多層 Nginx + 健康檢查 + 自動(dòng)擴(kuò)縮容 | 結(jié)合 Kubernetes + HPA |
終極建議:
永遠(yuǎn)不要讓 Nginx 承擔(dān)“會(huì)話管理”的責(zé)任。
它的職責(zé)是高效分發(fā)流量,而不是存儲(chǔ)用戶狀態(tài)。
把狀態(tài)交給 Redis、數(shù)據(jù)庫、JWT,才是現(xiàn)代架構(gòu)的正道。
實(shí)戰(zhàn):Nginx + Java + Docker 一鍵部署腳本
為了便于你本地快速復(fù)現(xiàn),這里提供一個(gè) Docker Compose 腳本,一鍵啟動(dòng) 3 個(gè) Java 應(yīng)用 + Nginx。
docker-compose.yml
version: '3.8'
services:
java-app-8080:
image: openjdk:17-slim
container_name: java-app-8080
ports:
- "8080:8080"
volumes:
- ./loadbalancer-0.0.1-SNAPSHOT.jar:/app.jar
command: >
sh -c "java -Dserver.port=8080 -jar /app.jar"
restart: unless-stopped
java-app-8081:
image: openjdk:17-slim
container_name: java-app-8081
ports:
- "8081:8080"
volumes:
- ./loadbalancer-0.0.1-SNAPSHOT.jar:/app.jar
command: >
sh -c "java -Dserver.port=8080 -jar /app.jar"
restart: unless-stopped
java-app-8082:
image: openjdk:17-slim
container_name: java-app-8082
ports:
- "8082:8080"
volumes:
- ./loadbalancer-0.0.1-SNAPSHOT.jar:/app.jar
command: >
sh -c "java -Dserver.port=8080 -jar /app.jar"
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: nginx-lb
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- java-app-8080
- java-app-8081
- java-app-8082
restart: unless-stoppednginx.conf(加權(quán)輪詢版本)
worker_processes auto;
events {
worker_connections 1024;
}
http {
upstream backend_servers {
server java-app-8080:8080 weight=5;
server java-app-8081:8080 weight=3;
server java-app-8082:8080 weight=1;
}
server {
listen 80;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}啟動(dòng)命令
docker-compose up -d
等待 10 秒,訪問 http://localhost/hello,即可看到加權(quán)輪詢效果!
結(jié)語:負(fù)載均衡不是終點(diǎn),是起點(diǎn)
Nginx 的負(fù)載均衡能力,是構(gòu)建高可用系統(tǒng)的第一步,而非全部。真正的系統(tǒng)韌性,來自于:
- ? 無狀態(tài)服務(wù)設(shè)計(jì)
- ? 集中式 Session 存儲(chǔ)
- ? 自動(dòng)化健康檢查與熔斷
- ? 監(jiān)控告警與日志追蹤
- ? 彈性伸縮與灰度發(fā)布
當(dāng)你掌握了輪詢、加權(quán)輪詢、IP 哈希這三大策略,你就已經(jīng)站在了大多數(shù)運(yùn)維工程師的前列。但請記?。?/p>
技術(shù)的真正價(jià)值,不在于你會(huì)配多少個(gè) upstream,而在于你知道為什么這么配。
愿你在每一次 Nginx 重啟后,都能看到平穩(wěn)上升的 QPS 曲線,而不是告警郵件的紅叉。
如果你正在為系統(tǒng)性能發(fā)愁,不妨從 Nginx 配置開始,一步一個(gè)腳印,你會(huì)發(fā)現(xiàn):真正的架構(gòu)之美,藏在每一個(gè)微小的細(xì)節(jié)里。
附錄:Nginx 負(fù)載均衡常用指令速查表
| 指令 | 作用 | 示例 |
|---|---|---|
server | 定義后端服務(wù)器 | server 192.168.1.10:8080; |
weight | 設(shè)置權(quán)重 | weight=5; |
max_fails | 失敗次數(shù)閾值 | max_fails=3; |
fail_timeout | 失敗后暫停時(shí)間 | fail_timeout=30s; |
down | 標(biāo)記服務(wù)器下線 | server 192.168.1.10 down; |
backup | 備用服務(wù)器 | server 192.168.1.10 backup; |
ip_hash | 啟用 IP 哈希 | ip_hash; |
least_conn | 最少連接 | least_conn; |
keepalive | 保持連接數(shù) | keepalive 32; |
最后一句
“不要讓 Nginx 做它不該做的事,也不要讓 Java 做它不該做的事。”
—— 架構(gòu)師的哲學(xué)
你,準(zhǔn)備好做那個(gè)懂技術(shù)、懂業(yè)務(wù)、懂架構(gòu)的工程師了嗎?
以上就是Nginx反向代理實(shí)現(xiàn)負(fù)載均衡的三種策略(輪詢、權(quán)重、IP哈希)的詳細(xì)內(nèi)容,更多關(guān)于Nginx反向代理實(shí)現(xiàn)負(fù)載均衡的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Nginx通過用戶IP獲取所在國家及地理位置的實(shí)現(xiàn)方法
Nginx是一款高性能、輕量級(jí)的Web服務(wù)器和反向代理服務(wù)器,今天講解Nginx十分常用的功能之一,通過IP獲取用戶所在的國家,一般廣泛應(yīng)用在各類需要定位的網(wǎng)站上面,來定位用戶首次訪問的國家,通過IP解析庫GeoLite2-Country來實(shí)現(xiàn)功能,需要的朋友可以參考下2023-10-10
Nginx代理Redis哨兵主從配置的實(shí)現(xiàn)
本文主要介紹了Nginx代理Redis哨兵主從配置的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
當(dāng)Nginx所在服務(wù)器的磁盤空間滿時(shí)的影響以及如何避免這一問題
Nginx所在服務(wù)器的磁盤空間滿了,會(huì)導(dǎo)致日志無法寫入、緩存失效、反向代理請求異常等問題,嚴(yán)重時(shí)可能導(dǎo)致服務(wù)不可用,這篇文章主要介紹了當(dāng)Nginx所在服務(wù)器的磁盤空間滿時(shí)的影響以及如何避免這一問題,需要的朋友可以參考下2024-12-12
基于Nginx實(shí)現(xiàn)限制某IP短時(shí)間訪問次數(shù)
這篇文章主要介紹了基于Nginx實(shí)現(xiàn)限制某IP短時(shí)間訪問次數(shù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12

