跳至内容

缓存

介绍

应用程序执行的某些数据检索或处理任务可能会占用大量 CPU 资源,或者需要几秒钟才能完成。在这种情况下,通常会将检索到的数据缓存一段时间,以便在后续对相同数据的请求中快速检索。缓存的数据通常存储在非常快速的数据存储中,例如MemcachedRedis

值得庆幸的是,Laravel 为各种缓存后端提供了一个富有表现力的统一 API,使您能够利用其极快的数据检索功能并加快您的 Web 应用程序的速度。

配置

您的应用程序的缓存配置文件位于config/cache.php。在此文件中,您可以指定在整个应用程序中默认使用的缓存存储。Laravel 支持常见的缓存后端,例如MemcachedRedisDynamoDB和关系数据库。此外,还提供基于文件的缓存驱动程序,而arraynull缓存驱动程序 为您的自动化测试提供了便捷的缓存后端。

缓存配置文件还包含各种其他选项供您查看。默认情况下,Laravel 配置为使用database缓存驱动程序,该驱动程序将序列化的缓存对象存储在应用程序的数据库中。

驱动程序先决条件

数据库

使用database缓存驱动时,你需要一个数据库表来保存缓存数据。通常,这包含在 Laravel 的默认0001_01_01_000001_create_cache_table.php 数据库迁移中;但是,如果你的应用程序不包含此迁移,你可以使用make:cache-tableArtisan 命令来创建它:

1php artisan make:cache-table
2 
3php artisan migrate

Memcached

使用 Memcached 驱动程序需要安装Memcached PECL 软件包config/cache.php。您可以在配置文件中列出所有 Memcached 服务器。此文件已包含一个memcached.servers条目,可帮助您入门:

1'memcached' => [
2 // ...
3 
4 'servers' => [
5 [
6 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
7 'port' => env('MEMCACHED_PORT', 11211),
8 'weight' => 100,
9 ],
10 ],
11],

如果需要,可以将该host选项设置为 UNIX 套接字路径。如果这样做,则该port选项应设置为0

1'memcached' => [
2 // ...
3 
4 'servers' => [
5 [
6 'host' => '/var/run/memcached/memcached.sock',
7 'port' => 0,
8 'weight' => 100
9 ],
10 ],
11],

Redis

在 Laravel 中使用 Redis 缓存之前,您需要通过 PECL 安装 PhpRedis PHP 扩展,或者predis/predis通过 Composer 安装该扩展包 (~2.0)。Laravel Sail已包含此扩展。此外,Laravel CloudLaravel Forge等官方 Laravel 应用平台已默认安装 PhpRedis 扩展。

有关配置 Redis 的更多信息,请查阅其Laravel 文档页面

DynamoDB

在使用DynamoDB缓存驱动程序之前,您必须创建一个 DynamoDB 表来存储所有缓存数据。通常,此表应命名为cache。但是,您应该根据配置文件stores.dynamodb.table中的配置值来命名表cache。表名也可以通过环境变量设置DYNAMODB_CACHE_TABLE

stores.dynamodb.attributes.key此表还应具有一个字符串分区键,其名称与应用程序配置文件中配置项的值相对应cache。默认情况下,分区键应命名为key

通常,DynamoDB 不会主动从表中移除过期项目。因此,您应该在表上启用生存时间 (TTL)。配置表的 TTL 设置时,应将 TTL 属性名称设置为expires_at

接下来,安装 AWS SDK,以便您的 Laravel 应用程序可以与 DynamoDB 通信:

1composer require aws/aws-sdk-php

此外,您还应确保为 DynamoDB 缓存存储配置选项提供值。通常,这些选项(例如AWS_ACCESS_KEY_ID和)应在应用程序的配置文件AWS_SECRET_ACCESS_KEY中定义:.env

1'dynamodb' => [
2 'driver' => 'dynamodb',
3 'key' => env('AWS_ACCESS_KEY_ID'),
4 'secret' => env('AWS_SECRET_ACCESS_KEY'),
5 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
6 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
7 'endpoint' => env('DYNAMODB_ENDPOINT'),
8],

MongoDB

如果您使用的是 MongoDB,mongodb官方软件包提供了缓存驱动程序mongodb/laravel-mongodb,可以使用mongodb数据库连接进行配置。MongoDB 支持 TTL 索引,可用于自动清除过期的缓存项。

有关配置 MongoDB 的更多信息,请参阅 MongoDB缓存和锁文档

缓存使用情况

获取缓存实例

要获取缓存存储实例,你可以使用CacheFacade,这也是本文档中将一直使用的方法。FacadeCache提供了对 Laravel 缓存契约底层实现的便捷、简洁的访问:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Support\Facades\Cache;
6 
7class UserController extends Controller
8{
9 /**
10 * Show a list of all users of the application.
11 */
12 public function index(): array
13 {
14 $value = Cache::get('key');
15 
16 return [
17 // ...
18 ];
19 }
20}

访问多个缓存存储

使用Cache外观,您可以通过方法访问各种缓存存储store。传递给方法的键应与配置文件中配置数组store中列出的存储之一相对应storescache

1$value = Cache::store('file')->get('foo');
2 
3Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes

从缓存中检索项目

FacadeCacheget方法用于从缓存中检索项目。如果缓存中不存在该项目,null则返回 。如果需要,可以向该get方法传递第二个参数,指定当该项目不存在时希望返回的默认值:

1$value = Cache::get('key');
2 
3$value = Cache::get('key', 'default');

你甚至可以传递一个闭包作为默认值。如果指定的缓存项在缓存中不存在,则返回闭包的结果。传递闭包可以让你延迟从数据库或其他外部服务中检索默认值:

1$value = Cache::get('key', function () {
2 return DB::table(/* ... */)->get();
3});

确定项目存在

has方法可用于判断缓存中是否存在某个项目。false如果缓存中存在某个项目,但其值为null

1if (Cache::has('key')) {
2 // ...
3}

增加/减少值

increment方法decrement可用于调整缓存中整数项的值。这两个方法都接受一个可选的第二个参数,该参数指示要增加或减少项值的量:

1// Initialize the value if it does not exist...
2Cache::add('key', 0, now()->addHours(4));
3 
4// Increment or decrement the value...
5Cache::increment('key');
6Cache::increment('key', $amount);
7Cache::decrement('key');
8Cache::decrement('key', $amount);

检索和存储

有时您可能希望从缓存中检索某个项目,但如果请求的项目不存在,则存储默认值。例如,您可能希望从缓存中检索所有用户,或者如果不存在,则从数据库中检索这些用户并将其添加到缓存中。您可以使用以下Cache::remember方法实现此目的:

1$value = Cache::remember('users', $seconds, function () {
2 return DB::table('users')->get();
3});

如果缓存中不存在该项目,则传递给该remember方法的闭包将被执行,并将其结果放入缓存中。

您可以使用该rememberForever方法从缓存中检索项目,如果不存在则永久存储它:

1$value = Cache::rememberForever('users', function () {
2 return DB::table('users')->get();
3});

重新验证时过期

使用该Cache::remember方法时,如果缓存值已过期,某些用户可能会遇到响应缓慢的情况。对于某些类型的数据,允许在后台重新计算缓存值的同时提供部分过时的数据会很有用,这样可以防止某些用户在计算缓存值时遇到响应缓慢的情况。这通常被称为“重新验证时过期”模式,该Cache::flexible方法提供了此模式的一种实现。

flexible 方法接受一个数组,该数组指定缓存值被视为“新鲜”的时间以及何时变为“陈旧”。数组中的第一个值表示缓存被视为新鲜的秒数,而第二个值定义在需要重新计算之前可以将其作为陈旧数据提供多长时间。

如果在刷新期内(第一个值之前)发出请求,则缓存会立即返回,无需重新计算。如果在过期期内(两个值之间)发出请求,则将过期值提供给用户,并注册一个延迟函数,以便在将响应发送给用户后刷新缓存值。如果在第二个值之后发出请求,则缓存被视为已过期,并立即重新计算该值,这可能会导致用户响应速度变慢:

1$value = Cache::flexible('users', [5, 10], function () {
2 return DB::table('users')->get();
3});

检索和删除

如果需要从缓存中检索某个项目然后删除它,可以使用该pull方法。与该get方法类似,null如果缓存中不存在该项目,则将返回:

1$value = Cache::pull('key');
2 
3$value = Cache::pull('key', 'default');

将项目存储在缓存中

您可以使用外观put上的方法Cache将项目存储在缓存中:

1Cache::put('key', 'value', $seconds = 10);

如果未将存储时间传递给put方法,则该项目将被无限期存储:

1Cache::put('key', 'value');

除了传递整数秒数之外,您还可以传递DateTime表示缓存项目所需过期时间的实例:

1Cache::put('key', 'value', now()->addMinutes(10));

如果不存在则存储

add仅当缓存存储中不存在该项时,该方法才会将其添加到缓存中。true如果该项确实已添加到缓存中,则该方法将返回。否则,该方法将返回false。该add方法是一个原子操作:

1Cache::add('key', 'value', $seconds);

永久存储物品

forever方法可用于将项目永久存储在缓存中。由于这些项目不会过期,因此必须使用该forget方法手动从缓存中删除它们:

1Cache::forever('key', 'value');

如果您使用的是 Memcached 驱动程序,则当缓存达到其大小限制时,“永久”存储的项目可能会被删除。

从缓存中删除项目

您可以使用下列方法从缓存中删除项目forget

1Cache::forget('key');

您还可以通过提供零或负数的到期秒数来删除项目:

1Cache::put('key', 'value', 0);
2 
3Cache::put('key', 'value', -5);

您可以使用下列方法清除整个缓存flush

1Cache::flush();

清除缓存不会遵循您配置的缓存“前缀”,并将从缓存中删除所有条目。清除与其他应用程序共享的缓存时,请仔细考虑这一点。

缓存记忆

Laravel 的memo缓存驱动程序允许您在单个请求或作业执行期间将已解析的缓存值临时存储在内存中。这可以避免在同一次执行中重复命中缓存,从而显著提高性能。

要使用记忆缓存,请调用该memo方法:

1use Illuminate\Support\Facades\Cache;
2 
3$value = Cache::memo()->get('key');

memo方法可选地接受缓存存储的名称,该名称指定记忆驱动程序将装饰的底层缓存存储:

1// Using the default cache store...
2$value = Cache::memo()->get('key');
3 
4// Using the Redis cache store...
5$value = Cache::memo('redis')->get('key');

对给定键的第一次get调用将从缓存存储中检索值,但同一请求或作业中的后续调用将从内存中检索值:

1// Hits the cache...
2$value = Cache::memo()->get('key');
3 
4// Does not hit the cache, returns memoized value...
5$value = Cache::memo()->get('key');

当调用修改缓存值的方法(例如put,,,等)时incrementremember记忆缓存会自动忘记记忆值,并将变异方法调用委托给底层缓存存储:

1Cache::memo()->put('name', 'Taylor'); // Writes to underlying cache...
2Cache::memo()->get('name'); // Hits underlying cache...
3Cache::memo()->get('name'); // Memoized, does not hit cache...
4 
5Cache::memo()->put('name', 'Tim'); // Forgets memoized value, writes new value...
6Cache::memo()->get('name'); // Hits underlying cache again...

缓存Helpers

除了使用Cache外观之外,您还可以使用全局cache函数通过缓存检索和存储数据。当cache使用单个字符串参数调用该函数时,它将返回给定键的值:

1$value = cache('key');

如果向函数提供一个键/值对数组和一个到期时间,它将在指定的时间内将值存储在缓存中:

1cache(['key' => 'value'], $seconds);
2 
3cache(['key' => 'value'], now()->addMinutes(10));

cache当不带任何参数调用该函数时,它会返回一个Illuminate\Contracts\Cache\Factory实现的实例,允许您调用其他缓存方法:

1cache()->remember('users', $seconds, function () {
2 return DB::table('users')->get();
3});

当测试对全局cache函数的调用时,您可以像测试外观Cache::shouldReceive一样使用该方法

原子锁

要使用此功能,您的应用程序必须使用memcachedredisdynamodbdatabasefilearray缓存驱动程序作为应用程序的默认缓存驱动程序。此外,所有服务器必须与同一个中央缓存服务器通信。

管理锁

原子锁允许操作分布式锁,而无需担心竞争条件。例如,Laravel Cloud使用原子锁来确保同一时间在服务器上只执行一个远程任务。你可以使用以下Cache::lock命令创建和管理锁:

1use Illuminate\Support\Facades\Cache;
2 
3$lock = Cache::lock('foo', 10);
4 
5if ($lock->get()) {
6 // Lock acquired for 10 seconds...
7 
8 $lock->release();
9}

get方法也接受一个闭包作为参数。闭包执行后,Laravel 将自动释放锁:

1Cache::lock('foo', 10)->get(function () {
2 // Lock acquired for 10 seconds and automatically released...
3});

如果请求锁时锁不可用,你可以指示 Laravel 等待指定的秒数。如果在指定的时间内无法获取锁,Illuminate\Contracts\Cache\LockTimeoutException则会抛出以下错误:

1use Illuminate\Contracts\Cache\LockTimeoutException;
2 
3$lock = Cache::lock('foo', 10);
4 
5try {
6 $lock->block(5);
7 
8 // Lock acquired after waiting a maximum of 5 seconds...
9} catch (LockTimeoutException $e) {
10 // Unable to acquire lock...
11} finally {
12 $lock->release();
13}

上面的示例可以通过将闭包传递给block方法进行简化。当将闭包传递给此方法时,Laravel 将尝试获取指定秒数的锁,并在闭包执行完成后自动释放锁:

1Cache::lock('foo', 10)->block(5, function () {
2 // Lock acquired for 10 seconds after waiting a maximum of 5 seconds...
3});

跨进程管理锁

有时,您可能希望在一个进程中获取锁,并在另一个进程中释放它。例如,您可能在 Web 请求期间获取锁,并希望在该请求触发的排队任务结束时释放该锁。在这种情况下,您应该将锁的作用域“所有者令牌”传递给排队任务,以便该任务可以使用给定的令牌重新实例化锁。

在下面的示例中,如果成功获取锁,我们将调度一个排队任务。此外,我们将通过锁的owner方法将锁的所有者令牌传递给排队任务:

1$podcast = Podcast::find($id);
2 
3$lock = Cache::lock('processing', 120);
4 
5if ($lock->get()) {
6 ProcessPodcast::dispatch($podcast, $lock->owner());
7}

在我们的应用程序的ProcessPodcast工作中,我们可以使用所有者令牌恢复和释放锁:

1Cache::restoreLock('processing', $this->owner)->release();

如果您希望在不尊重当前所有者的情况下释放锁,则可以使用该forceRelease方法:

1Cache::lock('processing')->forceRelease();

添加自定义缓存驱动程序

编写驱动程序

要创建自定义缓存驱动程序,我们首先需要实现Illuminate\Contracts\Cache\Store 契约。因此,MongoDB 缓存实现可能如下所示:

1<?php
2 
3namespace App\Extensions;
4 
5use Illuminate\Contracts\Cache\Store;
6 
7class MongoStore implements Store
8{
9 public function get($key) {}
10 public function many(array $keys) {}
11 public function put($key, $value, $seconds) {}
12 public function putMany(array $values, $seconds) {}
13 public function increment($key, $value = 1) {}
14 public function decrement($key, $value = 1) {}
15 public function forever($key, $value) {}
16 public function forget($key) {}
17 public function flush() {}
18 public function getPrefix() {}
19}

我们只需要使用 MongoDB 连接实现这些方法即可。有关如何实现这些方法的示例,请查看LaravelIlluminate\Cache\MemcachedStore框架源代码。实现完成后,我们可以通过调用CacheFacade 的extend方法来完成自定义驱动程序注册:

1Cache::extend('mongo', function (Application $app) {
2 return Cache::repository(new MongoStore);
3});

如果您不知道该将自定义缓存驱动程序代码放在哪里,可以在目录Extensions中创建一个命名空间app。不过,请记住,Laravel 没有严格的应用程序结构,您可以根据自己的喜好自由组织应用程序。

注册驱动程序

为了将自定义缓存驱动程序注册到 Laravel,我们将使用Facadeextend上的方法Cache。由于其他服务提供商可能会尝试在其boot方法中读取缓存值,因此我们将在回调中注册自定义驱动程序booting。通过使用booting回调,我们可以确保自定义驱动程序boot在应用程序的服务提供商调用该方法之前、在所有服务提供商调用该方法之后注册。我们将在应用程序类的方法register注册回调bootingregisterApp\Providers\AppServiceProvider

1<?php
2 
3namespace App\Providers;
4 
5use App\Extensions\MongoStore;
6use Illuminate\Contracts\Foundation\Application;
7use Illuminate\Support\Facades\Cache;
8use Illuminate\Support\ServiceProvider;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 /**
13 * Register any application services.
14 */
15 public function register(): void
16 {
17 $this->app->booting(function () {
18 Cache::extend('mongo', function (Application $app) {
19 return Cache::repository(new MongoStore);
20 });
21 });
22 }
23 
24 /**
25 * Bootstrap any application services.
26 */
27 public function boot(): void
28 {
29 // ...
30 }
31}

传递给该方法的第一个参数extend是驱动程序的名称。这将与您driver在配置文件中的选项相对应config/cache.php。第二个参数是一个闭包,它应该返回一个Illuminate\Cache\Repository实例。闭包将传递一个实例,该实例是服务容器$app的一个实例

一旦你的扩展被注册,请将应用程序配置文件中的CACHE_STORE环境变量或选项更新为你的扩展的名称。defaultconfig/cache.php

Events

为了在每个缓存操作上执行代码,您可以监听缓存调度的各种事件:

事件名称
Illuminate\Cache\Events\CacheFlushed
Illuminate\Cache\Events\CacheFlushing
Illuminate\Cache\Events\CacheHit
Illuminate\Cache\Events\CacheMissed
Illuminate\Cache\Events\ForgettingKey
Illuminate\Cache\Events\KeyForgetFailed
Illuminate\Cache\Events\KeyForgotten
Illuminate\Cache\Events\KeyWriteFailed
Illuminate\Cache\Events\KeyWritten
Illuminate\Cache\Events\RetrievingKey
Illuminate\Cache\Events\RetrievingManyKeys
Illuminate\Cache\Events\WritingKey
Illuminate\Cache\Events\WritingManyKeys

为了提高性能,您可以通过在应用程序的配置文件中eventsfalse给定的缓存存储设置配置选项来禁用缓存事件config/cache.php

1'database' => [
2 'driver' => 'database',
3 // ...
4 'events' => false,
5],