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
相关产品推荐
相关产品推荐

