负载均衡
场景
限流上线后,业务继续健康发展。
五个月后:
- 注册用户:2000 个
- 日活用户:500 个
- 日调用量:40 万次
虽然限流保护了系统,但单机服务器开始吃力。
问题发现
某天下午,我收到了告警:
【告警】服务器负载过高
- CPU 使用率:95%
- 内存使用率:85%
- 响应时间:500ms+我查了一下监控,发现:
当前负载情况:
- QPS:15
- 并发连接数:200
- 数据库连接数:50(接近上限)问题:单机服务器已经到极限了!
容量分析
我分析了当前的瓶颈:
CPU
# 计算 CPU 使用率
import psutil
cpu_percent = psutil.cpu_percent(interval=1)
print(f"CPU 使用率:{cpu_percent}%")
# 输出:CPU 使用率:95%瓶颈:
- 每个请求需要调用外部 API
- 数据处理、缓存操作
- Python 的单线程性能有限
内存
# 查看内存使用
memory = psutil.virtual_memory()
print(f"内存使用率:{memory.percent}%")
print(f"已用内存:{memory.used / 1024 / 1024 / 1024:.2f} GB")
# 输出:
# 内存使用率:85%
# 已用内存:6.8 GB / 8 GB瓶颈:
- 数据库连接池占用内存
- 缓存数据占用内存
- Python 进程占用内存
数据库连接
# 查看数据库连接数
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('SHOW PROCESSLIST')
connections = cursor.fetchall()
print(f"当前数据库连接数:{len(connections)}")
# 输出:当前数据库连接数:50瓶颈:
- MySQL 默认最大连接数:151
- 当前使用 50 个,已经占 1/3
- 继续增长会达到上限
解决方案
我决定引入负载均衡。
架构设计
原来的架构:
┌─────────────┐
│ 用户请求 │
└──────┬──────┘
│
▼
┌─────────────────┐
│ 单机服务器 │
│ (应用 + 数据库) │
└─────────────────┘
新架构:
┌─────────────┐
│ 用户请求 │
└──────┬──────┘
│
▼
┌─────────────────┐
│ 负载均衡器 │
│ (Nginx) │
└──────┬──────────┘
│
├──────────┬──────────┬──────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Server 1 │ │ Server 2 │ │ Server 3 │ │ Server 4 │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
└────────────┴────────────┴────────────┘
│
▼
┌─────────────────┐
│ 共享数据库 │
│ (MySQL) │
└─────────────────┘负载均衡器选择
方案对比
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Nginx | 成熟稳定、功能丰富 | 配置相对复杂 | 生产环境 |
| HAProxy | 性能极高、专门做负载均衡 | 配置复杂 | 高流量场景 |
| 云负载均衡 | 免运维、自动扩展 | 成本较高 | 云环境 |
| DNS 轮询 | 简单 | 无健康检查 | 简单场景 |
选择 Nginx
我选择 Nginx 作为负载均衡器:
- 免费开源
- 社区活跃
- 功能丰富(健康检查、SSL、静态文件)
- 性能优秀
Nginx 配置
安装 Nginx
# Ubuntu/Debian
sudo apt update
sudo apt install nginx
# CentOS/RHEL
sudo yum install nginx
# macOS
brew install nginx配置负载均衡
# /etc/nginx/nginx.conf
upstream api_backend {
# 负载均衡算法:轮询(默认)
# 其他算法:least_conn(最少连接)、ip_hash(IP 哈希)
server 10.0.1.10:8080 weight=1; # Server 1
server 10.0.1.11:8080 weight=1; # Server 2
server 10.0.1.12:8080 weight=1; # Server 3
server 10.0.1.13:8080 weight=1; # Server 4
# 备用服务器(只在所有主服务器都不可用时使用)
server 10.0.1.14:8080 backup;
# 健康检查(需要 nginx-plus 或第三方模块)
check interval=3000 rise=2 fall=3 timeout=1000;
}
server {
listen 80;
server_name api.kuaiyizhi.cn;
# 访问日志
access_log /var/log/nginx/api_access.log;
error_log /var/log/nginx/api_error.log;
location / {
# 代理到后端服务器
proxy_pass http://api_backend;
# 设置请求头
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 保持连接
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# 健康检查端点
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}启动 Nginx
# 测试配置
sudo nginx -t
# 重启 Nginx
sudo systemctl restart nginx
# 查看状态
sudo systemctl status nginx应用服务器配置
Server 1-4 的配置
# app.py
from flask import Flask, jsonify
import redis
import pymysql
app = Flask(__name__)
# 每台服务器连接相同的 Redis 和 MySQL
redis_client = redis.Redis(
host='10.0.2.10', # Redis 服务器 IP
port=6379,
decode_responses=True
)
def get_db_connection():
return pymysql.connect(
host='10.0.2.20', # MySQL 服务器 IP
user='api_user',
password='your_password',
database='api_platform',
cursorclass=pymysql.cursors.DictCursor
)
@app.route('/health')
def health_check():
"""健康检查端点"""
try:
# 检查数据库连接
conn = get_db_connection()
conn.close()
# 检查 Redis 连接
redis_client.ping()
return jsonify({
'status': 'healthy',
'server': socket.gethostname()
}), 200
except Exception as e:
return jsonify({
'status': 'unhealthy',
'error': str(e)
}), 503
@app.route('/api/weather')
@require_api_key_with_rate_limit
def get_weather():
# ... 业务逻辑
pass
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)负载均衡策略
策略 1:轮询(Round Robin)
upstream api_backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}请求分布:
请求 1 → Server 1
请求 2 → Server 2
请求 3 → Server 3
请求 4 → Server 4
请求 5 → Server 1
...策略 2:最少连接(Least Connections)
upstream api_backend {
least_conn;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}优势:
- 将请求分配给当前连接数最少的服务器
- 适合处理时间差异大的场景
策略 3:IP 哈希(IP Hash)
upstream api_backend {
ip_hash;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}优势:
- 同一个 IP 的请求总是分配到同一台服务器
- 适合有状态的服务
- 问题:可能导致负载不均
策略 4:加权轮询(Weighted Round Robin)
upstream api_backend {
server 10.0.1.10:8080 weight=3; # 性能好的服务器
server 10.0.1.11:8080 weight=2;
server 10.0.1.12:8080 weight=2;
server 10.0.1.13:8080 weight=1; # 性能差的服务器
}请求分布:
请求 1 → Server 1
请求 2 → Server 1
请求 3 → Server 1
请求 4 → Server 2
请求 5 → Server 2
请求 6 → Server 3
请求 7 → Server 3
请求 8 → Server 4
...健康检查
Nginx 被动健康检查
upstream api_backend {
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.13:8080 max_fails=3 fail_timeout=30s;
}参数说明:
max_fails=3:30 秒内失败 3 次,标记为不可用fail_timeout=30s:30 秒后重新尝试连接
主动健康检查(需要第三方模块)
upstream api_backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
# 主动健康检查
check interval=3000 rise=2 fall=3 timeout=1000;
# interval=3000:每 3 秒检查一次
# rise=2:连续 2 次成功标记为健康
# fall=3:连续 3 次失败标记为不健康
# timeout=1000:超时时间 1 秒
}会话保持
如果需要会话保持(同一用户的请求总是分配到同一台服务器):
upstream api_backend {
ip_hash; # 使用 IP 哈希
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}或者使用 cookie:
upstream api_backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}
server {
location / {
proxy_pass http://api_backend;
# 会话保持
proxy_set_header Cookie $http_cookie;
}
}效果验证
上线后,我观察了一周:
性能提升
单机时代:
- QPS:15
- 响应时间:500ms
- CPU 使用率:95%
负载均衡后(4 台服务器):
- 总 QPS:60
- 每台 QPS:15
- 响应时间:100ms
- 每台 CPU 使用率:40%可用性提升
单机时代:
- 服务器故障 → 全站不可用
- 恢复时间:重启服务器(5-10 分钟)
负载均衡后:
- 1 台服务器故障 → 其他服务器继续服务
- 影响范围:25% 的用户(如果有 4 台服务器)
- 恢复时间:自动检测并恢复流量(30 秒)监控和告警
def check_load_balancer_health():
"""检查负载均衡器健康状态"""
# 检查每台服务器的健康状态
servers = ['10.0.1.10', '10.0.1.11', '10.0.1.12', '10.0.1.13']
for server in servers:
try:
response = requests.get(
f'http://{server}:8080/health',
timeout=3
)
if response.status_code != 200:
send_alert(f'Server {server} is unhealthy!')
except Exception as e:
send_alert(f'Server {server} is unreachable!')本节小结
✅ 完成的工作:
- 引入 Nginx 负载均衡器
- 部署 4 台应用服务器
- 配置健康检查
- 性能提升 4 倍
✅ 效果:
- QPS 从 15 提升到 60
- 响应时间从 500ms 降到 100ms
- 可用性提升(单台故障不影响整体)
⚠️ 新的问题:
- 在多台服务器环境下,限流失效了
- 需要分布式限流
🎯 下一步: 多台服务器后,每台服务器独立限流,总限流上限被放大了。
