php实现 令牌桶
令牌桶算法简介
令牌桶算法是一种常用的流量控制算法,通过固定速率生成令牌,请求需获取令牌才能执行,未获取令牌的请求会被限制或排队。适用于API限流、接口防刷等场景。

PHP实现令牌桶
基本实现思路
- 维护一个容器存储令牌(可用Redis或内存变量)
- 设定令牌生成速率和桶容量
- 每次请求时检查可用令牌数量
- 有足够令牌则扣除并允许请求,否则拒绝
基于Redis的实现
<?php
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("hmset", key, "time", lastTime, "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_limit', 10, 1); // 容量10,速率1个/秒
if ($bucket->consume()) {
echo "请求通过";
} else {
echo "请求被限流";
}
?>
纯PHP实现(无Redis)
<?php
class MemoryTokenBucket {
private $tokens;
private $capacity;
private $rate;
private $lastTime;
public function __construct($capacity, $rate) {
$this->capacity = $capacity;
$this->rate = $rate;
$this->tokens = $capacity;
$this->lastTime = microtime(true);
}
public function consume($tokens = 1) {
$now = microtime(true);
$elapsed = $now - $this->lastTime;
$this->lastTime = $now;
$this->tokens = min(
$this->capacity,
$this->tokens + $elapsed * $this->rate
);
if ($this->tokens >= $tokens) {
$this->tokens -= $tokens;
return true;
}
return false;
}
}
// 使用示例
$bucket = new MemoryTokenBucket(10, 1); // 容量10,速率1个/秒
if ($bucket->consume()) {
echo "请求通过";
} else {
echo "请求被限流";
}
?>
实现要点
- 使用Redis的Lua脚本保证原子性操作
- 精确计算时间差来补充令牌
- 支持突发流量(桶容量大于生成速率时)
- 分布式环境下必须使用Redis等共享存储
性能优化建议
- 对高频访问的令牌桶可增加本地缓存
- 批量获取令牌时建议使用Lua脚本
- 多键操作时考虑使用Redis管道
- 根据业务场景调整桶容量和生成速率






