📖 先搞懂概念

反向代理是什么?

你(浏览器)不直接访问后端服务器,而是先访问 Nginx(守门人),Nginx 再帮你去后端拿数据回来给你。

1
2
3
4
5

❌ 不用反向代理:你 → 某台后端 → 只能打这一台

✅ 用反向代理:  你 → Nginx → 自动帮你挑一台后端

好处

  • 隐藏后端服务器,只暴露 Nginx,更安全

  • 统一入口,方便做负载均衡

  • SSL 证书、缓存、gzip 压缩等可以统一在 Nginx 层处理,后端不用管

轮询(Round-Robin)是什么?

就像发扑克牌一样,每人一张轮流来:

1
2
3
4
5
6
7
8
9

请求1 → 服务器A

请求2 → 服务器B

请求3 → 服务器C

请求4 → 服务器A  ← 循环

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

用户访问 http://你的域名/

         │

         ▼

   ┌─ Nginx (80端口) ─┐

   │    upstream       │

   │   round-robin     │

   └──┬───┬───┬───┬───┘

      │   │   │   │

      ▼   ▼   ▼   ▼

    :3001 :3002 :3003 :3004   ← 后端服务(Node.js / Python / Go / Java 任意)

     🟢   🔵   🟠   🟣

    节点A  节点B  节点C  节点D

一台 Nginx 挡在前面,后面挂 N 台完全一样的后端服务。用户只看到 Nginx,不知道后面有几台机器。


🪜 操作步骤

步骤 1️⃣:准备后端服务

先用你最熟悉的语言启动几个后端。以 Node.js 为例,创建四个目录,各放一个最简单的服务器:

1
2
3

mkdir -p ~/demo/node1 ~/demo/node2 ~/demo/node3 ~/demo/node4

每个目录下放一个 server.js唯一的区别是端口和返回的标识文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// node1/server.js

const http = require('http');

const port = 3001;                         // ← 每个节点端口不同



http.createServer((req, res) => {

    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });

    res.end(`

        <h1>🟢 节点 A — 端口 ${port}</h1>

        <p>这台服务器处理了你的请求</p>

    `);

}).listen(port, () => console.log(`节点 A 启动在 ${port}`));

另外 node2/server.jsnode3/server.jsnode4/server.js 同理,只需要改端口号(3002/3003/3004)和显示文字(节点 B/C/D)即可。

Python 就更简单了,一行搞定(后面会讲)。


步骤 2️⃣:启动后端服务

开四个终端窗口,分别运行:

1
2
3
4
5
6
7
8
# 终端1
cd ~/demo/node1 && node server.js    # → 端口 3001
# 终端2
cd ~/demo/node2 && node server.js    # → 端口 3002
# 终端3
cd ~/demo/node3 && node server.js    # → 端口 3003
# 终端4
cd ~/demo/node4 && node server.js    # → 端口 3004

验证是否启动成功:

1
2
3
4
5
6
7
8
9

# Linux / macOS

netstat -an | grep "3001\|3002\|3003\|3004"

lsof -i :3001    # 或者用这个

# Windows
netstat -an | findstr "3001 3002 3003 3004"
1
2
3
4
5
6
7
8
9

TCP  0.0.0.0:3001  LISTENING

TCP  0.0.0.0:3002  LISTENING

TCP  0.0.0.0:3003  LISTENING

TCP  0.0.0.0:3004  LISTENING

浏览器访问 http://localhost:3001 能看到页面就说明单台后端正常。

💡 如果不想写 Node.js,用 Python 自带的简易服务器一行就行:

1
2
3
4
5
6
7

cd ~/demo/node1 && python3 -m http.server 3001

cd ~/demo/node2 && python3 -m http.server 3002

# ...以此类推

python -m http.server 不需要装任何东西,把当前目录当网站根目录,访问 / 返回 index.html


步骤 3️⃣:编写 Nginx 配置(核心!)

Linux 下配置文件通常在 /etc/nginx/nginx.conf/etc/nginx/conf.d/ 下。Windows 则在 Nginx 安装目录的 conf/ 下。

3.1 定义上游服务器组(upstream 块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 写在 http {} 块里面

upstream my_backend {

    server 127.0.0.1:3001 weight=1;

    server 127.0.0.1:3002 weight=1;

    server 127.0.0.1:3003 weight=1;

    server 127.0.0.1:3004 weight=1;

}

| 关键字 | 含义 |

|——–|——|

| upstream my_backend | 定义一个”上游服务器组”,名字随便起,后面 proxy_pass 要用 |

| server IP:端口 | 组里的每台服务器 |

| weight=1 | 权重。默认轮询下,权重越大分到的请求越多。全是 1 就是平均分 |

3.2 配置反向代理(server 块)

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

server {

    listen       80;                         # Nginx 对外监听 80 端口

    server_name  your-domain.com;            # 换成你的域名



    location / {

        proxy_pass http://my_backend;        # 把请求转发给 upstream 组

        # 把真实客户端信息透传给后端(否则后端只能看到 Nginx 的 IP)

        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 Cache-Control "no-store, no-cache, must-revalidate";

        add_header X-Upstream-Server $upstream_addr;   # 调试用,能看到被分到哪台

    }

}

执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

用户访问 http://your-domain.com/

  → Nginx 收到请求

  → 查 upstream 列表,内部计数器决定这次轮到谁

  → 第1次:127.0.0.1:3001  🟢 节点A

  → 第2次:127.0.0.1:3002  🔵 节点B

  → 第3次:127.0.0.1:3003  🟠 节点C

  → 第4次:127.0.0.1:3004  🟣 节点D

  → 第5次:127.0.0.1:3001  🟢 又回到节点A — 循环!

⚠️ 关键:演示轮询时必须加 Cache-Control: no-store,否则浏览器会缓存返回内容,你刷新永远看到同一台服务器。


步骤 4️⃣:测试配置语法

改完配置先验证,有语法错误 Nginx 会启动失败:

1
2
3

nginx -t

正确输出:

1
2
3
4
5

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

nginx: configuration file /etc/nginx/nginx.conf test is successful

Windows 下需要加 -p 指定安装目录:

1
2
3

nginx.exe -t -p "C:\nginx"


步骤 5️⃣:启动 / 重载 Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 首次启动

sudo systemctl start nginx      # Linux (systemd)

sudo nginx                       # Linux (直接启动)



# 修改配置后热重载(不停机!)

sudo nginx -s reload



# Windows

nginx.exe -s reload -p "C:\nginx"

验证 80 端口是否在监听:

1
2
3
4
5
6
7
8
9
10
11

# Linux

sudo lsof -i :80



# Windows

netstat -an | findstr ":80 "


步骤 6️⃣:验证轮询效果

命令行(curl)

1
2
3
4
5
6
7
8
9
10
11

# 连续请求 4 次

for i in 1 2 3 4; do

    echo "=== 第 ${i} 次 ==="

    curl -s http://localhost/ | grep "<title>"

done

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

=== 第 1 次 ===

<title>🟢 节点 A — 端口 3001</title>

=== 第 2 次 ===

<title>🔵 节点 B — 端口 3002</title>

=== 第 3 次 ===

<title>🟠 节点 C — 端口 3003</title>

=== 第 4 次 ===

<title>🟣 节点 D — 端口 3004</title>

浏览器

访问 http://localhost(或你的域名),每次按 F5 刷新,返回的服务器标识轮着变:

🟢 A → 🔵 B → 🟠 C → 🟣 D → 🟢 循环…


🔧 Nginx 常用命令速查

| 操作 | Linux | Windows |

|——|——-|———|

| 启动 | sudo nginx | nginx.exe -p "C:\nginx" |

| 测试配置 | nginx -t | nginx.exe -t -p "C:\nginx" |

| 热重载 | nginx -s reload | nginx.exe -s reload -p "C:\nginx" |

| 优雅停止 | nginx -s quit | nginx.exe -s quit -p "C:\nginx" |

| 立即停止 | nginx -s stop | nginx.exe -s stop -p "C:\nginx" |

| 查看日志 | tail -f /var/log/nginx/access.log | type logs\access.log |

💡 Nginx 最大的优势:修改配置只需 reload,不中断服务,零停机更新。


🧪 进阶:四种负载均衡策略

upstream 块里加一行就能切换策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

upstream my_backend {

    # ── 以下策略选一个,不写默认就是轮询 ──
    # ① 默认轮询(不加就是它)

    #   请求按顺序轮流:A → B → C → D → A → B ...

    # ② 加权轮询 — 改 weight 值

    server 127.0.0.1:3001 weight=3;   # 这台扛一半流量 (3/6)

    server 127.0.0.1:3002 weight=1;   # 1/6

    server 127.0.0.1:3003 weight=1;   # 1/6

    server 127.0.0.1:3004 weight=1;   # 1/6

    # ③ 最少连接 — 谁空闲发给谁
    least_conn;
    # ④ IP 哈希 — 同一用户始终打到同一台(会话保持)
    ip_hash;
}

| 策略 | 关键字 | 适合场景 |

|——|——–|———-|

| 轮询 | 默认 | 通用无状态服务,服务器配置一样 |

| 加权轮询 | 改 weight | 服务器配置不同(好的多扛点) |

| 最少连接 | least_conn; | 长连接、WebSocket、文件上传 |

| IP 哈希 | ip_hash; | 需要 Session,用户不能跳到别的机器 |


📊 一条请求的完整旅程

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

你按 F5 刷新 http://your-domain.com/

   │

   ├─ ① 浏览器 → DNS 解析域名 → 得到服务器 IP

   │

   ├─ ② 浏览器 → 服务器 80 端口: "GET / HTTP/1.1"

   │

   ├─ ③ Nginx 收到请求,查看 upstream 内部计数器

   │     当前计数器 = 3 → 这次轮到 127.0.0.1:3003

   │

   ├─ ④ Nginx → 127.0.0.1:3003: "有人找你要首页"

   │

   ├─ ⑤ 3003 的 Node 服务返回页面内容

   │

   └─ ⑥ Nginx 加上 Cache-Control 头,返回给浏览器

         你看到 🟠 节点C 的页面

   再按 F5 → 计数器变 4 → 打到 3004 → 🟣 节点D

   再按 F5 → 计数器重置 → 回到 3001 → 🟢 节点A

核心三要素

| 要素 | 作用 |

|——|——|

| upstream | 告诉 Nginx 后端有哪些机器 |

| proxy_pass | 把请求转发到 upstream 里的某台 |

| Cache-Control: no-store | 演示时必须加,否则浏览器缓存了看不清轮询 |


⚠️ 常见问题

| 问题 | 可能原因 | 解决 |

|——|———-|——|

| 刷新页面不变 | 浏览器缓存了 | Ctrl+F5 强制刷新;检查有没有配 Cache-Control: no-store |

| 80 端口被占用 | Apache / IIS / 另一个 Nginx 在跑 | sudo lsof -i :80 找到进程并停掉 |

| connect() failed | 某台后端挂了 | 确保对应端口的进程在运行 |

| nginx -t 报错 | 配置语法错误 | 检查分号 ; 是否漏了、花括号 {} 是否配对 |

| 某台后端总是收不到请求 | weight 设太小或防火墙问题 | 检查 weight 值和 iptables 规则 |


🏨 延伸:Nginx 还能做什么

本文讲的是反向代理 + 负载均衡,但 Nginx 的能力远不止这些:

虚拟主机 — 一个端口跑多个网站

1
2
3
4
5
6
7
8
9
10
11
12
13

# 同一台服务器,同一个 80 端口,靠域名区分

server { listen 80; server_name blog.example.com; root /www/blog; }

server { listen 80; server_name shop.example.com; root /www/shop; }

server { listen 80; server_name api.example.com;

    location / { proxy_pass http://127.0.0.1:8080; }

}

路径分流 — 同一域名下不同模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

server {

    server_name example.com;



    location /           { root /www/home; }          # 首页

    location /blog/      { proxy_pass http://127.0.0.1:8080/; }  # 博客

    location /api/       { proxy_pass http://127.0.0.1:4000/; }  # API

    location /static/    { root /www/assets; expires 30d; }      # 静态资源缓存

}

SSL/HTTPS 终结

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

server {

    listen 443 ssl http2;

    server_name example.com;



    ssl_certificate     /etc/ssl/certs/example.crt;

    ssl_certificate_key /etc/ssl/private/example.key;



    location / { proxy_pass http://my_backend; }

}



# HTTP 自动跳转到 HTTPS

server {

    listen 80;

    server_name example.com;

    return 301 https://$host$request_uri;

}

反向代理典型场景汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

                                    ┌─────────────┐

               ┌────────────────────┤ 静态文件直返  │ ← root /www

               │                    ├─────────────┤

  用户 ──→ Nginx(80/443) ──────────┤ 博客 /blog/   │ ← proxy_pass :8080

               │                    ├─────────────┤

               │                    │ 商城 /shop/   │ ← proxy_pass :3000

               │                    ├─────────────┤

               │                    │ API /api/    │ ← proxy_pass upstream_pool

               └────────────────────┤ 管理后台      │ ← allow 内网IP + proxy_pass

                                    └─────────────┘


🎯 总结

本文覆盖了 Nginx 最实用的三个功能:

  1. 反向代理 — 你不需要知道后端在哪,Nginx 帮你去拿

  2. 负载均衡 — 多台后端轮流扛,配置不行还能加权

  3. 热重载 — 改配置无需停机,生产环境友好

学完这些,一台服务器上跑多个项目、横向扩容都不在话下。下一步可以深入的方向:

  • proxy_cache — 反向代理缓存,减轻后端压力
  • proxy_buffer — 缓冲调优,应对大文件场景
  • health_check — 自动检测后端健康状态,挂了自动踢掉
  • Nginx + Docker Compose — 容器编排,一键拉起整张拓扑
  • limit_req / limit_conn — 限流防刷