本文目录导读:

在 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();
}
注意事项
- 原子性:限流操作要保证原子性(建议使用 Lua 脚本或 Redis 事务)
- 性能:避免在每个请求中做过于复杂的限流计算
- 分布式一致性:多实例部署时需要考虑分布式限流
- 降级隔离:不同服务的降级策略要相互独立
- 监控:需要完善的日志和监控系统
- 自动恢复:降级后要有自动恢复机制
选择方案时,根据你的实际需求(QPS、并发量、业务复杂度)来选择合适的实现方式,对于小型应用,简单的 Redis 限流 + 熔断即可;对于大型应用,建议使用专业的限流组件或 API 网关。