Skip to content

请求限流

请求限流可通过中间件实现 IP 级别的限流,防止恶意请求和 DoS 攻击。项目中限流可通过 Nginx 反向代理或自定义中间件实现。

限流策略

项目支持两种限流实现方式,可根据部署环境选择:

方式适用场景性能精度
Nginx 限流生产环境(推荐)高(不占用应用进程)
Django 中间件开发/测试环境中(占用应用进程)

方式 1:Nginx 限流(推荐)

Nginx 限流在反向代理层实现,不占用 Django 进程资源,适合生产环境:

nginx
# nginx.conf
http {
    # 定义限流区域:按 IP 维度,每秒 10 个请求
    # 10m 共享内存区域可存储约 16 万个 IP 地址
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    server {
        location /api/ {
            # 应用限流:突发允许 20 个请求排队,不延迟处理
            limit_req zone=api_limit burst=20 nodelay;

            # 限流时返回 429 状态码
            limit_req_status 429;

            # 限流日志级别
            limit_req_log_level warn;

            proxy_pass http://127.0.0.1:8000;
        }
    }
}

Nginx 限流参数说明

参数说明示例
rate每秒允许的请求数10r/s(每秒 10 个)
burst突发请求数(排队等待)burst=20
nodelay突发请求不延迟处理有此参数则立即处理
limit_req_status限流时返回的状态码429
limit_req_log_level限流日志级别warn

不同接口差异化限流

nginx
http {
    # API 接口限流:每秒 10 个请求
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    # 登录接口限流:每分钟 5 个请求(防暴力破解)
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;

    # 上传接口限流:每秒 2 个请求
    limit_req_zone $binary_remote_addr zone=upload_limit:10m rate=2r/s;

    server {
        location /api/login {
            limit_req zone=login_limit burst=3 nodelay;
            limit_req_status 429;
            proxy_pass http://127.0.0.1:8000;
        }

        location /api/upload {
            limit_req zone=upload_limit burst=5 nodelay;
            limit_req_status 429;
            proxy_pass http://127.0.0.1:8000;
        }

        location /api/ {
            limit_req zone=api_limit burst=20 nodelay;
            limit_req_status 429;
            proxy_pass http://127.0.0.1:8000;
        }
    }
}

方式 2:Django 中间件限流

Django 中间件限流在应用层实现,支持更灵活的限流规则:

python
# middleware/rate_limit_middleware.py
import time
from django.core.cache import cache
from django.http import JsonResponse

class RateLimitMiddleware:
    """基于缓存的 IP 限流中间件"""

    def __init__(self, get_response):
        self.get_response = get_response
        self.rate_limit = 100  # 每分钟最大请求数
        self.window = 60       # 窗口时间(秒)

    def __call__(self, request):
        # 获取客户端 IP
        ip = self._get_client_ip(request)
        cache_key = f'rate_limit:{ip}'

        # 获取当前计数
        current = cache.get(cache_key, 0)

        if current >= self.rate_limit:
            return JsonResponse({
                'code': 1,
                'msg': '请求过于频繁,请稍后再试',
                'data': None,
                'ok': False
            }, status=429)

        # 递增计数
        cache.set(cache_key, current + 1, self.window)

        response = self.get_response(request)
        return response

    def _get_client_ip(self, request):
        """获取客户端 IP,支持代理头"""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            return x_forwarded_for.split(',')[0].strip()
        return request.META.get('REMOTE_ADDR')

注册中间件

python
# application/settings.py
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'middleware.rate_limit_middleware.RateLimitMiddleware',  # 限流中间件
    'django.middleware.common.CommonMiddleware',
    ...
]

按接口差异化限流

python
class RateLimitMiddleware:
    """支持按接口差异化限流的中间件"""

    # 接口限流配置
    RATE_LIMITS = {
        '/api/login': {'limit': 5, 'window': 60},      # 登录:每分钟 5 次
        '/api/captcha': {'limit': 10, 'window': 60},    # 验证码:每分钟 10 次
        '/api/upload': {'limit': 5, 'window': 60},      # 上传:每分钟 5 次
        'default': {'limit': 100, 'window': 60},        # 默认:每分钟 100 次
    }

    def __call__(self, request):
        ip = self._get_client_ip(request)
        path = request.path

        # 获取当前接口的限流配置
        config = self.RATE_LIMITS.get(path, self.RATE_LIMITS['default'])
        cache_key = f'rate_limit:{ip}:{path}'

        current = cache.get(cache_key, 0)
        if current >= config['limit']:
            return JsonResponse({
                'code': 1,
                'msg': '请求过于频繁,请稍后再试',
                'data': None,
                'ok': False
            }, status=429)

        cache.set(cache_key, current + 1, config['window'])
        return self.get_response(request)

配置项

配置项默认值说明
rate_limit100每分钟最大请求数
window60统计窗口时长(秒)

生产环境建议

生产环境建议使用 Nginx 限流,性能更好且不占用 Django 进程资源。Django 中间件限流适合开发环境和需要精细控制的场景。

限流响应格式

当请求被限流时,返回 429 状态码:

json
{
    "code": 1,
    "msg": "请求过于频繁,请稍后再试",
    "data": null,
    "ok": false
}

前端可通过 HTTP 状态码 429 识别限流,并提示用户稍后重试。

降级策略

1. Redis 不可用:直接放行,不阻塞请求
2. 限流计数异常:降级放行,避免全站不可用
3. 限流阈值配置错误:使用默认值兜底

fail-open 策略

限流采用 fail-open 策略:缓存不可用时放行所有请求,优先保证服务可用性。这是安全与可用性的权衡——宁可放过一些请求,也不能因为限流故障导致全站不可用。

验证码防刷

验证码接口有独立的限流策略,按 IP 频率控制:

python
# 验证码防刷:单 IP 每分钟最多获取 5 次验证码
def check_captcha_rate_limit(ip):
    cache_key = f'captcha_rate:{ip}'
    count = cache.get(cache_key, 0)
    if count >= 5:
        return False  # 超过限制
    cache.set(cache_key, count +  60)
    return True

前端配合

前端可通过 Axios 拦截器处理 429 响应:

typescript
// 响应拦截器
responseInterceptorsCatch: (error: any) => {
    if (error.response?.status === 429) {
        ElMessage.warning('请求过于频繁,请稍后再试');
        return Promise.reject(error);
    }
    // ... 其他错误处理
}

总结

请求限流模块具备以下特点:

1. 多种实现:Nginx 限流(推荐)或 Django 中间件限流
2. IP 维度:按客户端 IP 限流,支持代理头
3. 滑动窗口:基于缓存的计数器实现
4. 降级策略:缓存故障时 fail-open 放行
5. 灵活配置:可调整限流阈值和窗口时间
6. 差异化限流:支持按接口独立配置限流策略
7. 验证码防刷:验证码接口有独立的频率控制

小蚂蚁云团队 · 提供技术支持