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 tryAcquire($tokens = 1) {
$now = microtime(true);
$script = <<<LUA
local key = KEYS[1]
local now = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local capacity = tonumber(ARGV[3])
local tokensRequested = tonumber(ARGV[4])
local lastTime = tonumber(redis.call("hget", key, "lastTime")) or now
local tokensAvailable = tonumber(redis.call("hget", key, "tokens")) or capacity
local elapsed = now - lastTime
local newTokens = elapsed * rate
if newTokens > 0 then
tokensAvailable = math.min(capacity, tokensAvailable + newTokens)
lastTime = now
end
if tokensAvailable >= tokensRequested then
tokensAvailable = tokensAvailable - tokensRequested
redis.call("hmset", key, "lastTime", lastTime, "tokens", tokensAvailable)
return 1
else
return 0
end
LUA;
return $this->redis->eval($script, [$this->key, $now, $this->rate, $this->capacity, $tokens], 1);
}
}
使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$bucket = new TokenBucket($redis, 'api_rate_limit', 100, 10); // 容量100,每秒生成10个令牌
if ($bucket->tryAcquire()) {
echo "请求通过";
} else {
echo "请求被限流";
}
纯PHP实现(无Redis)
适合单机环境,使用文件或共享内存存储状态。
class FileTokenBucket {
private $file;
private $capacity;
private $rate;
public function __construct($file, $capacity, $rate) {
$this->file = $file;
$this->capacity = $capacity;
$this->rate = $rate;
}
public function tryAcquire($tokens = 1) {
$fp = fopen($this->file, 'c+');
if (!flock($fp, LOCK_EX)) {
fclose($fp);
return false;
}
$data = @json_decode(fread($fp, filesize($this->file) ?: 1024), true) ?: [
'tokens' => $this->capacity,
'lastTime' => microtime(true)
];
$now = microtime(true);
$elapsed = $now - $data['lastTime'];
$newTokens = $elapsed * $this->rate;
$data['tokens'] = min($this->capacity, $data['tokens'] + $newTokens);
$data['lastTime'] = $now;
if ($data['tokens'] >= $tokens) {
$data['tokens'] -= $tokens;
ftruncate($fp, 0);
fseek($fp, 0);
fwrite($fp, json_encode($data));
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
}
关键参数说明
- 容量(capacity):桶中最多存放的令牌数量
- 速率(rate):每秒生成的令牌数量
- 获取数量(tokens):每次请求消耗的令牌数(默认为1)
应用场景
- API接口限流
- 防止暴力破解
- 分布式系统流量控制
注意事项
- Redis实现适合分布式环境
- 文件锁实现存在性能瓶颈
- 令牌生成采用惰性计算(请求时计算)
- 实际应用中可结合IP或用户ID区分不同桶







