php实现限流算法
固定窗口计数器算法
固定窗口计数器算法将时间划分为固定大小的窗口,每个窗口内请求次数达到阈值后拒绝后续请求。实现简单但临界时间点可能出现请求突增。
class FixedWindowRateLimiter {
private $limit;
private $window;
private $counter = 0;
private $lastReset;
public function __construct($limit, $window) {
$this->limit = $limit;
$this->window = $window;
$this->lastReset = time();
}
public function allowRequest() {
$currentTime = time();
if ($currentTime - $this->lastReset >= $this->window) {
$this->counter = 0;
$this->lastReset = $currentTime;
}
if ($this->counter < $this->limit) {
$this->counter++;
return true;
}
return false;
}
}
// 使用示例:每分钟允许100次请求
$limiter = new FixedWindowRateLimiter(100, 60);
if ($limiter->allowRequest()) {
// 处理请求
} else {
// 拒绝请求
}
滑动窗口日志算法
滑动窗口日志算法记录每个请求的时间戳,统计当前窗口内的请求数量。精度高但内存消耗随请求量增加。
class SlidingWindowLogRateLimiter {
private $limit;
private $window;
private $timestamps = [];
public function __construct($limit, $window) {
$this->limit = $limit;
$this->window = $window;
}
public function allowRequest() {
$currentTime = time();
$this->timestamps = array_filter(
$this->timestamps,
function ($timestamp) use ($currentTime) {
return $timestamp > $currentTime - $this->window;
}
);
if (count($this->timestamps) < $this->limit) {
$this->timestamps[] = $currentTime;
return true;
}
return false;
}
}
令牌桶算法
令牌桶算法以固定速率向桶中添加令牌,请求获取令牌成功则放行。允许突发流量,适合需要应对流量波动的场景。
class TokenBucketRateLimiter {
private $capacity;
private $tokens;
private $fillRate; // 令牌/秒
private $lastFill;
public function __construct($capacity, $fillRate) {
$this->capacity = $capacity;
$this->fillRate = $fillRate;
$this->tokens = $capacity;
$this->lastFill = time();
}
public function allowRequest($tokens = 1) {
$this->refill();
if ($this->tokens >= $tokens) {
$this->tokens -= $tokens;
return true;
}
return false;
}
private function refill() {
$now = time();
$elapsed = $now - $this->lastFill;
$this->tokens = min(
$this->capacity,
$this->tokens + $elapsed * $this->fillRate
);
$this->lastFill = $now;
}
}
// 使用示例:桶容量100,每秒填充2个令牌
$limiter = new TokenBucketRateLimiter(100, 2);
if ($limiter->allowRequest()) {
// 处理请求
}
漏桶算法
漏桶算法以固定速率处理请求,超出桶容量的请求被丢弃。确保流量输出速率恒定,适合保护下游系统。
class LeakyBucketRateLimiter {
private $capacity;
private $leakRate; // 请求/秒
private $queue = [];
private $lastLeak;
public function __construct($capacity, $leakRate) {
$this->capacity = $capacity;
$this->leakRate = $leakRate;
$this->lastLeak = time();
}
public function allowRequest() {
$this->leak();
if (count($this->queue) < $this->capacity) {
$this->queue[] = time();
return true;
}
return false;
}
private function leak() {
$now = time();
$elapsed = $now - $this->lastLeak;
$leakCount = $elapsed * $this->leakRate;
$this->queue = array_slice(
$this->queue,
$leakCount
);
$this->lastLeak = $now;
}
}
Redis实现的分布式限流
使用Redis实现分布式环境下的限流,确保多服务器间共享计数状态。以下为令牌桶算法的Redis实现:
class RedisTokenBucketRateLimiter {
private $redis;
private $keyPrefix;
private $capacity;
private $fillRate;
public function __construct($redis, $keyPrefix, $capacity, $fillRate) {
$this->redis = $redis;
$this->keyPrefix = $keyPrefix;
$this->capacity = $capacity;
$this->fillRate = $fillRate;
}
public function allowRequest($userId, $tokens = 1) {
$key = $this->keyPrefix . $userId;
$now = time();
$lua = <<<LUA
local key = KEYS[1]
local now = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local fillRate = tonumber(ARGV[3])
local tokens = tonumber(ARGV[4])
local lastData = redis.call("HMGET", key, "tokens", "lastFill")
local lastTokens = tonumber(lastData[1]) or capacity
local lastFill = tonumber(lastData[2]) or now
local elapsed = now - lastFill
local newTokens = math.min(
capacity,
lastTokens + elapsed * fillRate
)
if newTokens >= tokens then
redis.call("HMSET", key, "tokens", newTokens - tokens, "lastFill", now)
return 1
else
return 0
end
LUA;
return $this->redis->eval(
$lua,
[$key, $now, $this->capacity, $this->fillRate, $tokens],
1
);
}
}
实际应用建议
生产环境中建议结合以下策略:
- 优先选择Redis实现的分布式限流方案
- 对API接口可采用中间件形式集成限流
- 配合HTTP 429状态码和Retry-After头部返回限流信息
- 重要接口建议采用令牌桶算法,普通接口可采用滑动窗口
Nginx层面也可通过limit_req模块实现限流,与业务层限流形成多级防护。







