滑动窗口算法通过时间切片和实时计数实现更平滑精准的接口限流,PHP中推荐用Redis有序集合(ZSET)存储毫秒级时间戳并动态清理过期数据,配合ZCARD统计与阈值比较。

滑动窗口算法是实现接口限流的常用方法,相比固定窗口更平滑、更精准。PHP 中可以通过 Redis(推荐)或内存(仅单机调试)来存储窗口内请求时间戳,动态计算当前窗口请求数是否超限。
核心思路:用时间窗口切片 + 实时计数
将一个完整的时间周期(如 60 秒)划分为多个小窗口(如每秒 1 个 slot),记录每个小窗口内的请求数。判断限流时,只累加“当前时间往前推周期长度”范围内的所有小窗口计数。这样避免了固定窗口在边界处的突增问题。
Redis 实现滑动窗口(生产推荐)
利用 Redis 的有序集合(ZSET)天然支持按时间排序和范围查询的特性:
- 每次请求,以当前毫秒时间戳为 score,唯一请求标识(如 ip:uri:timestamp)为 member,执行 ZADD key score member
- 用 ZREMRANGEBYSCORE key 0 (current_timestamp - window_ms) 清理过期数据
- 用 ZCARD key 获取当前窗口内请求数,与阈值比较
- 为防 key 持续膨胀,可配合设置过期时间(EXPIRE key window_ms + 1000)
简易 PHP 示例(基于 Redis)
注意:需安装 phpredis 扩展,且 Redis 版本 ≥ 6.2(ZREMRANGEBYSCORE 支持开区间)
立即学习“PHP免费学习笔记(深入)”;
class SlidingWindowLimiter
{
private $redis;
private $keyPrefix = 'rate_limit:';
<pre class='brush:php;toolbar:false;'>public function __construct($redis)
{
$this->redis = $redis;
}
public function isAllowed(string $identifier, int $maxRequests, int $windowSeconds): bool
{
$key = $this->keyPrefix . $identifier;
$nowMs = round(microtime(true) * 1000);
$windowMs = $windowSeconds * 1000;
$minScore = $nowMs - $windowMs;
// 清理过期请求(开区间,不包含 minScore)
$this->redis->zRemRangeByScore($key, '-inf', '(' . $minScore);
// 获取当前窗口请求数
$count = $this->redis->zCard($key);
if ($count >= $maxRequests) {
return false;
}
// 记录本次请求
$this->redis->zAdd($key, $nowMs, uniqid($identifier . ':'));
// 设置自动过期(稍长于窗口,留缓冲)
$this->redis->expire($key, $windowSeconds + 5);
return true;
}}
// 使用示例 $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $limiter = new SlidingWindowLimiter($redis);
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if (!$limiter->isAllowed("{$clientIp}:{$uri}", 100, 60)) { http_response_code(429); echo json_encode(['error' => 'Too many requests']); exit; }
注意事项与优化点
- 标识符要合理设计:建议组合 客户端 IP + 接口路径 +(可选)用户 ID,避免误限或绕过
- 时间精度建议用毫秒:防止同一秒内多个请求被当作一个处理
- Redis 连接需复用,避免频繁创建连接影响性能
- 高并发下可考虑用 Lua 脚本封装整个逻辑,保证原子性
- 若无 Redis,可用 APCu 或 Swoole Table 替代,但仅限单机部署且需自行处理过期和并发安全



















