Laravel 10 数据库缓存优雅降级是在缓存不可用时自动 fallback 到数据库查询,通过异常捕获、健康检查、空值缓存和异步重建四大策略保障一致性与可用性。

Laravel 10 的数据库缓存优雅降级,核心目标是在缓存不可用(如 Redis 宕机、连接超时、序列化失败)时,不中断业务,而是自动 fallback 到数据库查询,并维持数据一致性与响应可用性。这不是简单“报错后查库”,而是有策略、可感知、可恢复的柔性机制。
缓存降级 ≠ 放弃缓存,而是让缓存失效时系统仍稳
关键在于:识别失败时机 + 控制降级边界 + 避免雪崩 + 可观测可恢复
缓存操作层主动捕获异常并降级
Laravel 的 Cache::get() 和 Cache::remember() 默认不抛异常(遇到驱动异常可能返回 null 或触发错误),但生产中需显式兜底。推荐封装一层带降级逻辑的工具方法:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
function getWithFallback($key, $ttl, $callback, $fallbackCallback = null)
{
try {
// 先尝试从缓存读取
$value = Cache::get($key);
if ($value !== null) {
return $value;
}
} catch (\Exception $e) {
\Log::warning('Cache read failed', ['key' => $key, 'error' => $e->getMessage()]);
}
// 缓存未命中或异常 → 执行降级逻辑
$value = $fallbackCallback ? $fallbackCallback() : $callback();
// 尝试写回缓存(仅当缓存服务已恢复)
try {
Cache::put($key, $value, $ttl);
} catch (\Exception $e) {
\Log::debug('Cache write skipped due to driver unavailability', ['key' => $key]);
}
return $value;
}
// 使用示例
$user = getWithFallback(
"user:{$id}",
3600,
fn() => User::findOrFail($id)->toArray(),
fn() => DB::table('users')->where('id', $id)->first()
);✅ 优势:逻辑集中、可统一监控、支持 fallback 延迟加载
⚠️ 注意:避免在 $fallbackCallback 中再次调用 Cache::remember,防止递归降级
利用 Laravel 缓存驱动的“健康检查”做前置判断
不等请求打到缓存再失败,而是在应用启动或中间件中轻量探测缓存连通性:
// 在 App\Http\Middleware\CheckCacheHealth.php
public function handle($request, Closure $next)
{
if (config('cache.default') === 'redis') {
try {
Cache::store('redis')->get('health_check');
} catch (\Exception $e) {
// 标记当前缓存不可用,后续请求跳过缓存层(可通过 Request 属性或全局 flag 控制)
$request->attributes->set('cache_disabled', true);
}
}
return $next($request);
}然后在业务中判断:
if (! $request->attributes->get('cache_disabled')) {
return Cache::remember("posts:{$tag}", 300, fn() => Post::where('tag', $tag)->get());
}
return Post::where('tag', $tag)->get(); // 直接查库✅ 优势:避免大量请求反复触发缓存异常,减少日志噪音和资源浪费
⚠️ 注意:健康检查频率不宜过高(如每分钟一次),建议配合 Cache::store()->ping()(Redis)或轻量 get() 实现
为关键查询配置「空值缓存 + 短 TTL」防穿透式降级
缓存穿透(查不存在的 key)在降级期间更危险——每次穿透都直击数据库。应主动缓存空结果:
$user = Cache::remember("user:{$id}", 60, function () use ($id) {
$user = User::find($id);
if (! $user) {
// 显式缓存 null,防止重复穿透
Cache::put("user:{$id}", null, 30); // 比正常 TTL 更短,快速刷新
return null;
}
return $user;
});✅ 优势:即使缓存降级开启,也能抑制无效请求洪峰
⚠️ 注意:空值缓存时间不宜过长(通常 10–60 秒),避免掩盖真实数据写入延迟
结合队列实现「异步缓存重建」,缓解降级压力
当检测到缓存大规模不可用(如 Redis 故障恢复后),不要让所有请求同步重建缓存,而是:
- 第一个请求走数据库 → 写入缓存
- 后续请求若发现缓存为空,不立即查库,而是:
- 检查是否有重建任务已在队列中(用 Redis SETNX 或数据库锁标记)
- 若无,则 dispatch 一个低优先级重建任务(如
RebuildUserCacheJob::dispatch($id)) - 当前请求直接查库返回(即“本次降级”,但不阻塞)
这样既保障响应,又避免缓存重建风暴。
✅ 优势:平滑过渡,尤其适合热点数据批量失效场景
⚠️ 注意:需配套幂等设计与失败重试机制(如 tries=3 + backoff=60)
不复杂但容易忽略


















