必须用Lua脚本实现原子化限流,因INCR+EXPIRE两步操作非原子:incr成功但expire失败会导致key永久残留,并发请求还可能超限;Lua在Redis单线程中一次性完成读值、判断、更新、设过期,彻底规避竞态。

Redis + Lua 实现原子化限流为什么必须用 Lua
PHP 单机限流在分布式环境下会失效,因为多个 PHP 进程无法共享内存状态。直接用 incr + expire 两步操作不是原子的:若 incr 成功但 expire 失败,key 永久存在;若 key 已过期但并发请求同时触发 incr,可能超限。Lua 脚本在 Redis 中以原子方式执行,能一次完成「读当前值→判断→更新→设过期」整套逻辑。
常见错误现象:rate_limit:uid:123 的计数偶尔跳变、限流阈值形同虚设、压测时瞬时请求数翻倍。
- 固定窗口场景推荐用以下 Lua 脚本(存为
fixed_window.lua):
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call("INCR", key)
if current == 1 then
redis.call("EXPIRE", key, window)
end
return current <= limit
- PHP 调用示例:
$redis->eval($luaScript, 1, "rate_limit:ip:".$ip, 100, 60) - 注意:不要在脚本里用
redis.call("TIME"),它返回的是秒级时间戳,精度不够;改用redis.call("TIME")配合客户端传入毫秒时间戳更稳妥(需业务层控制)
Hyperf 框架内置限流器怎么配才不踩坑
Hyperf 提供了 RateLimit 组件,但默认配置容易忽略两个关键点:一是未绑定到具体服务方法,二是未启用协程安全的存储驱动。它底层依赖 hyperf/cache,若用 FileCache 或 APCu,在多 worker 场景下完全失效。
使用场景:适用于单服务内高频接口(如登录、短信发送),不适用于跨服务或全局维度限流。
立即学习“PHP免费学习笔记(深入)”;
- 必须显式在注解中指定限流策略:
@RateLimit(limit=10, period=60),否则注解不生效 - 缓存驱动必须设为
redis,并在config/autoload/cache.php中确认'default' => 'redis' - 若用自定义
RateLimiterInterface实现,isAllowed()方法必须是协程安全的——避免调用阻塞 IO(如 file_get_contents) - Hyperf 3.0+ 默认开启
enableCoroutine,但某些旧版中间件仍可能触发同步 Redis 客户端,导致协程挂起
PHP + Swoole 微服务中熔断与限流怎么协同
限流管「进」,熔断管「出」。只做限流,当下游服务已不可用时,PHP 进程还在不断发起 HTTP 请求,浪费连接和超时等待;只做熔断,上游突发流量仍可能打垮本服务自身。两者必须分层配合。
典型错误:把 phystrix 的 fallbackMethod 当作限流兜底,结果 fallback 本身也被打爆。
- 第一层:API 网关(如 Kong/Nginx)做粗粒度限流(IP/路径级),拦截 80% 无效流量
- 第二层:Swoole Worker 内用
RateLimit或自研令牌桶控制单实例吞吐 - 第三层:对下游 HTTP 服务调用(如 Guzzle)启用熔断,使用
Swoole\CircuitBreaker或封装hyperf/circuit-breaker,失败率 >50% 且持续 30 秒则打开熔断器 - 关键细节:熔断器打开后,必须拒绝新请求并快速返回(如 429 或 503),而不是排队等重试;否则队列堆积导致内存溢出
为什么 Nginx 的 limit_req 不足以替代 PHP 层限流
limit_req 是前置防护,但它无法识别业务上下文。比如「同一用户每分钟最多发 5 条评论」,Nginx 只能按 IP 或 $uri 限,而真实业务规则往往绑定用户 ID、设备指纹、Token 声明等,这些只有 PHP 解析完请求体才能拿到。
性能影响被高估:一次 Redis Lua 调用耗时通常
- 别在
limit_req里用$binary_remote_addr当唯一标识——IPv6 地址长度不固定,且代理层层转发后失真 - 若必须用 Nginx 限流,配合
map指令提取 Header 中的X-User-ID,再传给limit_req_zone,但要注意 Nginx 不支持动态 TTL - 最稳妥的组合:Nginx 做 IP 级突发保护(
burst=10 nodelay),PHP 层做用户/业务级精准限流
复杂点在于状态一致性——Redis 故障时,限流器该降级还是拒绝?多数团队忽略这个分支,直接让整个服务不可用。建议所有限流逻辑包裹 try/catch,捕获 RedisException 后走宽松策略(如允许通过,但记录告警),而不是抛 500。



















