本文目录导读:

- 核心原理:滑动窗口 + 动态阈值
- 方案一:基于时间段的静态高峰规则(最推荐,实现简单)
- 方案二:自适应动态阈值(基于实时QPS)
- 方案三:基于等待队列 + 优先级(业务高峰友好型)
- 方案四:全局限流 + 用户分级(终极方案)
- 关键实施建议
针对PHP项目的高峰时段限流,核心思路是动态调整阈值,不能只设置一个固定的“每分钟100次”的规则,而是要在流量高峰(如秒杀、大促、整点)时自动切换为更严格的规则(如每分钟20次或直接拒绝)。
以下是一套完整的高峰加强限制方案,从简单到复杂,分为三个层级:
核心原理:滑动窗口 + 动态阈值
- 滑动窗口:避免固定时间窗口(如整点)的“流量毛刺”问题,使用Redis的有序集合或Lua脚本实现。
- 动态阈值:根据当前流量状态(QPS、CPU、队列深度)决定使用哪套规则。
基于时间段的静态高峰规则(最推荐,实现简单)
适用场景:已知固定高峰时间(如每天 10:00-12:00,20:00-22:00)。
实现步骤:
- 定义多套限流配置:
normal:100次/秒peak:20次/秒
- 判断当前时段:
- 如果在
peak时间范围内,使用严格配置。
- 如果在
- 限流执行:使用Redis + Lua原子计数。
PHP代码示例(使用Redis + 滑动窗口):
class RateLimiter {
private $redis;
private $prefix = 'ratelimit:sliding:';
public function isAllowed($userId, $action = 'api') {
$period = $this->getCurrentPeriod(); // 返回 'peak' 或 'normal'
$rules = $this->getRules($period, $action);
$key = $this->prefix . $userId . ':' . $action;
$now = microtime(true);
$windowStart = $now - $rules['window']; // 1秒
// Lua脚本:移除窗口外数据,统计窗口内请求
$lua = <<<LUA
local key = KEYS[1]
local now = tonumber(ARGV[1])
local windowStart = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
-- 移除过期数据
redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
-- 统计当前窗口请求数
local count = redis.call('ZCARD', key)
-- 判断是否超限
if count >= limit then
return 0 -- 拒绝
end
-- 添加当前请求
redis.call('ZADD', key, now, now .. ':' .. math.random())
-- 设置过期时间,防止内存泄漏
redis.call('EXPIRE', key, math.ceil($rules['window']))
return 1 -- 允许
LUA;
$result = $this->redis->eval($lua, [$key, $now, $windowStart, $rules['limit']], 1);
return $result == 1;
}
private function getCurrentPeriod(): string {
$hour = (int)date('H');
$minute = (int)date('i');
// 定义高峰时段:10:00-12:00 和 20:00-22:00
if (($hour == 10 && $minute >= 0) || ($hour == 11) || ($hour == 12 && $minute == 0)) {
return 'peak';
}
if (($hour == 20 && $minute >= 0) || ($hour == 21) || ($hour == 22 && $minute == 0)) {
return 'peak';
}
return 'normal';
}
private function getRules(string $period, string $action): array {
// 可根据 $action 细分规则
$rules = [
'peak' => ['limit' => 20, 'window' => 1], // 高峰每秒20次
'normal' => ['limit' => 100, 'window' => 1], // 平峰每秒100次
];
return $rules[$period] ?? $rules['normal'];
}
}
// 使用
$limiter = new RateLimiter();
if (!$limiter->isAllowed($userId)) {
http_response_code(429);
die(json_encode(['error' => 'Too Many Requests']));
}
自适应动态阈值(基于实时QPS)
适用场景:高峰时间不确定(如突发流量、营销活动),需要系统自动识别并加强。
核心逻辑:
- 监控当前QPS(每秒请求数)。
- 如果QPS超过预设的警戒线(如80% * 服务器处理能力),则自动将限流阈值降低50%。
- 使用PID控制器或简单比例控制:
current_limit = target_limit * (1 - (current_qps / max_qps - 0.8))
示例逻辑(伪代码):
class AdaptiveRateLimiter {
private function getCurrentQps(): float {
// 从Redis计数器读取最近1秒的请求数
return $this->redis->get('qps_counter');
}
private function getAdjustedLimit(): int {
$baseLimit = 200; // 基础阈值
$maxQps = 250; // 服务最大处理能力(需压测得出)
$currentQps = $this->getCurrentQps();
$usageRate = $currentQps / $maxQps;
if ($usageRate > 1.0) {
return intval($baseLimit * 0.1); // 严重超载,急剧收紧
}
if ($usageRate > 0.8) {
// 超过80%负载,按比例收紧
return intval($baseLimit * (1 - ($usageRate - 0.8) * 2));
}
return $baseLimit; // 正常负载
}
}
注意:此方案需要配合熔断机制,当QPS异常高时,直接拒绝所有请求,保护数据库。
基于等待队列 + 优先级(业务高峰友好型)
适用场景:不希望直接拒绝普通用户,而是让它们排队等待,高峰时延长等待时间。
实现思路:
- 使用Redis List或Sorted Set作为队列。
- 正常时:队列容量100,等待时间<1秒。
- 高峰时:队列容量降低到20,等待时间增加到5秒,超时失败。
// 判断是否需要丢进队列
$queueKey = 'queue:' . $action;
$queueLen = $this->redis->LLEN($queueKey);
// 高峰规则:队列长度超过10就拒绝
$queueLimit = $this->isPeakTime() ? 10 : 100;
if ($queueLen >= $queueLimit) {
// 拒绝或返回重试
return false;
}
// 入队,等待处理
$this->redis->RPUSH($queueKey, $requestId);
// 设置超时时间
$this->redis->EXPIRE($queueKey, $this->isPeakTime() ? 5 : 60);
全局限流 + 用户分级(终极方案)
高峰时,不仅限流,还要按用户优先级分配资源。
- VIP用户:高峰期限流阈值不变(100次/秒)。
- 普通用户:高峰期限流阈值降低到10次/秒。
- 游客:高峰期直接限流到1次/秒或拒绝。
实现:在RateLimiter::isAllowed()中增加$userLevel参数,携带不同的rules数组。
关键实施建议
- 双阈值熔断:设置两个阈值,一个为警告阈值(触发日志、报警),一个为拒绝阈值(触发限流),高峰时段直接使用拒绝阈值。
- 不要在高峰时再去查数据库:限流配置应该缓存到Redis或本地内存(如APCu)。
- 配合Sentinel:如果使用微服务,可以集成Sentinel(阿里开源,有PHP扩展)。
- 削峰填谷:对于异步任务(如发短信、上传图片),使用消息队列(如RabbitMQ)平滑处理,高峰时降低队列消费速率。
- 测试与压测:必须通过JMeter或wrk压测,找到服务器的最大安全QPS和最大安全连接数,以此为基础设置高峰阈值。
- 最简单最可靠:方案一(固定时间切换) + 熔断。
- 需要灵活性:方案一 + 方案二(静态时间+自适应调整)。
- 面向大流量:方案四(用户分级) + 方案三(队列)。
核心原则:永远不要让服务完全崩溃,宁可丢掉请求,不可拖垮数据库。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播