php实现 令牌桶
令牌桶算法简介
令牌桶算法是一种常用于流量控制和速率限制的算法。它维护一个固定容量的桶,按照固定速率向桶中添加令牌。请求需要获取令牌才能执行,若桶中没有足够令牌,请求会被限制。
PHP实现令牌桶
基于Redis的实现
使用Redis可以方便地实现分布式令牌桶,确保多服务器环境下的一致性。
class TokenBucket {
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 consume($tokens = 1) {
$now = microtime(true);
$script = '
local key = KEYS[1]
local now = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local rate = tonumber(ARGV[3])
local tokens = tonumber(ARGV[4])
local lastTime = redis.call("hget", key, "time")
local currentTokens = redis.call("hget", key, "tokens")
if not lastTime then
lastTime = now
currentTokens = capacity
else
local elapsed = now - lastTime
local newTokens = elapsed * rate
currentTokens = math.min(capacity, currentTokens + newTokens)
lastTime = now
end
if currentTokens >= tokens then
currentTokens = currentTokens - tokens
redis.call("hset", key, "time", lastTime)
redis.call("hset", key, "tokens", currentTokens)
return 1
else
return 0
end
';
return $this->redis->eval($script, [$this->key, $now, $this->capacity, $this->rate, $tokens], 1);
}
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$bucket = new TokenBucket($redis, 'api_rate_limit', 10, 1); // 容量10,每秒1个令牌
if ($bucket->consume()) {
echo "请求通过";
} else {
echo "请求被限制";
}
纯PHP实现
如果不依赖Redis,可以使用纯PHP实现单机版令牌桶。
class LocalTokenBucket {
private $capacity;
private $tokens;
private $rate;
private $lastTime;
public function __construct($capacity, $rate) {
$this->capacity = $capacity;
$this->tokens = $capacity;
$this->rate = $rate;
$this->lastTime = microtime(true);
}
public function consume($tokens = 1) {
$now = microtime(true);
$elapsed = $now - $this->lastTime;
$this->tokens = min($this->capacity, $this->tokens + $elapsed * $this->rate);
$this->lastTime = $now;
if ($this->tokens >= $tokens) {
$this->tokens -= $tokens;
return true;
}
return false;
}
}
// 使用示例
$bucket = new LocalTokenBucket(10, 1); // 容量10,每秒1个令牌
if ($bucket->consume()) {
echo "请求通过";
} else {
echo "请求被限制";
}
实现要点
令牌补充逻辑 计算自上次请求以来的时间差,补充相应数量的令牌: $newTokens = elapsedTime * rate$
令牌消耗检查 每次请求时检查是否有足够令牌: $if (currentTokens >= requestedTokens) { allow } else { deny }$
原子性操作 Redis实现使用Lua脚本确保操作的原子性,防止并发问题。
容量限制 补充令牌时不超过桶的容量: $currentTokens = min(capacity, currentTokens + newTokens)$
这两种实现方式分别适用于不同场景,Redis版本适合分布式系统,纯PHP版本适合单机应用。根据实际需求选择合适的实现方式。







