本文目录导读:

在PHP项目中实现接口限速(Rate Limiting)有多种方法,这里介绍几种主流的实现方案:
基于Redis的滑动窗口算法
这是最常用的实现方式,Redis的原子性操作非常适合做限速。
<?php
class RateLimiter {
private $redis;
private $maxRequests; // 最大请求数
private $windowTime; // 时间窗口(秒)
public function __construct($redis, $maxRequests = 100, $windowTime = 60) {
$this->redis = $redis;
$this->maxRequests = $maxRequests;
$this->windowTime = $windowTime;
}
public function check($key) {
$current = time();
$windowStart = $current - $this->windowTime;
// 使用Redis事务保证原子性
$this->redis->multi();
$this->redis->zRemRangeByScore($key, 0, $windowStart);
$this->redis->zAdd($key, $current, uniqid('', true));
$this->redis->expire($key, $this->windowTime + 1);
$count = $this->redis->zCard($key);
$this->redis->exec();
return $count <= $this->maxRequests;
}
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$limiter = new RateLimiter($redis, 100, 60);
$userId = $_SERVER['REMOTE_ADDR']; // 可以根据IP或用户ID限速
if (!$limiter->check("rate_limit:{$userId}")) {
http_response_code(429);
header('Retry-After: 60');
echo json_encode(['error' => '请求过于频繁,请稍后再试']);
exit;
}
基于令牌桶算法
令牌桶算法可以允许突发的短时间高并发。
<?php
class TokenBucket {
private $redis;
private $capacity; // 桶容量
private $rate; // 令牌生成速率(每秒)
public function __construct($redis, $capacity = 100, $rate = 10) {
$this->redis = $redis;
$this->capacity = $capacity;
$this->rate = $rate;
}
public function consume($key, $tokens = 1) {
$now = microtime(true);
$bucketKey = "bucket:{$key}";
$lastRefillKey = "last_refill:{$key}";
// 使用Lua脚本保证原子性
$lua = <<<LUA
local key = KEYS[1]
local lastRefillKey = KEYS[2]
local now = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local rate = tonumber(ARGV[3])
local tokens = tonumber(ARGV[4])
local currentTokens = redis.call('GET', key)
if not currentTokens then
currentTokens = capacity
else
currentTokens = tonumber(currentTokens)
end
local lastRefill = redis.call('GET', lastRefillKey)
if not lastRefill then
lastRefill = now
else
lastRefill = tonumber(lastRefill)
end
-- 计算新生成的令牌
local newTokens = (now - lastRefill) * rate
currentTokens = math.min(capacity, currentTokens + newTokens)
-- 更新最后填充时间
redis.call('SET', lastRefillKey, now)
if currentTokens >= tokens then
redis.call('SET', key, currentTokens - tokens)
return 1
else
redis.call('SET', key, currentTokens)
return 0
end
LUA;
$result = $this->redis->eval($lua, [$bucketKey, $lastRefillKey, $now, $this->capacity, $this->rate, $tokens], 2);
return $result == 1;
}
}
基于文件系统的简单实现
适合没有Redis的小项目:
<?php
class FileRateLimiter {
private $limitDir;
private $maxRequests;
private $interval;
public function __construct($maxRequests = 100, $interval = 60) {
$this->limitDir = sys_get_temp_dir() . '/rate_limits/';
$this->maxRequests = $maxRequests;
$this->interval = $interval;
if (!is_dir($this->limitDir)) {
mkdir($this->limitDir, 0755, true);
}
}
public function check($key) {
$file = $this->limitDir . md5($key) . '.tmp';
$now = time();
// 使用文件锁防止并发
$fp = fopen($file, 'c+');
if (!flock($fp, LOCK_EX)) {
return false;
}
$data = [];
if (filesize($file) > 0) {
$data = json_decode(fread($fp, filesize($file)), true) ?? [];
}
// 清理过期记录
$data = array_filter($data, function($timestamp) use ($now, $key) {
return $timestamp > ($now - $this->interval);
});
if (count($data) >= $this->maxRequests) {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
$data[] = $now;
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode($data));
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
}
使用中间件封装(推荐)
结合框架使用中间件模式,如Laravel:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Redis;
class RateLimitMiddleware {
public function handle($request, Closure $next, $limit = 100, $timeWindow = 60) {
$key = sprintf('rate_limit:%s:%s',
$request->ip(),
$request->path()
);
$current = Redis::get($key) ?: 0;
if ($current >= $limit) {
return response()->json([
'error' => 'Too Many Requests',
'message' => '请稍后再试'
], 429);
}
Redis::multi();
Redis::incr($key);
Redis::expire($key, $timeWindow);
Redis::exec();
return $next($request);
}
}
Nginx级别的限速
如果只需要基本的IP限速,Nginx配置更高效:
# nginx.conf
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://php_backend;
}
}
}
最佳实践建议
-
选择合适算法:
- 滑动窗口:适合精确限速
- 令牌桶:允许短时间突发
- 漏桶算法:平滑请求
-
返回合适的HTTP头:
header('X-RateLimit-Limit: ' . $limit); header('X-RateLimit-Remaining: ' . $remaining); header('X-RateLimit-Reset: ' . $resetTime); -
分布式环境:使用Redis等中心化存储
-
粒度设计:
- 按IP限速
- 按用户ID限速
- 按API路径限速
- 组合使用
-
异常处理:限速失败时应该放行而不是拒绝所有请求
对于生产环境,建议使用成熟的库如 nikic/FastRoute 配合Redis实现,或者采用API网关来处理限速逻辑。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播