Laravel 13 缓存策略完全指南

缓存是提升应用性能的关键手段。本文将深入探讨 Laravel 13 中各种缓存策略的实现和最佳实践。

缓存配置

缓存驱动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// config/cache.php
return [
'default' => env('CACHE_STORE', 'database'),

'stores' => [
'apc' => [
'driver' => 'apc',
],

'array' => [
'driver' => 'array',
'serialize' => false,
],

'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],

'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],

'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],

'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],

'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
],

'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

缓存基础操作

存取缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Illuminate\Support\Facades\Cache;

// 存储缓存
Cache::put('key', 'value', $seconds);
Cache::put('key', 'value', now()->addHours(1));
Cache::put('key', 'value'); // 永久存储

// 获取缓存
$value = Cache::get('key');
$value = Cache::get('key', 'default');
$value = Cache::get('key', function () {
return 'computed value';
});

// 检查存在
if (Cache::has('key')) {
// 存在
}

if (Cache::missing('key')) {
// 不存在
}

原子操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 增加值
Cache::increment('counter');
Cache::increment('counter', 5);

// 减少值
Cache::decrement('counter');
Cache::decrement('counter', 3);

// 原子添加(不存在时才添加)
Cache::add('key', 'value', $seconds);

// 永久存储
Cache::forever('key', 'value');

// 删除缓存
Cache::forget('key');
Cache::flush();

获取并存储

1
2
3
4
5
6
7
8
9
10
11
$value = Cache::remember('users', 3600, function () {
return User::all();
});

$value = Cache::rememberForever('settings', function () {
return Setting::all()->pluck('value', 'key');
});

$value = Cache::sear('key', function () {
return 'value'; // 永久存储
});

缓存标签

标签操作

1
2
3
4
5
6
7
8
9
10
// 存储带标签的缓存
Cache::tags(['people', 'authors'])->put('author:1', $author, 3600);
Cache::tags(['people', 'artists'])->put('artist:1', $artist, 3600);

// 获取带标签的缓存
$author = Cache::tags(['people', 'authors'])->get('author:1');

// 清除标签下的所有缓存
Cache::tags(['people'])->flush();
Cache::tags(['authors', 'artists'])->flush();

标签缓存服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use App\Models\Post;

class PostCacheService
{
protected string $tag = 'posts';

public function getPost(int $id): ?Post
{
return Cache::tags([$this->tag])
->remember("post.{$id}", 3600, fn() => Post::find($id));
}

public function getRecentPosts(int $limit = 10): iterable
{
return Cache::tags([$this->tag])
->remember("recent.{$limit}", 1800, fn() => Post::latest()->limit($limit)->get());
}

public function clearPostCache(int $id): void
{
Cache::tags([$this->tag])->forget("post.{$id}");
}

public function clearAllCache(): void
{
Cache::tags([$this->tag])->flush();
}
}

缓存策略模式

Cache-Aside 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;

class CacheAsideService
{
public function getData(string $key, callable $loader, int $ttl = 3600): mixed
{
$data = Cache::get($key);

if ($data === null) {
$data = $loader();
Cache::put($key, $data, $ttl);
}

return $data;
}

public function updateData(string $key, mixed $data, int $ttl = 3600): void
{
Cache::put($key, $data, $ttl);
}

public function invalidate(string $key): void
{
Cache::forget($key);
}
}

Read-Through 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Services\Cache;

use Illuminate\Support\Facades\Cache;
use App\Contracts\CacheRepository;

class ReadThroughCache
{
public function __construct(
protected CacheRepository $repository
) {}

public function get(string $key, int $ttl = 3600): mixed
{
return Cache::remember($key, $ttl, function () use ($key) {
return $this->repository->find($key);
});
}

public function put(string $key, mixed $value, int $ttl = 3600): void
{
$this->repository->save($key, $value);
Cache::put($key, $value, $ttl);
}

public function forget(string $key): void
{
$this->repository->delete($key);
Cache::forget($key);
}
}

Write-Through 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Services\Cache;

use Illuminate\Support\Facades\Cache;

class WriteThroughCache
{
public function __construct(
protected $repository
) {}

public function put(string $key, mixed $value, int $ttl = 3600): bool
{
$this->repository->save($key, $value);
return Cache::put($key, $value, $ttl);
}

public function forget(string $key): bool
{
$this->repository->delete($key);
return Cache::forget($key);
}
}

Write-Behind 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

namespace App\Services\Cache;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
use App\Jobs\SyncCacheToDatabase;

class WriteBehindCache
{
protected array $buffer = [];
protected int $bufferSize = 100;

public function put(string $key, mixed $value, int $ttl = 3600): bool
{
Cache::put($key, $value, $ttl);

$this->buffer[$key] = $value;

if (count($this->buffer) >= $this->bufferSize) {
$this->flush();
}

return true;
}

protected function flush(): void
{
if (empty($this->buffer)) {
return;
}

Queue::push(new SyncCacheToDatabase($this->buffer));
$this->buffer = [];
}
}

模型缓存

自动缓存 Trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

namespace App\Traits;

use Illuminate\Support\Facades\Cache;

trait Cacheable
{
protected static function bootCacheable(): void
{
static::saved(function ($model) {
$model->clearModelCache();
});

static::deleted(function ($model) {
$model->clearModelCache();
});
}

public static function findCached($id): ?static
{
$key = static::getCacheKey($id);

return Cache::remember($key, static::getCacheTtl(), function () use ($id) {
return static::find($id);
});
}

public function clearModelCache(): void
{
Cache::forget(static::getCacheKey($this->id));
Cache::tags([static::getCacheTag()])->flush();
}

public static function getCacheKey($id): string
{
return static::getCacheTag() . '.' . $id;
}

public static function getCacheTag(): string
{
return static::class;
}

public static function getCacheTtl(): int
{
return 3600;
}
}

使用缓存 Trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace App\Models;

use App\Traits\Cacheable;

class User extends Model
{
use Cacheable;

protected static function getCacheTtl(): int
{
return 7200;
}
}

// 使用
$user = User::findCached(1);

查询缓存

缓存查询结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use App\Models\User;

class UserQueryService
{
public function getActiveUsers(): iterable
{
return Cache::remember('users.active', 3600, function () {
return User::where('active', true)
->orderBy('name')
->get();
});
}

public function searchUsers(string $query, int $page = 1): array
{
$key = "users.search.{$query}.{$page}";

return Cache::remember($key, 1800, function () use ($query, $page) {
return User::where('name', 'like', "%{$query}%")
->paginate(15, ['*'], 'page', $page)
->toArray();
});
}

public function getUserStatistics(): array
{
return Cache::remember('users.statistics', 300, function () {
return [
'total' => User::count(),
'active' => User::where('active', true)->count(),
'new_this_month' => User::whereMonth('created_at', now()->month)->count(),
];
});
}
}

响应缓存

HTTP 响应缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;

class CacheResponse
{
public function handle($request, Closure $next, int $ttl = 3600)
{
if (!$request->isMethod('GET')) {
return $next($request);
}

$key = $this->getCacheKey($request);

if (Cache::has($key)) {
return response(Cache::get($key))
->header('X-Cache', 'HIT');
}

$response = $next($request);

if ($response->status() === 200) {
Cache::put($key, $response->getContent(), $ttl);
$response->header('X-Cache', 'MISS');
}

return $response;
}

protected function getCacheKey($request): string
{
return 'response:' . md5($request->fullUrl());
}
}

缓存预热

预热命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use App\Models\User;
use App\Models\Post;
use App\Models\Category;

class WarmCache extends Command
{
protected $signature = 'cache:warm';
protected $description = '预热应用缓存';

public function handle(): int
{
$this->info('开始预热缓存...');

$this->warmUsers();
$this->warmPosts();
$this->warmCategories();
$this->warmStatistics();

$this->info('缓存预热完成!');

return self::SUCCESS;
}

protected function warmUsers(): void
{
$this->info('预热用户缓存...');

User::chunk(100, function ($users) {
foreach ($users as $user) {
Cache::remember("user.{$user->id}", 3600, fn() => $user);
}
});
}

protected function warmPosts(): void
{
$this->info('预热文章缓存...');

$posts = Post::with(['author', 'category', 'tags'])
->published()
->latest()
->limit(100)
->get();

Cache::put('posts.recent', $posts, 1800);
}

protected function warmCategories(): void
{
$this->info('预热分类缓存...');

$categories = Category::withCount('posts')->get();
Cache::put('categories.all', $categories, 3600);
}

protected function warmStatistics(): void
{
$this->info('预热统计缓存...');

Cache::remember('stats.dashboard', 300, function () {
return [
'users_count' => User::count(),
'posts_count' => Post::count(),
'comments_count' => Comment::count(),
];
});
}
}

缓存失效策略

基于事件的失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace App\Listeners;

use Illuminate\Support\Facades\Cache;
use App\Events\PostUpdated;

class InvalidatePostCache
{
public function handle(PostUpdated $event): void
{
$post = $event->post;

Cache::forget("post.{$post->id}");
Cache::forget('posts.recent');
Cache::tags(['posts'])->flush();

if ($post->category) {
Cache::forget("category.{$post->category->id}.posts");
}
}
}

基于时间的失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace App\Services\Cache;

use Illuminate\Support\Facades\Cache;

class TimeBasedCache
{
public function getWithRefresh(string $key, int $ttl, callable $callback, int $refreshBefore = 60): mixed
{
$data = Cache::get($key);

if ($data === null) {
return $this->storeAndReturn($key, $ttl, $callback);
}

$expiresAt = Cache::get("{$key}:expires_at");

if ($expiresAt && now()->addSeconds($refreshBefore)->gte($expiresAt)) {
dispatch(function () use ($key, $ttl, $callback) {
$this->storeAndReturn($key, $ttl, $callback);
});
}

return $data;
}

protected function storeAndReturn(string $key, int $ttl, callable $callback): mixed
{
$data = $callback();
Cache::put($key, $data, $ttl);
Cache::put("{$key}:expires_at", now()->addSeconds($ttl), $ttl);
return $data;
}
}

缓存监控

缓存命中率统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

namespace App\Services\Cache;

use Illuminate\Support\Facades\Cache;

class CacheMetrics
{
protected string $key = 'cache:metrics';

public function hit(string $cacheKey): void
{
$this->increment('hits');
}

public function miss(string $cacheKey): void
{
$this->increment('misses');
}

public function getMetrics(): array
{
return Cache::get($this->key, [
'hits' => 0,
'misses' => 0,
]);
}

public function getHitRate(): float
{
$metrics = $this->getMetrics();
$total = $metrics['hits'] + $metrics['misses'];

return $total > 0 ? round($metrics['hits'] / $total * 100, 2) : 0;
}

protected function increment(string $type): void
{
$metrics = $this->getMetrics();
$metrics[$type]++;
Cache::put($this->key, $metrics);
}
}

总结

Laravel 13 的缓存系统提供了:

  • 多种缓存驱动支持
  • 灵活的缓存标签
  • 多种缓存策略模式
  • 模型自动缓存
  • 查询结果缓存
  • 响应缓存
  • 缓存预热机制
  • 智能失效策略

合理使用缓存可以显著提升应用性能和用户体验。