代理与负载均衡
问题
什么是正向代理和反向代理?它们的区别和应用场景是什么?
答案
代理(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: 反向代理的作用?
答案:
- 负载均衡:分发请求到多台服务器
- SSL 终止:在代理层处理 HTTPS
- 缓存加速:缓存静态资源
- 安全防护:隐藏真实服务器
- 统一入口:路由到不同微服务
- 压缩优化:Gzip 压缩响应