Laravel 缓存层次与 Redis
缓存是提升 Laravel 应用性能最直接的手段。从路由缓存(减少启动开销)到模型数据缓存(避免重复数据库查询),再到分布式 Session,Redis 是连接这些缓存层的核心。
缓存 Driver 对比
| Driver | 特点 | 适用场景 |
|---|---|---|
file | 缓存到文件系统 | 开发环境、单机部署 |
redis | Redis 内存缓存 | 生产推荐,分布式、高性能 |
memcached | Memcached 缓存 | 遗留系统 |
database | 缓存到数据库 | 无 Redis 时的替代 |
array | 只在当前请求内有效 | 测试环境 |
dynamodb | AWS DynamoDB | 云原生 |
# .env
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null
基础缓存操作
use Illuminate\Support\Facades\Cache;
// 存储(永久)
Cache::forever('site_settings', ['theme' => 'dark']);
// 存储(有效期)
Cache::put('user_stats_1', $stats, now()->addHours(1));
Cache::put('leaderboard', $data, 3600); // 秒数
// 获取
$stats = Cache::get('user_stats_1');
$stats = Cache::get('user_stats_1', fn() => 'default'); // 缺省值(懒加载)
// 记住(最常用模式:如果有就用缓存,没有就计算并缓存)
$stats = Cache::remember('user_stats_1', 3600, function () {
return Task::where('user_id', 1)->selectRaw('
COUNT(*) as total,
SUM(CASE WHEN status = "done" THEN 1 ELSE 0 END) as completed
')->first();
});
// 永久记住
$settings = Cache::rememberForever('app_settings', fn() => Settings::all());
// 检查
Cache::has('user_stats_1');
Cache::missing('user_stats_1');
// 删除
Cache::forget('user_stats_1');
// 删除多个
Cache::deleteMultiple(['key1', 'key2', 'key3']);
// 清空全部(谨慎!)
Cache::flush();
缓存键命名规范
// 推荐:命名空间:标识符
'tasks:user:42' // 用户 42 的任务列表
'tasks:stats:user:42' // 用户 42 的任务统计
'project:1:member_ids' // 项目 1 的成员 ID 列表
'leaderboard:weekly' // 每周排行榜
// 使用常量类统一管理
class CacheKeys
{
public static function userStats(int $userId): string
{
return "tasks:stats:user:{$userId}";
}
public static function projectMembers(int $projectId): string
{
return "project:{$projectId}:member_ids";
}
}
// 使用
Cache::remember(CacheKeys::userStats($userId), 3600, fn() => /* ... */);
Cache::forget(CacheKeys::userStats($userId));
原子锁(防止缓存击穿)
use Illuminate\Support\Facades\Cache;
// 问题:多个请求同时发现缓存不存在,并发查询数据库(缓存击穿)
// 解决:原子锁确保只有一个请求重建缓存
$value = Cache::remember('expensive_key', 3600, function () {
// 这里不能防止缓存击穿!多个请求会并发执行
$lock = Cache::lock('expensive_key_lock', 10); // 10秒锁
if ($lock->get()) {
// 获取了锁:重建缓存
$data = DB::table('...')->get();
$lock->release();
return $data;
} else {
// 没获取锁:等一下再查缓存
sleep(1);
return Cache::get('expensive_key');
}
});
// 更简洁的 block() 方法(等待直到获取锁)
Cache::lock('generate_report')->block(30, function () {
// 最多等待 30 秒,获取锁后执行
GenerateWeeklyReport::dispatch();
});
在模型中使用缓存
// app/Models/User.php
public function getTaskStatsAttribute(): array
{
return Cache::remember(
CacheKeys::userStats($this->id),
now()->addMinutes(30),
fn() => [
'total' => $this->tasks()->count(),
'pending' => $this->tasks()->pending()->count(),
'done' => $this->tasks()->done()->count(),
'overdue' => $this->tasks()->overdue()->count(),
]
);
}
// 数据变化时清除缓存(在 Observer 中)
// app/Observers/TaskObserver.php
public function saved(Task $task): void
{
Cache::forget(CacheKeys::userStats($task->user_id));
}
public function deleted(Task $task): void
{
Cache::forget(CacheKeys::userStats($task->user_id));
}
Redis 直接操作(超出 Cache 抽象时)
use Illuminate\Support\Facades\Redis;
// 原子递增计数器(如阅读量)
Redis::incr("task:{$taskId}:views");
Redis::incrby("task:{$taskId}:views", 10);
// 有序集合(排行榜)
Redis::zadd('leaderboard', $score, "user:{$userId}");
$top10 = Redis::zrevrange('leaderboard', 0, 9, 'WITHSCORES');
// 列表(简单队列)
Redis::lpush("notifications:{$userId}", json_encode($notification));
$latest = Redis::lrange("notifications:{$userId}", 0, 19); // 最新20条
// 设置过期时间
Redis::expire("task:{$taskId}:views", 86400); // 24小时后过期
// Redis 事务(批量操作保证原子性)
Redis::pipeline(function ($pipe) use ($userId) {
$pipe->incr("user:{$userId}:task_count");
$pipe->incr("daily:task_count:" . date('Y-m-d'));
$pipe->expire("user:{$userId}:task_count", 86400);
});
下一节:路由缓存、配置缓存、视图缓存——除了数据缓存,Laravel 还有框架级别的缓存命令:
route:cache、config:cache、view:cache——生产部署必须执行这些命令。