导航菜单

负载均衡

场景

限流上线后,业务继续健康发展。

五个月后:

  • 注册用户: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
  • 可用性提升(单台故障不影响整体)

⚠️ 新的问题:

  • 在多台服务器环境下,限流失效了
  • 需要分布式限流

🎯 下一步: 多台服务器后,每台服务器独立限流,总限流上限被放大了。

搜索