限流和熔断不是一回事;laravel限流用throttle中间件或ratelimiter,熔断需自行实现,因throttle只控频次、不感知服务健康,且依赖缓存驱动,redis宕机时会抛异常而非降级,无法替代熔断。

直接说结论:限流和熔断不是一回事,不能混用;Laravel 里限流靠 throttle 中间件或 RateLimiter,熔断必须自己封装或引入第三方逻辑,官方不提供开箱即用的熔断器。
为什么 throttle 中间件不能当熔断用
很多人看到 throttle:60,1 就以为“超了就拒绝”,误以为这能防 Redis 挂掉后的雪崩。其实它只管请求频次,不管后端服务健康状态。Redis 连不上时,throttle 会直接抛出 ConnectionException,整个请求失败,而不是降级或跳过缓存——这反而加剧了数据库压力。
-
throttle的底层是读写缓存计数器,依赖Cache::store(),一旦缓存驱动异常(比如 Redis 宕机),就会中断流程 - 它没有状态机(Closed/Open/Half-Open),也不记录失败次数、不自动切换策略
- 错误日志里常出现
Redis connection refused,但请求仍继续打向数据库,形成缓存击穿+DB 扛压双重问题
用 RateLimiter::attempt 实现带兜底的限流
如果你需要在限流同时保留降级能力(比如 Redis 不可用时查 DB),就不能只靠中间件,得把限流逻辑下沉到业务层,配合 try/catch 控制流向。
- 键名必须稳定且唯一,推荐组合
"rate:{$request->header('X-API-Key') ?: $request->ip()}",避免 IP 共享场景误杀 - 必须确保
CACHE_DRIVER=redis,array驱动在多 PHP-FPM 进程下完全失效 - 调用时包裹异常捕获:
try { if (! RateLimiter::attempt($key, 10, function () { return true; })) { return response(['error' => 'Too many requests'], 429); } } catch (\Illuminate\Contracts\Cache\LockTimeoutException $e) { // 缓存锁超时 → 可能 Redis 延迟高,走降级 return $this->fallbackFromDatabase(); } catch (\Predis\Connection\ConnectionException $e) { // Redis 连接失败 → 熔断入口 return $this->fallbackFromDatabase(); }
手动实现缓存熔断器的最小可行结构
不需要重造轮子,一个轻量 CircuitBreaker 类就能解决核心问题:识别连续失败、阻断后续请求、定时试探恢复。
- 状态存储不能放 Redis(否则熔断器自己也依赖 Redis),建议用
apcu或file驱动存本地状态,例如键名为circuit:cache:redis - 关键参数要可配:
failure_threshold(默认 5)、reset_timeout(默认 60 秒)、half_open_sample(半开时只放行 10% 请求) - 每次
Cache::get()前先调$breaker->allow('cache:redis');成功则recordSuccess(),失败则recordFailure() - 注意:
allow()返回false时,必须跳过所有缓存操作,包括Cache::put(),否则可能把脏数据写进本地缓存
最容易被忽略的点:熔断 ≠ 关闭缓存
很多团队上线熔断后发现接口变慢,查下来是因为降级逻辑里没控制 DB 查询复杂度,或者 fallback 返回的是全量模型(含 N+1 关系)。熔断只是切换路径,不是性能银弹。
真正关键的是:降级响应必须比原路径更快、更轻。比如缓存失效时返回预生成的静态 JSON 片段,而不是现场查 5 张表再拼数组。另外,Cache::store('redis') 和 Cache::store('array') 在同一请求中混用要格外小心——它们共享同一个 Repository 实例,状态可能互相污染。











