throttler是ci4内置的轻量级限流工具,基于redis或文件存储,适用于登录、密码重置等低频敏感操作的软防护,不适用于高频接口或替代nginx限流。

CodeIgniter 4.7.0 自带的 Throttler 类能做基础请求频率控制,但**不能替代 Nginx 的 ngx_http_limit_req_module**——它只在 PHP 层拦截,对恶意连接、CC 攻击或高并发刷接口基本无效。
Throttler 是什么,适合什么场景
Throttler 是 CI4 内置的轻量级限流工具,基于 Redis 或文件存储计数器,主要用于:登录页、密码重置、短信发送等低频敏感操作的“软防护”。它不阻断 TCP 连接,也不影响静态资源或路由解析,只在控制器逻辑执行前做一次判断。
常见误用是把它当防火墙用——比如给整个 /api/ 加 throttle:60,1,结果压测时 CPU 暴涨、请求排队,而真实攻击流量早把 PHP 进程拖垮了。
- 适用:单次操作需人工干预的路径(如
/auth/login、/auth/forgot) - 不适用:高频接口(搜索、列表)、文件下载、WebSocket 握手
- 性能影响:启用 Redis 后延迟约 2–5ms/次;用 file 驱动在并发 >50 时可能出现计数偏差
怎么在控制器里正确使用 Throttler
CI4.7.0 默认已注册 Throttler 服务,无需手动加载。关键在于调用时机和参数粒度:
- 必须在
beforeFilter()或方法开头调用,不能放在业务逻辑后 - 限流键要带用户上下文,例如用
$this->request->getIPAddress()+ 路由名,避免全站共用一个桶 - 不要写死
throttle:10,1这种硬编码,应从配置或环境变量读取
示例(登录控制器):
public function login()
{
$ip = $this->request->getIPAddress();
$key = 'login:'.$ip;
if (! $this->throttler->check($key, 3, MINUTE)) {
return $this->fail('请求过于频繁,请 '.(int)$this->throttler->getRemainingTime($key).' 秒后再试');
}
// 后续验证逻辑...
}
注意:check() 返回 false 表示超限,此时 getRemainingTime() 才有效;若先调 getRemainingTime() 再 check,会返回 0。
Throttler 和 Nginx 限流怎么配合
真正防刷必须分层:Nginx 做第一道防线(连接数 + 请求速率),Throttler 做第二道(业务语义判断)。两者键值不能冲突,否则会重复拦截或漏判。
- Nginx 对
/api/login用limit_req zone=login_limit burst=5 nodelay,限制每秒 1 次 - PHP 层
Throttler用更细粒度:同一 IP 5 分钟内最多 3 次失败登录,成功后重置计数器 - 错误码统一:Nginx 超限时返回
503,Throttler超限返回429,前端可区分处理 - 共享存储:若用 Redis,确保 Nginx 的
redis2模块和 CI4 的RedisHandler连同一实例,键前缀要错开(如 Nginx 用nginx:login:,PHP 用ci:login:)
容易踩的坑:file 驱动 + 多服务器 + 时间不同步
默认 Throttler 使用 file 驱动时,所有计数器写在 Writable/Throttler/ 下。这在单机开发没问题,但一上生产就出问题:
- 多台 PHP 服务器共享 NFS?文件锁竞争会导致计数不准,实际放行量可能是理论值的 2–3 倍
- 服务器时间差 >1s?
filemtime()判断过期失效,出现“明明刚限流,又立刻能发请求” - 没清理旧文件?
Writables/Throttler/目录积累百万个 timestamp 文件,glob()扫描变慢,拖慢整个请求
结论:生产环境必须切 Redis,且配置 app/Config/Throttler.php 中的 $driver = 'redis',别依赖默认值。











