跳到主要内容

代理与负载均衡

问题

什么是正向代理和反向代理?它们的区别和应用场景是什么?

答案

代理(Proxy)是客户端和服务端之间的中间层,代表一方与另一方通信。


正向代理 vs 反向代理

特性正向代理反向代理
代理对象客户端服务端
服务端感知只看到代理 IP只看到代理 IP
客户端感知知道代理存在不知道代理存在
典型场景VPN、翻墙、内网访问外网CDN、负载均衡、反向代理

正向代理

工作原理

应用场景

// 1. 访问受限网站(VPN)
// 客户端配置代理
const proxyConfig = {
host: 'proxy.example.com',
port: 8080
};

// 2. 内网访问外网
// 公司内网通过代理服务器访问互联网

// 3. 缓存加速
// 代理缓存常用资源

// 4. 隐藏真实 IP
// 访问目标服务器时使用代理 IP

前端开发中的正向代理

// webpack-dev-server 代理配置
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
};

// Vite 代理配置
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};

反向代理

工作原理

应用场景

// 1. 负载均衡
// 将请求分发到多个后端服务器

// 2. SSL 终止
// 在代理层处理 HTTPS,后端使用 HTTP

// 3. 缓存静态资源
// 缓存 HTML、CSS、JS、图片

// 4. 安全防护
// 隐藏真实服务器,防止攻击

// 5. 统一入口
// 不同路径路由到不同服务

Nginx 配置示例

基础反向代理

# /etc/nginx/nginx.conf
server {
listen 80;
server_name www.example.com;

# 反向代理到后端服务
location /api {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# 静态资源
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}

负载均衡

# 定义上游服务器组
upstream backend {
# 轮询(默认)
server 192.168.1.1:3000;
server 192.168.1.2:3000;
server 192.168.1.3:3000;
}

# 权重负载均衡
upstream backend_weighted {
server 192.168.1.1:3000 weight=5;
server 192.168.1.2:3000 weight=3;
server 192.168.1.3:3000 weight=2;
}

# IP Hash(会话保持)
upstream backend_iphash {
ip_hash;
server 192.168.1.1:3000;
server 192.168.1.2:3000;
}

# 最少连接
upstream backend_least {
least_conn;
server 192.168.1.1:3000;
server 192.168.1.2:3000;
}

server {
listen 80;

location /api {
proxy_pass http://backend;
}
}

HTTPS 配置

server {
listen 443 ssl http2;
server_name www.example.com;

# SSL 证书
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;

# SSL 优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;

location / {
proxy_pass http://backend;
}
}

# HTTP 重定向到 HTTPS
server {
listen 80;
server_name www.example.com;
return 301 https://$server_name$request_uri;
}

负载均衡算法

算法说明适用场景
轮询依次分发服务器性能相同
加权轮询按权重分发服务器性能不同
IP Hash同一 IP 访问同一服务器需要会话保持
最少连接优先分配给连接少的请求耗时差异大
随机随机分配分布式场景
一致性哈希按 key 哈希分配缓存场景

前端相关配置

跨域代理

server {
listen 80;

# 前端静态资源
location / {
root /var/www/frontend;
try_files $uri $uri/ /index.html;
}

# API 代理
location /api {
proxy_pass http://api.backend.com;

# 允许跨域
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
add_header Access-Control-Allow-Headers 'Content-Type, Authorization';

if ($request_method = 'OPTIONS') {
return 204;
}
}
}

SPA 路由支持

server {
listen 80;
root /var/www/dist;

location / {
# 关键:所有路由都返回 index.html
try_files $uri $uri/ /index.html;
}

# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

Gzip 压缩

http {
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;

# 预压缩文件
gzip_static on;
}

Node.js 代理实现

import http from 'http';
import httpProxy from 'http-proxy';

// 创建代理服务器
const proxy = httpProxy.createProxyServer({});

const server = http.createServer((req, res) => {
// 路由分发
if (req.url?.startsWith('/api')) {
proxy.web(req, res, {
target: 'http://localhost:3000'
});
} else if (req.url?.startsWith('/static')) {
proxy.web(req, res, {
target: 'http://localhost:4000'
});
}
});

// 负载均衡示例
const servers = [
{ target: 'http://localhost:3001' },
{ target: 'http://localhost:3002' },
{ target: 'http://localhost:3003' }
];

let current = 0;

const loadBalance = http.createServer((req, res) => {
// 轮询
const target = servers[current];
current = (current + 1) % servers.length;

proxy.web(req, res, target);
});

常见面试问题

Q1: 正向代理和反向代理的区别?

答案

对比点正向代理反向代理
代理对象代理客户端代理服务端
客户端配置需要配置透明无感
服务端感知看到代理 IP可获取真实 IP
典型例子VPN、翻墙Nginx、CDN
用途突破限制、隐藏 IP负载均衡、安全

Q2: Nginx 负载均衡策略有哪些?

答案

# 1. 轮询(默认)
upstream backend {
server 192.168.1.1;
server 192.168.1.2;
}

# 2. 加权轮询
upstream backend {
server 192.168.1.1 weight=3;
server 192.168.1.2 weight=1;
}

# 3. IP Hash
upstream backend {
ip_hash;
server 192.168.1.1;
server 192.168.1.2;
}

# 4. 最少连接
upstream backend {
least_conn;
server 192.168.1.1;
server 192.168.1.2;
}

Q3: 前端开发中如何解决跨域?

答案

// 开发环境:devServer 代理
// vite.config.ts
export default {
server: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true
}
}
}
};

// 生产环境:Nginx 反向代理
// 将前端和 API 部署在同域下
// 或配置 CORS 响应头

Q4: 什么是会话保持?为什么需要?

答案

会话保持确保同一用户的请求被分发到同一服务器。

需要的场景

  • Session 存储在服务器内存
  • 有状态的 WebSocket 连接
  • 文件上传分片

解决方案

# IP Hash
upstream backend {
ip_hash;
server 192.168.1.1;
server 192.168.1.2;
}

# Cookie 方式
upstream backend {
hash $cookie_jsessionid consistent;
server 192.168.1.1;
server 192.168.1.2;
}

Q5: 反向代理的作用?

答案

  1. 负载均衡:分发请求到多台服务器
  2. SSL 终止:在代理层处理 HTTPS
  3. 缓存加速:缓存静态资源
  4. 安全防护:隐藏真实服务器
  5. 统一入口:路由到不同微服务
  6. 压缩优化:Gzip 压缩响应

相关链接