PHP项目如何配置站点访问限流?

wen PHP项目 19

本文目录导读:

PHP项目如何配置站点访问限流?

  1. 核心原理
  2. 代码层面(使用 PHP 实现,最灵活,无需服务器权限)
  3. 服务器层面(Nginx / Apache,高性能,无代码侵入)
  4. 框架/中间件层面
  5. 针对用户/API Key 的精细限流
  6. 总结与建议

在 PHP 项目中配置站点访问限流,通常有 代码层面服务器层面软件/中间件层面 三种思路,下面分别介绍几种常用且有效的方案,你可以根据项目架构和运维权限选择。

核心原理

限流的本质是计数器 + 时间窗口(或令牌桶、漏桶算法),我们需要记录每个用户/IP 在特定时间内的请求次数,超过阈值则拒绝。


代码层面(使用 PHP 实现,最灵活,无需服务器权限)

这种方式适合共享主机、无权限修改 Nginx/Apache 配置的环境。

基于文件/IP 的简单计数器(适用于小规模站点)

思路:记录每个 IP 的访问次数到文件或 Redis,超过阈值则返回 429 状态码。

<?php
// rate_limit.php
class RateLimiter {
    private $storage; // 存储驱动,Redis 或文件
    private $maxRequests = 100;      // 最大请求数
    private $timeWindow = 60;        // 时间窗口(秒)
    public function __construct($storage) {
        $this->storage = $storage;
    }
    public function isAllowed($identifier, $maxRequests = null, $timeWindow = null) {
        $maxRequests = $maxRequests ?? $this->maxRequests;
        $timeWindow = $timeWindow ?? $this->timeWindow;
        $key = "rate_limit:{$identifier}";
        $current = $this->storage->get($key);
        if ($current === false) {
            // 第一次访问,设置初始值
            $this->storage->setex($key, $timeWindow, 1);
            return true;
        }
        // 检查当前时间窗口内的请求次数
        $count = (int)$current;
        if ($count >= $maxRequests) {
            return false; // 限流
        }
        // 增加计数
        $this->storage->incr($key);
        return true;
    }
}
// 使用示例(假设你用了 Redis)
// $redis = new Redis();
// $redis->connect('127.0.0.1', 6379);
// $limiter = new RateLimiter($redis);
// $ip = $_SERVER['REMOTE_ADDR'];
// if (!$limiter->isAllowed($ip, 100, 60)) {
//     http_response_code(429);
//     die('Too Many Requests');
// }
?>

注意:如果不使用 Redis,配合文件锁(flock)可以写文件实现,但高并发下性能较差。

使用 Redis 的“滑动窗口”算法(工业级推荐)

Redis 的有序集合(ZSET)非常适合做滑动窗口限流。

<?php
/** 滑动窗口限流 */
function isAllowed($userId, $action = 'api', $maxCount = 100, $window = 60) {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key = "rate_limit:{$action}:{$userId}";
    $now = microtime(true);
    $start = $now - $window;
    // 1. 移除窗口之外的数据
    $redis->zRemRangeByScore($key, 0, $start);
    // 2. 计算当前窗口内的请求数
    $currentCount = $redis->zCard($key);
    if ($currentCount >= $maxCount) {
        return false;
    }
    // 3. 记录本次请求
    $redis->zAdd($key, $now, $now . '_' . uniqid());
    // 设置过期时间,避免内存泄漏
    $redis->expire($key, $window + 1);
    return true;
}
// 使用
if (!isAllowed($_SERVER['REMOTE_ADDR'])) {
    http_response_code(429);
    exit('请求过于频繁');
}

优点:精确控制时间窗口,防止边界瞬高流量(不同于固定窗口的“突刺”问题)。


服务器层面(Nginx / Apache,高性能,无代码侵入)

如果可以直接操作 Web 服务器,这是最高效的方式,几乎不消耗 PHP 进程资源。

Nginx 使用 ngx_http_limit_req_module(推荐)

步骤:

  1. nginx.confhttp 块定义限流区域。
  2. serverlocation 块启用限流。
# /etc/nginx/nginx.conf
http {
    # 定义限流区域:基于 binary_remote_addr(客户端IP)
    # 分配 10MB 内存空间,速率限制为 10 请求/秒(burst 允许突发 20 个请求)
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
    # 可选:限制连接数
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    server {
        listen 80;
        server_name example.com;
        location /api/ {
            # 启用限流,burst=20 允许瞬间超过速率限制的请求排队等待
            # nodelay 表示不延迟,排队立即处理但超出 burst 则返回 503
            limit_req zone=one burst=20 nodelay;
            # 超出限制时返回的状态码和提示信息
            limit_req_status 429;
        }
        location / {
            # 限制并发连接数(比如防止 CC 攻击)
            limit_conn addr 100;
            limit_conn_status 429;
        }
    }
}

关键参数说明:

  • rate=10r/s:每秒允许 10 个请求。
  • burst=20:瞬间突发最高允许 30(10+20)个请求。
  • nodelay:排队的请求不延迟处理(尽量不要加,或谨慎使用,否则可能超限)。

Apache 使用 mod_ratelimit(较旧)

<IfModule mod_ratelimit.c>
    SetOutputFilter RATE_LIMIT
    SetEnv rate-limit 400   # 400 KB/s 带宽限速(非请求数限流)
</IfModule>

注意:Apache 的原生工具对请求数限流支持较弱,通常需要结合 mod_qos 或其他第三方模块。


框架/中间件层面

如果你使用了现代 PHP 框架(如 Laravel, Symfony),可以直接使用框架提供的限流功能,非常方便。

Laravel 的 RateLimiter

Laravel 内置了强大的限流器,支持 Redis/数据库驱动。

定义限流规则(在 App\Providers\RouteServiceProviderApp\Http\Kernel):

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
// 按 IP 限流,每分钟 100 次
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(100)->by($request->ip());
});

在路由中应用:

Route::middleware('throttle:api')->group(function () {
    Route::get('/users', [UserController::class, 'index']);
});

Symfony 的 Rate Limiter(需要安装组件)

composer require symfony/rate-limiter

config/packages/framework.yaml 配置:

framework:
    rate_limiter:
        anonymous_api:
            policy: 'fixed_window' # 或 'sliding_window', 'token_bucket'
            limit: 100
            interval: '1 minute'

然后在控制器中使用 Symfony\Component\RateLimiter\RateLimiterFactory


针对用户/API Key 的精细限流

对于提供 API 的服务,通常需要针对不同 API Key 做不同额度的限流。

方案: 结合 Redis 哈希表,以 api_key 作为标识,存储该 Key 在时间窗口内的使用情况。

public function checkApiKeyLimit($apiKey) {
    $redis = new Redis();
    // 从数据库读取该 key 的限额配置(1000 req/min)
    $limitConfig = $this->getApiKeyConfig($apiKey);
    $key = "apikey:{$apiKey}";
    $current = $redis->get($key);
    if ($current === false) {
        $redis->setex($key, 60, 1);
        return true;
    }
    if ((int)$current >= $limitConfig['max_requests']) {
        return false;
    }
    $redis->incr($key);
    return true;
}

总结与建议

方案 适用场景 优点 缺点
Nginx 限流 独享服务器/VPS 极高性能,不耗费 PHP 资源 只能基于 IP,不易动态调整限额
Redis + PHP 需要按用户/API Key 精细限流 灵活,支持复杂逻辑和配额管理 依赖 Redis,有一定网络开销
Laravel/Symfony 使用该框架的情况下 开箱即用,集成度高 依赖框架,更换框架需重写
文件/IP 计数 无Redis,无服务器权限,小流量 简单,无外部依赖 性能差(并发极高时文件锁会成为瓶颈),不适合高并发

推荐组合:

  • 基础防护:Nginx limit_req(防止 DDOS 和恶意爬虫)。
  • 业务层限流:Redis 滑动窗口(例如控制用户每分钟最多发布 1 次评论)。
  • 最终保障:PHP 代码层兜底(防止缓存穿透导致的限流失效)。

如果你能提供更多信息(比如是否使用框架、是否拥有服务器 root 权限、项目并发量级别),我可以给出更具体的配置示例。

抱歉,评论功能暂时关闭!