You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP中应对高频HTTP请求:IP封禁及批量请求拦截函数实现

嘿,这两个问题都是高并发场景下非常典型的防刷需求,我来结合实战经验给你详细讲讲解决方案:

1. 当每秒收到大量HTTP请求时,PHP中实现IP封禁的方案

处理这类问题,我建议优先从服务器层面入手,因为PHP是请求驱动的脚本,在请求到达PHP之前拦截效率更高。如果必须在PHP层面实现,也有可靠的方案:

服务器层面(推荐)

比如用Nginx的limit_req_zone模块,可以直接在配置里限制请求频率,超过阈值的请求直接返回503,根本不用走到PHP:

http {
    # 定义一个存储请求状态的区域,key是客户端IP,容量10M,每个请求的状态保留60秒
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

    server {
        location / {
            # 应用上面定义的规则,burst设置为5(允许5个突发请求),nodelay不延迟处理
            limit_req zone=one burst=5 nodelay;
            # 你的PHP配置
            fastcgi_pass unix:/run/php/php8.1-fpm.sock;
            ...
        }
    }
}

PHP层面实现(适合需要自定义封禁逻辑的场景)

如果需要更灵活的封禁规则(比如结合用户行为、请求内容),可以用Redis来存储IP的请求统计和封禁状态——Redis的高性能非常适合高并发场景:

步骤1:初始化Redis连接

// 假设用phpredis扩展
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 如果有密码的话
// $redis->auth('your_redis_password');

步骤2:实现IP封禁逻辑

function blockIP(string $ip, int $blockDuration = 3600): void {
    global $redis;
    // 存储封禁IP,设置过期时间(这里是1小时)
    $redis->setex("blocked_ip:$ip", $blockDuration, 1);
}

function isIPBlocked(string $ip): bool {
    global $redis;
    return $redis->exists("blocked_ip:$ip") === 1;
}

// 在请求入口处检查
$clientIP = $_SERVER['REMOTE_ADDR'];
// 如果有反向代理,要取真实IP,比如:
// $clientIP = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];

if (isIPBlocked($clientIP)) {
    http_response_code(403);
    echo "你的IP已被临时封禁,请稍后再试";
    exit;
}
2. 编写检测批量请求并临时封禁的函数

这里推荐用滑动窗口算法来统计请求,比固定窗口更精准,避免“窗口切换瞬间大量请求绕过限制”的问题。下面是结合Redis实现的完整函数:

function checkAndBlockBatchRequests(string $ip, int $maxRequests = 10, int $windowSeconds = 60, int $blockSeconds = 3600): bool {
    global $redis;

    // 检查是否已经被封禁
    if ($redis->exists("blocked_ip:$ip")) {
        return true;
    }

    $key = "request_count:$ip";
    $currentTime = time();
    $windowStart = $currentTime - $windowSeconds;

    // 移除窗口外的旧请求记录
    $redis->zRemRangeByScore($key, 0, $windowStart);
    // 获取当前窗口内的请求数量
    $requestCount = $redis->zCard($key);

    if ($requestCount >= $maxRequests) {
        // 超过阈值,封禁IP
        $redis->setex("blocked_ip:$ip", $blockSeconds, 1);
        return true;
    }

    // 记录当前请求的时间戳
    $redis->zAdd($key, $currentTime, uniqid()); // 用uniqid作为成员,避免重复
    // 设置key的过期时间,比窗口时间长一点,避免内存浪费
    $redis->expire($key, $windowSeconds + 10);

    return false;
}

// 使用示例
$clientIP = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
if (checkAndBlockBatchRequests($clientIP, 10, 60, 3600)) {
    http_response_code(429);
    echo "请求过于频繁,请1小时后再试";
    exit;
}

// 继续处理正常请求
...

关键细节说明

  • 真实IP获取:如果你的服务在反向代理(比如Nginx、Cloudflare)后面,一定要用HTTP_X_FORWARDED_FOR或者对应的请求头来获取真实IP,否则会把代理IP当成客户端IP。
  • 滑动窗口优势:相比固定窗口(比如统计每分钟的请求数),滑动窗口能精准控制任意60秒内的请求次数,不会出现“第59秒发10次,第61秒又发10次”的绕过情况。
  • Redis性能:Redis的ZSet操作都是O(logN)的时间复杂度,完全能扛住每秒上万次的请求。

内容的提问来源于stack exchange,提问作者Ali Haroon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:09:13