当前位置:首页 > PHP

php实现 令牌桶

2026-01-30 08:32:25PHP

令牌桶算法简介

令牌桶算法是一种常用的流量控制算法,通过固定速率生成令牌,请求需要获取令牌才能被处理。当令牌耗尽时,请求会被限制或拒绝。

php实现 令牌桶

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 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区分不同桶

标签: 令牌php
分享给朋友:

相关文章

php 实现自动收货

php 实现自动收货

PHP 实现自动收货功能 自动收货功能通常用于电商系统,在订单超过一定时间未手动确认收货时自动完成订单。以下是实现方法: 数据库设计 需要订单表包含以下关键字段: CREATE TABLE…

php实现接口

php实现接口

PHP 实现接口的方法 在 PHP 中,接口(Interface)用于定义一组方法的规范,而不实现具体功能。类可以实现一个或多个接口,并必须实现接口中定义的所有方法。 定义接口 接口使用 inter…

php 实现下载

php 实现下载

PHP 实现文件下载的方法 使用 PHP 实现文件下载可以通过设置 HTTP 头信息,强制浏览器将文件作为附件下载而非直接打开。 基本下载实现 $file_path = 'path/to/your/…

php 实现类

php 实现类

PHP 类的基本实现 在 PHP 中,类通过 class 关键字定义,后跟类名和一对花括号。类名通常遵循大驼峰命名法(如 ClassName)。以下是基本语法: class ClassName {…

php实现mvc

php实现mvc

PHP 实现 MVC 架构 MVC(Model-View-Controller)是一种常用的软件设计模式,将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller…

php 线程实现

php 线程实现

PHP 线程实现方法 PHP 本身是单线程语言,但可以通过扩展或库实现多线程功能。以下是几种常见的实现方式: 使用 pthreads 扩展 pthreads 是一个 PHP 扩展,允许在 PHP 中…