本文目录导读:

在PHP中实现按用户的API限频,推荐以下方案:
基于Token/UserID的Redis方案(推荐)
<?php
class RateLimiter {
private $redis;
private $maxRequests = 100; // 限制次数
private $timeWindow = 60; // 时间窗口(秒)
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
// 滑动窗口算法
public function isAllowed($userId) {
$key = "rate_limit:user:{$userId}";
$now = time();
// 移除窗口外的记录
$this->redis->zRemRangeByScore($key, 0, $now - $this->timeWindow);
// 获取当前窗口内的请求数
$currentCount = $this->redis->zCard($key);
if ($currentCount >= $this->maxRequests) {
return false;
}
// 添加当前请求
$this->redis->zAdd($key, $now, $now . ':' . uniqid());
$this->redis->expire($key, $this->timeWindow);
return true;
}
}
// 使用示例
$limiter = new RateLimiter();
$userId = $_SERVER['HTTP_X_USER_ID'] ?? $_GET['user_id'];
if (!$limiter->isAllowed($userId)) {
http_response_code(429);
echo json_encode(['error' => 'Too Many Requests']);
exit;
}
// 正常处理请求
基于数据库的方案(简单实现)
<?php
// 数据库表结构
/*
CREATE TABLE api_rate_limits (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
window_start TIMESTAMP NOT NULL,
request_count INT DEFAULT 0,
UNIQUE KEY idx_user_window (user_id, window_start)
);
*/
class DBRateLimiter {
private $db;
private $maxRequests = 100;
private $timeWindow = 60;
public function __construct() {
$this->db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
}
public function isAllowed($userId) {
$windowStart = date('Y-m-d H:i:s', time() - $this->timeWindow);
try {
$this->db->beginTransaction();
// 清理过期记录
$stmt = $this->db->prepare("DELETE FROM api_rate_limits WHERE window_start < ?");
$stmt->execute([$windowStart]);
// 尝试插入或更新
$stmt = $this->db->prepare("
INSERT INTO api_rate_limits (user_id, window_start, request_count)
VALUES (?, NOW(), 1)
ON DUPLICATE KEY UPDATE request_count = request_count + 1
");
$stmt->execute([$userId]);
// 检查是否超限
$stmt = $this->db->prepare("
SELECT COUNT(*) as total
FROM api_rate_limits
WHERE user_id = ? AND window_start >= ?
");
$stmt->execute([$userId, $windowStart]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$this->db->commit();
return $result['total'] <= $this->maxRequests;
} catch (Exception $e) {
$this->db->rollBack();
return false;
}
}
}
中间件实现的完整方案
<?php
// Middleware接口
interface RateLimitMiddleware {
public function handle($request, $next);
}
// Token Bucket算法实现
class TokenBucketRateLimiter implements RateLimitMiddleware {
private $redis;
private $config = [
'capacity' => 100, // 桶容量
'refillRate' => 10, // 每秒补充速率
'refillTime' => 1 // 补充间隔(秒)
];
public function __construct($config = []) {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->config = array_merge($this->config, $config);
}
public function handle($request, $next) {
$userId = $this->getUserId($request);
$key = "token_bucket:{$userId}";
// 获取或初始化令牌桶
$bucket = $this->redis->hGetAll($key);
if (empty($bucket)) {
$bucket = [
'tokens' => $this->config['capacity'],
'last_refill' => time()
];
$this->redis->hMSet($key, $bucket);
$this->redis->expire($key, 3600);
}
// 补充令牌
$now = time();
$elapsed = $now - $bucket['last_refill'];
$tokensToAdd = floor($elapsed * $this->config['refillRate']);
if ($tokensToAdd > 0) {
$bucket['tokens'] = min(
$this->config['capacity'],
$bucket['tokens'] + $tokensToAdd
);
$bucket['last_refill'] = $now;
$this->redis->hMSet($key, $bucket);
}
// 检查是否有可用令牌
if ($bucket['tokens'] < 1) {
http_response_code(429);
header('Retry-After: ' . (1 / $this->config['refillRate']));
echo json_encode([
'error' => 'Rate limit exceeded',
'retry_after' => 1 / $this->config['refillRate']
]);
exit;
}
// 消耗令牌
$this->redis->hIncrBy($key, 'tokens', -1);
// 设置响应头
header('X-RateLimit-Limit: ' . $this->config['capacity']);
header('X-RateLimit-Remaining: ' . ($bucket['tokens'] - 1));
return $next($request);
}
private function getUserId($request) {
// 根据API密钥或Token获取用户ID
return $request['user_id'] ?? $_SERVER['HTTP_AUTHORIZATION'];
}
}
按不同用户级别配置限制
<?php
class TieredRateLimiter {
private $redis;
private $tiers = [
'free' => ['limit' => 100, 'window' => 3600], // 每小时100次
'basic' => ['limit' => 1000, 'window' => 3600], // 每小时1000次
'premium' => ['limit' => 10000, 'window' => 3600], // 每小时10000次
'enterprise' => ['limit' => 100000, 'window' => 3600]
];
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
public function checkRateLimit($userId, $userTier = 'free') {
$tier = $this->tiers[$userTier] ?? $this->tiers['free'];
$key = "rate_limit:{$userTier}:{$userId}";
$currentCount = $this->redis->incr($key);
if ($currentCount === 1) {
$this->redis->expire($key, $tier['window']);
}
if ($currentCount > $tier['limit']) {
$ttl = $this->redis->ttl($key);
return [
'allowed' => false,
'retry_after' => $ttl,
'limit' => $tier['limit'],
'remaining' => 0
];
}
return [
'allowed' => true,
'limit' => $tier['limit'],
'remaining' => $tier['limit'] - $currentCount
];
}
}
使用注意:
- 用户识别:确保能正确识别用户(JWT Token、API Key、Session等)
- 性能优化:
- 使用Redis的Lua脚本实现原子操作
- 考虑使用Local Cache减轻Redis压力
- 分布式环境:
- Redis集群支持
- 考虑使用一致性哈希
- 错误处理:
- 返回适当的HTTP状态码(429)
- 包含Retry-After头
- 提供重试信息
根据你的具体需求选择合适的方案,Redis方案性能和灵活性最佳。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播