如何用PHP项目实现限流算法?

wen java案例 1

本文目录导读:

如何用PHP项目实现限流算法?

  1. 计数器算法(固定窗口)
  2. 滑动窗口算法
  3. 令牌桶算法
  4. 漏桶算法
  5. 分布式限流中间件
  6. 基于文件系统的简单限流
  7. 使用建议

我来详细介绍如何在PHP中实现几种常见的限流算法:

计数器算法(固定窗口)

<?php
class CounterLimiter {
    private $redis;
    private $key;
    private $limit;
    private $window; // 时间窗口(秒)
    public function __construct($redis, $key, $limit, $window = 60) {
        $this->redis = $redis;
        $this->key = $key;
        $this->limit = $limit;
        $this->window = $window;
    }
    public function allow() {
        $current = $this->redis->get($this->key);
        if ($current === false) {
            $this->redis->setex($this->key, $this->window, 1);
            return true;
        }
        if ($current >= $this->limit) {
            return false;
        }
        $this->redis->incr($this->key);
        return true;
    }
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$limiter = new CounterLimiter($redis, 'api_limit', 100, 60);
if ($limiter->allow()) {
    echo "请求允许";
} else {
    echo "请求被限流";
}

滑动窗口算法

<?php
class SlidingWindowLimiter {
    private $redis;
    private $key;
    private $limit;
    private $window;
    public function __construct($redis, $key, $limit, $window = 60) {
        $this->redis = $redis;
        $this->key = $key;
        $this->limit = $limit;
        $this->window = $window;
    }
    public function allow() {
        $now = microtime(true);
        $key = $this->key . ':sliding';
        // 添加当前时间戳
        $this->redis->zAdd($key, $now, $now . '_' . uniqid());
        // 移除窗口之外的数据
        $this->redis->zRemRangeByScore($key, 0, $now - $this->window);
        // 设置过期时间
        $this->redis->expire($key, $this->window + 1);
        // 获取窗口内的请求数
        $count = $this->redis->zCard($key);
        return $count <= $this->limit;
    }
}

令牌桶算法

<?php
class TokenBucketLimiter {
    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 allow() {
        $key = $this->key . ':bucket';
        $tokensKey = $key . ':tokens';
        $timeKey = $key . ':time';
        // 使用Lua脚本保证原子性
        $script = <<<LUA
            local tokens = redis.call('GET', KEYS[1])
            local last_time = redis.call('GET', KEYS[2])
            if not tokens then
                tokens = ARGV[1]
                last_time = ARGV[2]
            else
                local now = tonumber(ARGV[2])
                local elapsed = now - tonumber(last_time)
                local new_tokens = tonumber(tokens) + (elapsed * tonumber(ARGV[3]))
                if new_tokens > tonumber(ARGV[1]) then
                    tokens = ARGV[1]
                else
                    tokens = tostring(new_tokens)
                end
                last_time = ARGV[2]
            end
            if tonumber(tokens) >= 1 then
                redis.call('SET', KEYS[1], tonumber(tokens) - 1)
                redis.call('SET', KEYS[2], last_time)
                return 1
            else
                redis.call('SET', KEYS[1], tokens)
                redis.call('SET', KEYS[2], last_time)
                return 0
            end
LUA;
        $result = $this->redis->eval(
            $script,
            [$tokensKey, $timeKey],
            2,
            $this->capacity,
            microtime(true),
            $this->rate
        );
        return $result == 1;
    }
}

漏桶算法

<?php
class LeakyBucketLimiter {
    private $redis;
    private $key;
    private $capacity;  // 桶容量
    private $leakRate;  // 漏水速率(个/秒)
    public function __construct($redis, $key, $capacity, $leakRate) {
        $this->redis = $redis;
        $this->key = $key;
        $this->capacity = $capacity;
        $this->leakRate = $leakRate;
    }
    public function allow() {
        $key = $this->key . ':leaky';
        $waterKey = $key . ':water';
        $timeKey = $key . ':time';
        // 使用Lua脚本保证原子性
        $script = <<<LUA
            local water = redis.call('GET', KEYS[1])
            local last_time = redis.call('GET', KEYS[2])
            if not water then
                water = 0
                last_time = ARGV[2]
            else
                local now = tonumber(ARGV[2])
                local elapsed = now - tonumber(last_time)
                local leaked = elapsed * tonumber(ARGV[3])
                water = math.max(0, tonumber(water) - leaked)
                last_time = ARGV[2]
            end
            if tonumber(water) < tonumber(ARGV[1]) then
                redis.call('SET', KEYS[1], tonumber(water) + 1)
                redis.call('SET', KEYS[2], last_time)
                return 1
            else
                redis.call('SET', KEYS[1], water)
                redis.call('SET', KEYS[2], last_time)
                return 0
            end
LUA;
        $result = $this->redis->eval(
            $script,
            [$waterKey, $timeKey],
            2,
            $this->capacity,
            microtime(true),
            $this->leakRate
        );
        return $result == 1;
    }
}

分布式限流中间件

<?php
class RateLimitMiddleware {
    private $limiter;
    private $redis;
    public function __construct($redis) {
        $this->redis = $redis;
    }
    // IP限流
    public function ipLimiter($ip, $limit = 100, $window = 60) {
        $this->limiter = new SlidingWindowLimiter(
            $this->redis,
            "rate_limit:ip:{$ip}",
            $limit,
            $window
        );
        return $this->limiter;
    }
    // 用户限流
    public function userLimiter($userId, $limit = 50, $window = 60) {
        $this->limiter = new TokenBucketLimiter(
            $this->redis,
            "rate_limit:user:{$userId}",
            $limit,
            $limit / $window
        );
        return $this->limiter;
    }
    // API限流
    public function apiLimiter($apiPath, $limit = 1000, $window = 3600) {
        $this->limiter = new CounterLimiter(
            $this->redis,
            "rate_limit:api:{$apiPath}",
            $limit,
            $window
        );
        return $this->limiter;
    }
}
// 使用示例
class ApiController {
    private $rateLimitMiddleware;
    public function __construct() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $this->rateLimitMiddleware = new RateLimitMiddleware($redis);
    }
    public function handleRequest() {
        $ip = $_SERVER['REMOTE_ADDR'];
        // 检查IP限流
        $ipLimiter = $this->rateLimitMiddleware->ipLimiter($ip, 100, 60);
        if (!$ipLimiter->allow()) {
            http_response_code(429);
            echo json_encode(['error' => 'Too Many Requests']);
            return;
        }
        // 处理业务逻辑
        // ...
    }
}

基于文件系统的简单限流

<?php
class FileBasedLimiter {
    private $logFile;
    private $limit;
    private $window;
    public function __construct($logFile, $limit = 100, $window = 60) {
        $this->logFile = $logFile;
        $this->limit = $limit;
        $this->window = $window;
    }
    public function allow() {
        $now = time();
        $logs = $this->readLogs();
        // 清理过期日志
        $logs = array_filter($logs, function($timestamp) use ($now) {
            return ($now - $timestamp) <= $this->window;
        });
        // 检查是否超过限制
        if (count($logs) >= $this->limit) {
            return false;
        }
        // 添加当前请求
        $logs[] = $now;
        $this->writeLogs($logs);
        return true;
    }
    private function readLogs() {
        if (!file_exists($this->logFile)) {
            return [];
        }
        $content = file_get_contents($this->logFile);
        return $content ? explode(',', $content) : [];
    }
    private function writeLogs($logs) {
        // 只保留最近时间窗口内的日志
        $now = time();
        $logs = array_filter($logs, function($timestamp) use ($now) {
            return ($now - $timestamp) <= $this->window;
        });
        file_put_contents($this->logFile, implode(',', $logs));
    }
}

使用建议

  1. 选择合适的算法

    • 简单场景:计数器算法
    • 精确控制:滑动窗口算法
    • 允许突发流量:令牌桶算法
    • 平滑处理:漏桶算法
  2. 性能优化

    • 使用Redis Lua脚本保证原子性
    • 合理设置缓存过期时间
    • 考虑使用本地缓存减少Redis请求
  3. 实际应用

    • 结合IP、用户、API等多维度限流
    • 返回适当的HTTP状态码(429)
    • 提供限流信息头(X-RateLimit-*)

这些实现可以根据具体需求进行调整和优化。

抱歉,评论功能暂时关闭!