# 缓存系统
- [简介](#introduction)
- [配置](#configuration)
- [驱动的前提条件](#driver-prerequisites)
- [缓存使用](#cache-usage)
- [获取缓存实例](#obtaining-a-cache-instance)
- [从缓存获取数据](#retrieving-items-from-the-cache)
- [向缓存存储数据](#storing-items-in-the-cache)
- [从缓存删除数据](#removing-items-from-the-cache)
- [Cache 辅助函数](#the-cache-helper)
- [缓存标记](#cache-tags)
- [存储被标记的缓存数据](#storing-tagged-cache-items)
- [访问被标记的缓存数据](#accessing-tagged-cache-items)
- [删除被标记的缓存数据](#removing-tagged-cache-items)
- [原子锁](#atomic-locks)
- [驱动的前提条件](#lock-driver-prerequisites)
- [管理锁](#managing-locks)
- [跨进程管理锁](#managing-locks-across-processes)
- [添加自定义缓存驱动](#adding-custom-cache-drivers)
- [编写驱动](#writing-the-driver)
- [注册驱动](#registering-the-driver)
- [事件](#events)
## 简介
在某些应用中,一些查询数据或处理任务的操作会在某段时间里短时间内大量进行,或是一个操作花费好几秒钟。当出现这种情况时,通常会将检索到的数据缓存起来,从而为后面请求同一数据的请求迅速返回结果。这些缓存数据通常会储存在极快的存储系统中,例如 [Memcached](https://memcached.org) 和 [Redis](https://redis.io)。
Laravel 为各种缓存后端提供了富有表现力且统一的 API,以便你利用它们极快的查询数据来加快你的应用。
## 配置
缓存配置文件位于 `config/cache.php`。在这个文件中,你可以指定应用默认使用哪个缓存驱动。Laravel 支持的缓存后端包括 [Memcached](https://memcached.org)、[Redis](https://redis.io)、[DynamoDB](https://aws.amazon.com/dynamodb),以及现成的关系型数据库。此外,还支持基于文件的缓存驱动,以及方便自动化测试的缓存驱动 `array`、`null`。
缓存配置文件还包含其他各种选项,这些选项记录在文件中,因此请务必仔细阅读这些选项。默认情况下,Laravel 配置为使用 `file` 缓存驱动,该驱动将序列化的缓存对象存储在服务器的文件系统上。对于较大的应用程序,建议您使用功能更强大的驱动,例如 Memcached 或 Redis。你甚至可以为同一驱动程序配置多个缓存配置。
### 驱动程序先决条件
#### 数据库
当使用 `database` 缓存驱动程序时,你将需要设置一个表来包含缓存项。你将在下表中找到示例 ` Schema` 声明:
Schema::create('cache', function ($table) {
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
});
> 技巧:你也可以使用 `php artisan cache:table` Artisan 命令生成具有正确架构的迁移。
#### Memcached
使用 Memcached 驱动程序需要安装 [Memcached PECL package](https://pecl.php.net/package/memcached)。你可以在 `config / cache.php` 配置文件中列出所有的 Memcached 服务器。这个文件已经包含一个 `memcached.servers` 条目,可以帮助你入门:
'memcached' => [
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
如果需要,可以将 `host` 选项设置为 UNIX 套接字路径。如果这样做,则应将 `port` 选项设置为 `0`:
'memcached' => [
[
'host' => '/var/run/memcached/memcached.sock',
'port' => 0,
'weight' => 100
],
],
#### Redis
在使用 Laravel 使用 Redis 缓存之前,你将需要通过 PECL 安装 PhpRedis 的 PHP 扩展或通过 Composer 安装 `predis/predis`(~1.0) 依赖包。[Laravel Sail](/docs/laravel/8.x/sail) 已包含此扩展名。此外,默认的正式 Laravel 部署平台,例如 [Laravel Forge](https://forge.laravel.com) 和 [Laravel Vapor](https://vapor.laravel.com),都已安装了 PhpRedis 扩展。
有关配置 Redis 的更多信息,请参阅 [文档](/docs/laravel/8.x/redis#configuration)。
## 缓存使用
### 获取缓存实例
要获取缓存存储实例,可以使用 `Cache` facade,我们将在本文档中使用它。`Cache` facade提供了对 Laravel 缓存契约底层实现的方便、简洁的访问:
#### 访问多个缓存存储
使用 `Cache` facade,可以通过 `store` 方法访问各种缓存存储。传递给 `store` 方法的键应与 `cache` 配置文件中 `stores` 配置数组中列出的其中一个存储相对应:
$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
### 从缓存中检索项目
`Cache` facade 的 `get` 方法用于从缓存中检索项。如果缓存中不存在该项,则返回 `null`。如果愿意,可以将第二个参数传递给 `get` 方法,指定在该项不存在时返回的默认值:
$value = Cache::get('key');
$value = Cache::get('key', 'default');
甚至可以传递闭包作为默认值。如果缓存中不存在指定的项,则将返回关闭的结果。传递闭包允许你延迟从数据库或其他外部服务检索默认值:
$value = Cache::get('key', function () {
return DB::table(...)->get();
});
#### 检查缓存项是否存在
使用 `has` 方法判断一个缓存项是否存在于缓存里。 如果缓存项存在但是为 `null` 此方法也返回 `false`:
if (Cache::has('key')) {
//
}
#### 递增 / 递减值
`increment` 和 `decrement` 方法用来改变缓存里整形项目的值。这两个方法接收的第二参数为可选参数,这个参数用来指明要递增或递减数值:
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);
#### 获取和存储
有时你可能想从缓存中获取一个数据,但当请求的缓存项不存在时,则存储一个默认值。例如,你可能想从缓存中获取所有用户,但当缓存中不存在这些用户时,程序从数据库将用户取出并放入缓存。你可以使用 `Cache::remember` 方法实现:
$value = Cache::remember('users', $seconds, function () {
return DB::table('users')->get();
});
如果缓存数据不存在,那么 `remember` 方法的闭包将被执行,然后将其结果返回并放置到缓存中。
你可以使用 `rememberForever` 方法从缓存中获取数据或者永久存储在缓存里不存在的数据:
$value = Cache::rememberForever('users', function () {
return DB::table('users')->get();
});
#### 获取和删除
如果你需要从缓存中获取到数据,之后就删除它,可以使用 `pull` 方法。和 `get` 方法一样,如果缓存不存在,则返回 `null`:
$value = Cache::pull('key');
### 在缓存中存储数据
你可以使用 `Cache` Facade 的 `put` 方法将数据存储到缓存中:
Cache::put('key', 'value', $seconds = 10);
如果不告诉 `put` 方法存储的期限,那么数据会永久存储。
Cache::put('key', 'value');
除了传递整形的时间秒数外,你也可以传递一个 `DateTime` 的实例表示缓存数据的过期时间:
Cache::put('key', 'value', now()->addMinutes(10));
#### 只存储没有的数据
`add` 方法仅仅只会把缓存里不存在的数据添加到缓存里。如果存储成功这个方法返回 `true` 反之,返回 `false`。`add` 方法是原子化的操作:
Cache::add('key', 'value', $seconds);
#### 数据永久存储
`forever` 方法可用于在缓存里持久化存储数据。由于这些数据不会过期,他们必须使用 `forget` 方法手动删除:
Cache::forever('key', 'value');
> 技巧:如果你使用 Memcached 驱动,当缓存数据达到存储上限时,「 永久存储 」的数据可能会被删除。
### 从缓存中删除数据
你可以使用 `forget` 方法从缓存中删除这些数据:
Cache::forget('key');
你也可以通过提供零或者负数过期秒数值来删除这些数据:
Cache::put('key', 'value', 0);
Cache::put('key', 'value', -5);
你可以使用 `flush` 方法清空所有缓存数据:
Cache::flush();
> 注意:清空缓存的方法并不会考虑缓存前缀,会将缓存中的所有内容删除。因此在清除与其它应用程序共享的缓存时,请慎重考虑。
### Cache 辅助函数
除了可以使用 `Cache` facade ,你也可以使用全局辅助函数 `cache` 来获取和保存缓存数据。当只使用一个字符串参数调用 `cache` 函数时,将返回给定键对应的值。
$value = cache('key');
如果你向函数提供了一组带有过期时间的键值对,那么在这段时间内,它将缓存此数据:
cache(['key' => 'value'], $seconds);
cache(['key' => 'value'], now()->addMinutes(10));
当 `cache` 函数在没有任何参数的情况下被调用,那么它返回的将是一个实现 `Illuminate\Contracts\Cache\Factory` 的实例,并且允许你调用其他的缓存方法:
cache()->remember('users', $seconds, function () {
return DB::table('users')->get();
});
> 技巧:如果在测试中使用全局函数 `cache` ,你可以使用 `Cache::shouldReceive` 方法就像 [testing the facade](/docs/laravel/8.x/mocking#mocking-facades)。
## 缓存标记
> 注意:当使用 `file`,`dynamodb` 或 `database` 缓存驱动时,缓存标记将不支持。此外,当使用多个带有「永久」存储的缓存的标记时,最好使用自动清除陈旧记录的驱动程序 例如 `Memcached` 来获得最佳性能。
### 存储被打上标签的缓存数据
缓存标签允许你给相关的缓存标签项打上同一个标签以便后续可以清除这些缓存值。你可以通过传入一个精心安排的标签数组来访问这些缓存项。例如,我们可以在使用标签的同时使用 `put` 方法来设置缓存。
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);
### 访问被打上标签的缓存数据
若要获取一个被打上标签的缓存数据,将相同标签的有序数组传递给 `tags` 方法,然后调用 `get` 方法检索你要获取的键:
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');
### 移除带有标签的缓存数据
你可能需要移除单个标签或者一组标签所标记的所有缓存数据,例如,下面这个例子将会移除带有 `people`,`authors` 或者两者都有的标签的所有缓存数据,所以 `Anne` 和 `John` 将会从缓存中删除:
Cache::tags(['people', 'authors'])->flush();
相反,下面的例子只会移除带有标签 `authors` 的缓存数据,因此 `Anne` 缓存数据将会被移除, 但是 `John` 就不会:
Cache::tags('authors')->flush();
## 原子锁
> 注意:要利用此功能,您的应用程序必须使用 `memcached`,`dynamodb`,`redis`,`database` 或 `array` 缓存驱动程序作为应用程序的默认缓存驱动程序。 此外,所有服务器必须与同一中央高速缓存服务器通信。
### 驱动的前提条件
#### 数据库
当使用 `database` 缓存驱动时,你需要配置一个表来存放原子锁。下面是构建原子锁表结构的 `Schema` 声明:
Schema::create('cache_locks', function ($table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
### 管理锁
原子锁允许对分布式锁进行操作而不必担心竞争条件。如 [Laravel Forge](https://forge.laravel.com) 使用原子锁来确保在一台服务器上每次只有一个远程任务在执行。你可以使用 `Cache::lock` 方法来创建和管理锁:
use Illuminate\Support\Facades\Cache;
$lock = Cache::lock('foo', 10);
if ($lock->get()) {
// 锁定时间为10秒...
$lock->release();
}
`get` 方法可以接受一个闭包。在闭包执行之后,`Laravel` 将会自动释放锁
Cache::lock('foo')->get(function () {
// 无限期获取锁并自动释放...
});
如果你在请求时锁无法使用,你可以控制 Laravel 等待指定的秒数。如果在指定的时间限制内无法获取锁,则会抛出 `Illuminate\Contracts\Cache\LockTimeoutException` :
use Illuminate\Contracts\Cache\LockTimeoutException;
$lock = Cache::lock('foo', 10);
try {
$lock->block(5);
// 为获得锁等待最多 5 秒...
} catch (LockTimeoutException $e) {
// 无法获取锁...
} finally {
optional($lock)->release();
}
通过向 `block` 方法传递闭包,可以简化上述示例。当闭包传递给此方法时,Laravel将尝试在指定的秒数内获取锁,并在执行闭包后自动释放锁:
Cache::lock('foo', 10)->block(5, function () {
// 为获得锁等待最多 5 秒...
});
### 管理跨进程锁
有时候,你可能希望在一个进程中获取锁然后在另一个进程中释放它。例如,你可能在一个 Web 请求中获取锁并且希望在由该请求触发的队列任务结束时释放锁。对于这样的场景,你应该将锁的作用域 「owner token」传递给队列任务,以便该队列任务可以使用给定的 token 重新实例化锁:
在下面的示例中,如果成功获取锁,我们将调度一个队列作业。此外,我们将通过锁的 `owner` 方法将锁的token令牌传递给队列:
$podcast = Podcast::find($id);
$lock = Cache::lock('processing', 120);
if ($result = $lock->get()) {
ProcessPodcast::dispatch($podcast, $lock->owner());
}
在应用程序的 `ProcessPodcast` 的队列中,我们可以使用token令牌还原和释放锁:
Cache::restoreLock('processing', $this->owner)->release();
如果你想在无视当前锁的所有者的情况下释放锁,你可以使用 forceRelease 方法:
Cache::lock('processing')->forceRelease();
## 添加自定义缓存驱动
### 编写驱动
要创建自定义的缓存驱动,首先需要实现 `Illuminate\Contracts\Cache\Store` [contract](/docs/laravel/8.x/contracts)。因此, MongoDB 缓存实现看起来就像是这样:
技巧:如果你不知如何放置缓存驱动的代码,你可以在 `app` 目录里创建一个 `Extensions` 命名空间。 然而请注意:Laravel 并没有硬性规定程序的结构,你可以按偏好随意放置你的程序。
### 注册驱动
为了在 Laravel 注册一个自定义的缓存驱动,我们会在 `Cache` Facade 中使用 `extend` 方法。由于其它的服务提供者会尝试在它们 `boot` 方法里读取缓存数据,我们将在 `booting` 回调里注册我们的自定义驱动。使用 `booting` 回调,我们可以确保这个自定义驱动在我们所有的服务提供者的 `register` 方法被调用之后并且 `boot` 方法被调用之前注册完成。我们会在我们程序的 `App\Providers\AppServiceProvider` 类中的 `register` 方法里注册 `booting` 回调。
app->booting(function () {
Cache::extend('mongo', function ($app) {
return Cache::repository(new MongoStore);
});
});
}
/**
* 启动任意程序服务.
*
* @return void
*/
public function boot()
{
//
}
}
传递给 `extend` 方法的第一个参数是驱动名。该值对应 `config/cache.php` 配置文件中的 `driver` 选项。 第二个参数是返回 `Illuminate\Cache\Repository` 实例的闭包,该闭包中被传入一个 `$app` 的实例,它是一个 [ 服务容器 ](/docs/laravel/8.x/container) 的实例。
一旦你的扩展被注册后,只需要更新配置文件 `config/cache.php` 的 `driver` 选项作为自定义扩展名称即可。
## 事件
要在每次缓存操作时执行代码,你可以监听缓存触发的 [ 事件 ](/docs/laravel/8.x/events) 。 通常,你应该在 `App\Providers\EventServiceProvider` 类中放置这些事件监听器:
/**
* 程序事件监听器对应数组.
*
* @var array
*/
protected $listen = [
'Illuminate\Cache\Events\CacheHit' => [
'App\Listeners\LogCacheHit',
],
'Illuminate\Cache\Events\CacheMissed' => [
'App\Listeners\LogCacheMissed',
],
'Illuminate\Cache\Events\KeyForgotten' => [
'App\Listeners\LogKeyForgotten',
],
'Illuminate\Cache\Events\KeyWritten' => [
'App\Listeners\LogKeyWritten',
],
];