redis滑动窗口限流需用lua脚本保证原子性,基于zset维护时间戳并清理过期数据,避免incr+expire竞态;ip限流须校验可信代理头,优先使用user_id;nginx层limit_req更高效,php层适合细粒度策略。

用 Redis 实现滑动窗口限流最实用
直接上生产环境能扛住每秒几百请求的方案,不是靠 PHP 自己计数,而是借 Redis 的原子操作和过期机制。关键在于避免用 INCR + EXPIRE 两步走——中间可能被并发打断,必须用 Lua 脚本保证原子性。
常见错误是把时间窗口设成固定整点(比如每分钟从 :00 开始),导致流量在窗口切换时突增一倍。正确做法是滑动窗口:每次请求都检查「过去 N 秒内」的请求数,不依赖绝对时间点。
- 用
EVAL执行 Lua 脚本,传入 key、窗口秒数、最大请求数三个参数 - 脚本里用
zremrangebyscore清理过期时间戳,再用zcard统计当前窗口请求数 - 如果未超限,用
zadd写入当前毫秒时间戳,并设 key 过期时间为窗口长度 + 1 秒(防残留) - PHP 中推荐用
Predis客户端,它对 Lua 脚本支持稳定,比原生phpredis的eval方法更少出错
$_SERVER['REMOTE_ADDR'] 做 IP 限流要小心代理
直接拿 $_SERVER['REMOTE_ADDR'] 当用户标识,在有 CDN 或 Nginx 反向代理的环境下会全部变成 127.0.0.1 或网关 IP。必须配合 X-Forwarded-For 或 X-Real-IP 头,但不能无条件信任——这些头可被客户端伪造。
安全做法是只信任你可控的上游代理 IP 段。例如 Nginx 在 proxy_set_header X-Real-IP $remote_addr; 前加了 set_real_ip_from 10.0.0.0/8;,那 PHP 里就得先判断 $_SERVER['REMOTE_ADDR'] 是否在该段内,再取 $_SERVER['HTTP_X_REAL_IP']。
立即学习“PHP免费学习笔记(深入)”;
- 永远不要用
$_SERVER['HTTP_X_FORWARDED_FOR']的第一个值,它可能被拼接多个 IP,要 split 后取最后一个可信段 - 如果业务允许,优先用登录态
user_id代替 IP 限流,更精准且防绕过 - 注意 IPv6 地址格式(含冒号和缩写),用
inet_pton()和inet_ntop()标准化后再做 key 拼接
token bucket 算法在 PHP 里怎么落地
令牌桶适合需要平滑放行、允许短时突发的场景(比如 API 调用配额)。难点不在算法逻辑,而在“桶”状态的跨请求持久化——不能存在 PHP 进程内存里,得存 Redis 或数据库。
核心字段就两个:当前令牌数 tokens、上次补充时间 last_refill。每次请求先算出到当前时刻该补多少令牌(rate × elapsed),再更新 tokens,最后判断是否 ≥1。
- 用
HGETALL读桶状态,用HSET+EXPIRE写回,但要注意竞态:两个请求同时读到旧tokens,各自计算后写回,会丢失一次补充 - 解决方法是改用 Lua 脚本,把读、算、写三步锁死;或者直接用
Redis的INCRBYFLOAT配合GETSET模拟 CAS - 桶容量别设太大(比如 >1000),否则 Redis 中浮点精度误差会导致令牌数异常
Apache / Nginx 层限流比 PHP 更早生效
PHP 层限流本质是“请求已进应用”,CPU 和内存已被占用。真正要挡在最外层,得靠 Web 服务器配置。Nginx 的 limit_req 模块比 PHP 方案快一个数量级,且不依赖应用代码。
典型配置里容易踩的坑是没配 burst 和 nodelay:只写 limit_req zone=api burst=5; 会让超出的请求排队,等前面处理完才放行,实际延迟飙升;加 nodelay 才是严格限速,超了直接 503。
- key 用
$binary_remote_addr而不是$remote_addr,节省 Redis 内存(IPv4 固定 4 字节,IPv6 固定 16 字节) - Apache 用户可用
mod_ratelimit,但功能弱于 Nginx,仅支持响应体限速,不适用于请求频率控制 - 注意 Nginx 共享内存区大小:
limit_req_zone的zone=api:10m最多存约 16 万个 IP,高并发下不够用会静默失效
限流不是加个 if 判断就完事,关键在状态一致性、边界识别和层级协作。越靠近客户端的拦截越有效,但越难做细粒度策略;PHP 层适合按用户身份、接口类型做差异化控制,别让它承担本该由基础设施干的活。











