php实现频率限制
实现频率限制的基本方法
在PHP中实现频率限制(Rate Limiting)可以通过多种方式完成,核心目标是限制用户在特定时间内的请求次数。以下是几种常见的方法:
使用Redis存储计数
Redis是高频场景下的理想选择,支持原子操作和过期时间。通过INCR和EXPIRE命令组合实现计数:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'user:' . $userId . ':requests';
$limit = 100; // 限制次数
$window = 3600; // 时间窗口(秒)
$current = $redis->incr($key);
if ($current === 1) {
$redis->expire($key, $window);
}
if ($current > $limit) {
header('HTTP/1.1 429 Too Many Requests');
exit;
}
数据库记录方式 适用于无Redis的环境,通过MySQL记录请求时间戳:
// 清理过期记录
$db->query("DELETE FROM rate_limit WHERE timestamp < NOW() - INTERVAL 1 HOUR");
// 检查当前计数
$count = $db->query("SELECT COUNT(*) FROM rate_limit WHERE user_id = $userId")->fetchColumn();
if ($count >= 100) {
header('HTTP/1.1 429 Too Many Requests');
exit;
}
// 记录新请求
$db->query("INSERT INTO rate_limit (user_id, timestamp) VALUES ($userId, NOW())");
滑动窗口算法优化
固定窗口可能在窗口边界出现流量突增,滑动窗口通过时间范围精确控制:
$window = 60; // 60秒窗口
$now = microtime(true);
$oldest = $now - $window;
// 移除旧记录
$redis->zRemRangeByScore('requests', 0, $oldest);
// 添加当前请求并检查总数
$redis->zAdd('requests', $now, uniqid());
if ($redis->zCard('requests') > 30) { // 限制30次/分钟
header('Retry-After: ' . $window);
die('Rate limit exceeded');
}
Token Bucket算法实现
适用于需要平滑流量的场景,使用漏桶或令牌桶算法:
$bucketKey = 'user:' . $userId . ':tokens';
$capacity = 10; // 桶容量
$refillRate = 1; // 每秒补充1个令牌
$lastChecked = $redis->hGet($bucketKey, 'last_checked') ?: time();
$tokens = $redis->hGet($bucketKey, 'tokens') ?: $capacity;
$now = time();
$elapsed = $now - $lastChecked;
$tokens += $elapsed * $refillRate;
$tokens = min($tokens, $capacity);
if ($tokens < 1) {
header('HTTP/1.1 429 Too Many Requests');
exit;
}
$tokens--;
$redis->hMSet($bucketKey, [
'last_checked' => $now,
'tokens' => $tokens
]);
中间件封装方案
在框架(如Laravel)中可通过中间件统一处理:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;
class ThrottleRequests
{
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = $request->ip();
if (app(RateLimiter::class)->tooManyAttempts($key, $maxAttempts)) {
return new Response('Too Many Attempts', 429);
}
app(RateLimiter::class)->hit($key, $decayMinutes * 60);
return $next($request);
}
}
HTTP头部与缓存控制
返回标准HTTP状态码和头部信息:

header('X-RateLimit-Limit: 100');
header('X-RateLimit-Remaining: ' . ($limit - $current));
header('Retry-After: ' . $window); // 429响应时提示重试时间
分布式系统注意事项
在分布式环境中需确保时钟同步,或使用集中式存储(如Redis)。避免本地内存计数导致限制失效。






