本文目录导读:

我来详细介绍如何在PHP中实现几种常见的限流算法:
计数器算法(固定窗口)
<?php
class CounterLimiter {
private $redis;
private $key;
private $limit;
private $window; // 时间窗口(秒)
public function __construct($redis, $key, $limit, $window = 60) {
$this->redis = $redis;
$this->key = $key;
$this->limit = $limit;
$this->window = $window;
}
public function allow() {
$current = $this->redis->get($this->key);
if ($current === false) {
$this->redis->setex($this->key, $this->window, 1);
return true;
}
if ($current >= $this->limit) {
return false;
}
$this->redis->incr($this->key);
return true;
}
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$limiter = new CounterLimiter($redis, 'api_limit', 100, 60);
if ($limiter->allow()) {
echo "请求允许";
} else {
echo "请求被限流";
}
滑动窗口算法
<?php
class SlidingWindowLimiter {
private $redis;
private $key;
private $limit;
private $window;
public function __construct($redis, $key, $limit, $window = 60) {
$this->redis = $redis;
$this->key = $key;
$this->limit = $limit;
$this->window = $window;
}
public function allow() {
$now = microtime(true);
$key = $this->key . ':sliding';
// 添加当前时间戳
$this->redis->zAdd($key, $now, $now . '_' . uniqid());
// 移除窗口之外的数据
$this->redis->zRemRangeByScore($key, 0, $now - $this->window);
// 设置过期时间
$this->redis->expire($key, $this->window + 1);
// 获取窗口内的请求数
$count = $this->redis->zCard($key);
return $count <= $this->limit;
}
}
令牌桶算法
<?php
class TokenBucketLimiter {
private $redis;
private $key;
private $capacity; // 桶容量
private $rate; // 令牌生成速率(个/秒)
public function __construct($redis, $key, $capacity, $rate) {
$this->redis = $redis;
$this->key = $key;
$this->capacity = $capacity;
$this->rate = $rate;
}
public function allow() {
$key = $this->key . ':bucket';
$tokensKey = $key . ':tokens';
$timeKey = $key . ':time';
// 使用Lua脚本保证原子性
$script = <<<LUA
local tokens = redis.call('GET', KEYS[1])
local last_time = redis.call('GET', KEYS[2])
if not tokens then
tokens = ARGV[1]
last_time = ARGV[2]
else
local now = tonumber(ARGV[2])
local elapsed = now - tonumber(last_time)
local new_tokens = tonumber(tokens) + (elapsed * tonumber(ARGV[3]))
if new_tokens > tonumber(ARGV[1]) then
tokens = ARGV[1]
else
tokens = tostring(new_tokens)
end
last_time = ARGV[2]
end
if tonumber(tokens) >= 1 then
redis.call('SET', KEYS[1], tonumber(tokens) - 1)
redis.call('SET', KEYS[2], last_time)
return 1
else
redis.call('SET', KEYS[1], tokens)
redis.call('SET', KEYS[2], last_time)
return 0
end
LUA;
$result = $this->redis->eval(
$script,
[$tokensKey, $timeKey],
2,
$this->capacity,
microtime(true),
$this->rate
);
return $result == 1;
}
}
漏桶算法
<?php
class LeakyBucketLimiter {
private $redis;
private $key;
private $capacity; // 桶容量
private $leakRate; // 漏水速率(个/秒)
public function __construct($redis, $key, $capacity, $leakRate) {
$this->redis = $redis;
$this->key = $key;
$this->capacity = $capacity;
$this->leakRate = $leakRate;
}
public function allow() {
$key = $this->key . ':leaky';
$waterKey = $key . ':water';
$timeKey = $key . ':time';
// 使用Lua脚本保证原子性
$script = <<<LUA
local water = redis.call('GET', KEYS[1])
local last_time = redis.call('GET', KEYS[2])
if not water then
water = 0
last_time = ARGV[2]
else
local now = tonumber(ARGV[2])
local elapsed = now - tonumber(last_time)
local leaked = elapsed * tonumber(ARGV[3])
water = math.max(0, tonumber(water) - leaked)
last_time = ARGV[2]
end
if tonumber(water) < tonumber(ARGV[1]) then
redis.call('SET', KEYS[1], tonumber(water) + 1)
redis.call('SET', KEYS[2], last_time)
return 1
else
redis.call('SET', KEYS[1], water)
redis.call('SET', KEYS[2], last_time)
return 0
end
LUA;
$result = $this->redis->eval(
$script,
[$waterKey, $timeKey],
2,
$this->capacity,
microtime(true),
$this->leakRate
);
return $result == 1;
}
}
分布式限流中间件
<?php
class RateLimitMiddleware {
private $limiter;
private $redis;
public function __construct($redis) {
$this->redis = $redis;
}
// IP限流
public function ipLimiter($ip, $limit = 100, $window = 60) {
$this->limiter = new SlidingWindowLimiter(
$this->redis,
"rate_limit:ip:{$ip}",
$limit,
$window
);
return $this->limiter;
}
// 用户限流
public function userLimiter($userId, $limit = 50, $window = 60) {
$this->limiter = new TokenBucketLimiter(
$this->redis,
"rate_limit:user:{$userId}",
$limit,
$limit / $window
);
return $this->limiter;
}
// API限流
public function apiLimiter($apiPath, $limit = 1000, $window = 3600) {
$this->limiter = new CounterLimiter(
$this->redis,
"rate_limit:api:{$apiPath}",
$limit,
$window
);
return $this->limiter;
}
}
// 使用示例
class ApiController {
private $rateLimitMiddleware;
public function __construct() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$this->rateLimitMiddleware = new RateLimitMiddleware($redis);
}
public function handleRequest() {
$ip = $_SERVER['REMOTE_ADDR'];
// 检查IP限流
$ipLimiter = $this->rateLimitMiddleware->ipLimiter($ip, 100, 60);
if (!$ipLimiter->allow()) {
http_response_code(429);
echo json_encode(['error' => 'Too Many Requests']);
return;
}
// 处理业务逻辑
// ...
}
}
基于文件系统的简单限流
<?php
class FileBasedLimiter {
private $logFile;
private $limit;
private $window;
public function __construct($logFile, $limit = 100, $window = 60) {
$this->logFile = $logFile;
$this->limit = $limit;
$this->window = $window;
}
public function allow() {
$now = time();
$logs = $this->readLogs();
// 清理过期日志
$logs = array_filter($logs, function($timestamp) use ($now) {
return ($now - $timestamp) <= $this->window;
});
// 检查是否超过限制
if (count($logs) >= $this->limit) {
return false;
}
// 添加当前请求
$logs[] = $now;
$this->writeLogs($logs);
return true;
}
private function readLogs() {
if (!file_exists($this->logFile)) {
return [];
}
$content = file_get_contents($this->logFile);
return $content ? explode(',', $content) : [];
}
private function writeLogs($logs) {
// 只保留最近时间窗口内的日志
$now = time();
$logs = array_filter($logs, function($timestamp) use ($now) {
return ($now - $timestamp) <= $this->window;
});
file_put_contents($this->logFile, implode(',', $logs));
}
}
使用建议
-
选择合适的算法:
- 简单场景:计数器算法
- 精确控制:滑动窗口算法
- 允许突发流量:令牌桶算法
- 平滑处理:漏桶算法
-
性能优化:
- 使用Redis Lua脚本保证原子性
- 合理设置缓存过期时间
- 考虑使用本地缓存减少Redis请求
-
实际应用:
- 结合IP、用户、API等多维度限流
- 返回适当的HTTP状态码(429)
- 提供限流信息头(X-RateLimit-*)
这些实现可以根据具体需求进行调整和优化。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播