nginx 层限流比 thinkphp 中间件更早、更省资源,但需配合中间件做登录态二次校验;两者是分层防御,非二选一。

直接在 Nginx 层做限流,比在 ThinkPHP 5.1 应用层拦截更早、更省资源;但仅靠 Nginx 无法识别登录态或业务身份,必须配合中间件做二次校验。两者不是二选一,而是分层防御:Nginx 拦异常洪峰,中间件拦绕过 IP 限制的高频合法用户。
为什么不能只用 ThinkPHP 中间件限流
ThinkPHP 5.1 的中间件(如自定义 CorsMiddleware)在请求进入 PHP 运行时才执行,此时已消耗 CPU、内存和 FastCGI 连接。面对 CC 攻击,攻击者可能每秒发起上百请求,PHP 还没开始解析路由就卡死在 fpm 队列里。Nginx 的 limit_req 在事件循环阶段就完成令牌桶判断,不进 PHP 就返回 503,压根不触发框架初始化。
- 中间件无法限制并发连接数,
limit_conn只能在 Nginx 配置 - PHP 层拿到的
$_SERVER['REMOTE_ADDR']可能是 Nginx 的内网地址(未配real_ip_module),导致限流 key 错误 - Redis 计数器在高并发下可能出现竞态,而 Nginx 共享内存区域是原子操作
Nginx 配置限流必须匹配 ThinkPHP 的入口路径
ThinkPHP 5.1 默认所有请求经由 index.php,但 Nginx 的 location ~ \.php$ 块才是 PHP 实际处理位置。若把 limit_req 写在 location / 下,对伪静态路由(如 /user/profile)无效,因为这些请求根本不会命中 PHP location。
- 在
location ~ \.php$ { }块内添加:limit_req zone=tp_cc burst=15 nodelay; -
zone=tp_cc需提前在http块定义:limit_req_zone $binary_remote_addr zone=tp_cc:10m rate=5r/s; - 若使用 CDN 或反向代理,务必加
set_real_ip_from和real_ip_header X-Forwarded-For;,否则$binary_remote_addr是 CDN IP - 避免对静态资源限流,可单独配置
location ~* \.(js|css|png|jpg)$ { limit_req off; }
ThinkPHP 5.1 中间件补漏:按用户身份限流
Nginx 只能按 IP 限,但一个 IP 后可能是几百个真实用户(如公司出口 NAT)。这时需在中间件里基于登录态(如 token、session_id)做二次限流,防止账号被暴力遍历。
- 中间件中获取用户标识:
$uid = session('user_id') ?: request()->header('X-User-ID'); - 用 Redis 记录频次:
$key = 'rate:user:' . md5($uid); $count = Redis::incr($key); Redis::expire($key, 60); - 超过阈值返回 429:
if ($count > 30) { return response('Too many requests', 429); } - 注意:不要在中间件里调用
Db::查询,会拖慢响应;Redis 必须用连接池或持久化连接
burst 和 nodelay 组合的真实影响
burst=15 nodelay 不是“允许 15 次不排队”,而是“允许 15 次突发请求立即通过,第 16 次开始拒绝”。这会导致攻击者用短连接打满 burst,瞬间耗尽令牌桶。生产环境建议:
- 登录、验证码等敏感接口:用
burst=3 nodelay,严格防爆破 - 首页、列表页等公开接口:用
burst=20(无 nodelay),让超额请求排队,消耗攻击者时间 - 监控
limit_req_status日志字段,统计 503 比例;若超 5%,说明 burst 设太小,误伤正常用户
最易忽略的是 Nginx 共享内存大小与实际 IP 数量的匹配——10m 区域最多存约 16 万个 IP 状态,如果日均独立访客超 50 万,会因哈希冲突导致限流失效。这时候得换 zone=tp_cc:32m,或者改用 $server_name:$binary_remote_addr 降低冲突率。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











