PHP 怎么限流降级

wen PHP项目 3

本文目录导读:

PHP 怎么限流降级

  1. 限流实现方案
  2. 降级实现方案
  3. 综合限流降级框架
  4. 实践建议
  5. 注意事项

在 PHP 中实现限流降级,通常需要结合限流(控制请求速率)和降级(在系统压力过大时返回降级响应),以下是常用的实现方案,从简单到复杂:


限流实现方案

基于 Redis 的固定窗口限流

<?php
class RateLimiter {
    private $redis;
    private $key;
    private $maxRequests;
    private $windowSize;
    public function __construct($redis, $key, $maxRequests, $windowSize = 60) {
        $this->redis = $redis;
        $this->key = $key;
        $this->maxRequests = $maxRequests;
        $this->windowSize = $windowSize;
    }
    public function allow() {
        $current = $this->redis->get($this->key);
        if ($current === false) {
            $this->redis->setex($this->key, $this->windowSize, 1);
            return true;
        }
        if ($current < $this->maxRequests) {
            $this->redis->incr($this->key);
            return true;
        }
        return false;
    }
}

基于 Redis 的滑动窗口限流(更精确)

<?php
class SlidingWindowRateLimiter {
    private $redis;
    private $key;
    public function __construct($redis, $key) {
        $this->redis = $redis;
        $this->key = $key;
    }
    public function allow($maxRequests, $windowSize = 60) {
        $currentTime = time();
        $windowStart = $currentTime - $windowSize;
        // 使用有序集合存储请求时间戳
        $this->redis->zremrangebyscore($this->key, 0, $windowStart);
        $count = $this->redis->zcard($this->key);
        if ($count < $maxRequests) {
            $this->redis->zadd($this->key, $currentTime, uniqid());
            $this->redis->expire($this->key, $windowSize);
            return true;
        }
        return false;
    }
}

基于令牌桶算法(推荐)

<?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 allow() {
        $script = "
            local bucket = KEYS[1]
            local capacity = tonumber(ARGV[1])
            local rate = tonumber(ARGV[2])
            local now = tonumber(ARGV[3])
            local tokens = redis.call('get', bucket .. ':tokens')
            if not tokens then
                tokens = capacity
            else
                tokens = tonumber(tokens)
            end
            local lastTime = redis.call('get', bucket .. ':lastTime')
            if not lastTime then
                lastTime = now
            else
                lastTime = tonumber(lastTime)
            end
            local elapsed = now - lastTime
            tokens = math.min(capacity, tokens + elapsed * rate)
            redis.call('set', bucket .. ':tokens', tokens)
            redis.call('set', bucket .. ':lastTime', now)
            redis.call('expire', bucket .. ':tokens', 60)
            redis.call('expire', bucket .. ':lastTime', 60)
            if tokens >= 1 then
                redis.call('set', bucket .. ':tokens', tokens - 1)
                return 1
            else
                return 0
            end
        ";
        return $this->redis->eval($script, 1, $this->key, $this->capacity, $this->rate, time());
    }
}

降级实现方案

简单的降级开关

<?php
class CircuitBreaker {
    private $redis;
    private $key;
    private $failureThreshold;    // 失败阈值
    private $timeoutWindow;       // 超时窗口
    public function __construct($redis, $key, $failureThreshold = 5, $timeoutWindow = 30) {
        $this->redis = $redis;
        $this->key = $key;
        $this->failureThreshold = $failureThreshold;
        $this->timeoutWindow = $timeoutWindow;
    }
    public function isOpen() {
        $state = $this->redis->get($this->key . ':state');
        return $state === 'open';
    }
    public function recordSuccess() {
        $this->redis->del($this->key . ':failures');
        $this->redis->set($this->key . ':state', 'closed');
    }
    public function recordFailure() {
        $failures = $this->redis->incr($this->key . ':failures');
        $this->redis->expire($this->key . ':failures', $this->timeoutWindow);
        if ($failures >= $this->failureThreshold) {
            $this->redis->setex($this->key . ':state', $this->timeoutWindow, 'open');
        }
    }
}

完整降级示例

<?php
class ServiceDegrader {
    private $circuitBreaker;
    public function __construct() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $this->circuitBreaker = new CircuitBreaker($redis, 'api_name');
    }
    public function callApi($params) {
        // 检查熔断状态
        if ($this->circuitBreaker->isOpen()) {
            return $this->fallbackResponse();
        }
        try {
            // 调用真实服务
            $result = $this->makeRealApiCall($params);
            $this->circuitBreaker->recordSuccess();
            return $result;
        } catch (Exception $e) {
            $this->circuitBreaker->recordFailure();
            return $this->fallbackResponse();
        }
    }
    private function fallbackResponse() {
        // 降级响应:返回缓存数据或默认值
        return [
            'data' => $this->getCacheData(),
            'is_degraded' => true
        ];
    }
    private function getCacheData() {
        // 从缓存获取降级数据
        return Redis::get('fallback_data') ?? [];
    }
    private function makeRealApiCall($params) {
        // 真实 API 调用
    }
}

综合限流降级框架

中间件方式(适用于 Laravel/Slim 等框架)

<?php
class RateLimitAndDegradeMiddleware {
    public function handle($request, $next) {
        $rateLimiter = new TokenBucket(
            Redis::connection(),
            'user:' . $request->user_id,
            $capacity = 100,
            $rate = 10
        );
        // 限流检查
        if (!$rateLimiter->allow()) {
            return response()->json([
                'error' => 'Too Many Requests',
                'retry_after' => 60
            ], 429);
        }
        // 熔断检查
        $circuitBreaker = new CircuitBreaker(Redis::connection(), 'api');
        if ($circuitBreaker->isOpen()) {
            // 降级策略
            return $this->getCachedResponse($request);
        }
        try {
            $response = $next($request);
            $circuitBreaker->recordSuccess();
            return $response;
        } catch (Exception $e) {
            $circuitBreaker->recordFailure();
            return $this->getCachedResponse($request);
        }
    }
    private function getCachedResponse($request) {
        // 从缓存获取降级响应
        return Cache::remember('degraded:' . $request->path(), 60, function() {
            return ['status' => 'degraded'];
        });
    }
}

使用第三方库

  • Redis 的 Redis::throttle() (Laravel)
  • Rate Limit 包 - briceburg/php-rate-limiter
  • Symfony RateLimiter 组件

实践建议

降级策略选择

  • 缓存降级:从 Redis/数据库缓存获取旧数据
  • 默认值降级:返回预设的默认响应
  • 延迟队列:将请求放入队列稍后处理
  • 静态兜底:返回静态页面/响应

监控与告警

// 限流降级监控
public function trackMetrics($metric) {
    $monitor->increment('rate_limit.blocks');
    $monitor->increment('circuit_breaker.opens');
}

配置化

$config = [
    'rate_limits' => [
        'public_api' => ['capacity' => 100, 'rate' => 10],
        'internal_api' => ['capacity' => 1000, 'rate' => 100],
    ],
    'circuit_breaker' => [
        'failure_threshold' => 5,
        'timeout_window' => 30,
        'half_open_timeout' => 10,
    ]
];

降级策略组合

switch ($degradeLevel) {
    case 1: // 轻度降级
        return $this->cacheOnly();
    case 2: // 中度降级
        return $this->defaultResponse();
    case 3: // 重度降级
        return $this->simplePage();
    case 4: // 完全熔断
        throw new ServiceUnavailableException();
}

注意事项

  1. 原子性:限流操作要保证原子性(建议使用 Lua 脚本或 Redis 事务)
  2. 性能:避免在每个请求中做过于复杂的限流计算
  3. 分布式一致性:多实例部署时需要考虑分布式限流
  4. 降级隔离:不同服务的降级策略要相互独立
  5. 监控:需要完善的日志和监控系统
  6. 自动恢复:降级后要有自动恢复机制

选择方案时,根据你的实际需求(QPS、并发量、业务复杂度)来选择合适的实现方式,对于小型应用,简单的 Redis 限流 + 熔断即可;对于大型应用,建议使用专业的限流组件或 API 网关。

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