如何通过Laravel API实现高效限流与防滥用策略?
- 内容介绍
- 文章标签
- 相关推荐
本文共计890个文字,预计阅读时间需要4分钟。
直接输出结果:
throttle:60,1 和 throttle:api 到底怎么选?
别被名字骗了。throttle:60,1 是硬编码策略,每分钟最多 60 次,按 IP 或用户 ID(取决于是否在 auth:api 后)自动分桶;而 throttle:api 是命名策略,实际行为完全由你在 RouteServiceProvider::configureRateLimiting() 里写的 RateLimiter::for('api', ...) 决定。
-
throttle:60,1适合临时加保护、调试快、不依赖额外注册 -
throttle:api必须提前在configureRateLimiting()中定义,否则会 fallback 到默认 60/minute,且日志里查不到 key - 两者都支持
by=ip或by=id参数(Laravel 9+),但by=id要求中间件链中auth:api已执行,否则$request->user()为 null,key 变成throttle:NULL,所有请求挤进同一个桶
RateLimiter::attempt() 总返回 false?检查这三点
这个函数不是“试一下限流”,而是手动触发一次计数 + 判断是否超限。它返回 false 不代表配置失败,大概率是 key 构造错了。
- 第一个参数
$key必须唯一且非空:不能裸用auth()->id(),得写成'rate_limit_'.($request->user()?->id ?? 'guest_'.request()->ip()) - 第三个参数是最大尝试次数,第四个是窗口秒数(不是分钟):漏传第四个参数会默认用 60 秒,但前两个参数不满足时直接返回
false,不会报错 - 如果用了
array缓存驱动,RateLimiter::attempt()在多进程下必然失效——因为 array 驱动不支持原子递增和 TTL,remaining()永远返回 -1
为什么按 IP 限流在上线后全站 429?
常见于 Nginx + Laravel 部署,所有请求的 $request->ip() 都是 127.0.0.1 或负载均衡内网 IP,真实客户端 IP 被压在 X-Forwarded-For 头里,但 Laravel 默认不信任它。
- 必须在
App\Http\Middleware\TrustProxies中配置$proxies,比如['127.0.0.1', '10.0.0.0/8'] - key 构造别只用
$request->ip(),改用$request->header('X-Forwarded-For') ?: $request->ip() - CDN 场景更复杂:Cloudflare 用
Cf-Connecting-Ip,阿里云 SLB 用X-Real-IP,得按实际头名取值,且确保该 header 不被 Nginx 丢弃
缓存驱动踩坑:array、file、redis 的真实表现
开发环境用 array 看起来没问题,一上生产就崩。这不是 bug,是设计如此。
-
array:单进程内存数组,无 TTL、无原子操作,remaining()永远 -1,attempt()多次调用结果不可预测 -
file:文件锁 + 序列化,高并发下容易锁等待,响应延迟抖动大,不适合 API 限流 -
redis:唯一推荐的生产方案。必须确认CACHE_DRIVER=redis,且config/cache.php中stores.redis.connection指向正确 DB(别误配成 session DB)
上线前加一行 Log::info('Cache driver: '.config('cache.default'));,别只信 .env 里的值——config cache 生效后,.env 修改不生效。
本文共计890个文字,预计阅读时间需要4分钟。
直接输出结果:
throttle:60,1 和 throttle:api 到底怎么选?
别被名字骗了。throttle:60,1 是硬编码策略,每分钟最多 60 次,按 IP 或用户 ID(取决于是否在 auth:api 后)自动分桶;而 throttle:api 是命名策略,实际行为完全由你在 RouteServiceProvider::configureRateLimiting() 里写的 RateLimiter::for('api', ...) 决定。
-
throttle:60,1适合临时加保护、调试快、不依赖额外注册 -
throttle:api必须提前在configureRateLimiting()中定义,否则会 fallback 到默认 60/minute,且日志里查不到 key - 两者都支持
by=ip或by=id参数(Laravel 9+),但by=id要求中间件链中auth:api已执行,否则$request->user()为 null,key 变成throttle:NULL,所有请求挤进同一个桶
RateLimiter::attempt() 总返回 false?检查这三点
这个函数不是“试一下限流”,而是手动触发一次计数 + 判断是否超限。它返回 false 不代表配置失败,大概率是 key 构造错了。
- 第一个参数
$key必须唯一且非空:不能裸用auth()->id(),得写成'rate_limit_'.($request->user()?->id ?? 'guest_'.request()->ip()) - 第三个参数是最大尝试次数,第四个是窗口秒数(不是分钟):漏传第四个参数会默认用 60 秒,但前两个参数不满足时直接返回
false,不会报错 - 如果用了
array缓存驱动,RateLimiter::attempt()在多进程下必然失效——因为 array 驱动不支持原子递增和 TTL,remaining()永远返回 -1
为什么按 IP 限流在上线后全站 429?
常见于 Nginx + Laravel 部署,所有请求的 $request->ip() 都是 127.0.0.1 或负载均衡内网 IP,真实客户端 IP 被压在 X-Forwarded-For 头里,但 Laravel 默认不信任它。
- 必须在
App\Http\Middleware\TrustProxies中配置$proxies,比如['127.0.0.1', '10.0.0.0/8'] - key 构造别只用
$request->ip(),改用$request->header('X-Forwarded-For') ?: $request->ip() - CDN 场景更复杂:Cloudflare 用
Cf-Connecting-Ip,阿里云 SLB 用X-Real-IP,得按实际头名取值,且确保该 header 不被 Nginx 丢弃
缓存驱动踩坑:array、file、redis 的真实表现
开发环境用 array 看起来没问题,一上生产就崩。这不是 bug,是设计如此。
-
array:单进程内存数组,无 TTL、无原子操作,remaining()永远 -1,attempt()多次调用结果不可预测 -
file:文件锁 + 序列化,高并发下容易锁等待,响应延迟抖动大,不适合 API 限流 -
redis:唯一推荐的生产方案。必须确认CACHE_DRIVER=redis,且config/cache.php中stores.redis.connection指向正确 DB(别误配成 session DB)
上线前加一行 Log::info('Cache driver: '.config('cache.default'));,别只信 .env 里的值——config cache 生效后,.env 修改不生效。

