ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] # 缓存系统 ## 配置 配置文件`config/cache.php`,默认使用 `file` 缓存驱动,支持多种驱动,可以为同一个驱动程序设置多个缓存配置。 ### 驱动的前提条件 #### 数据库 使用 `database` 缓存驱动时,需要配置一个表来存放缓存数据。 ``` Schema::create('cache', function ($table) { $table->string('key')->unique(); $table->text('value'); $table->integer('expiration'); }); ``` >[success] 可以使用 Artisan 命令 `php artisan cache:table` 来生成合适的迁移。 #### Memcached 使用 Memcached 驱动需要安装 [Memcached PECL 扩展包](https://pecl.php.net/package/memcached) ,设置配置文件`config/cache.php` ``` 'memcached' => [ [ 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100 ], ], ``` 可以将 `host` 选项设置为 UNIX socket 路径,端口 `port` 选项应该设置为 `0` ``` 'memcached' => [ [ 'host' => '/var/run/memcached/memcached.sock', 'port' => 0, 'weight' => 100 ], ], ``` #### Redis 使用 Redis 驱动需要通过 Composer 安装 `predis/predis` 扩展包 (~1.0) 或者使用 PECL 安装 PhpRedis PHP扩展。 ## 缓存的使用 ### 获取缓存实例 `Illuminate\Contracts\Cache\Factory` 和 `Illuminate\Contracts\Cache\Repository` [契约](/docs/{{version}}/contracts) 提供了 Laravel 缓存服务的访问机制。 `Factory` 契约为你的应用程序定义了访问所有缓存驱动的机制。 `Repository` 契约通常是由你的 `cache` 配置文件指定的默认缓存驱动实现的。 `Cache` Facade 为 Laravel 缓存契约底层的实现提供了方便又简洁的方法。 ``` <?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller { /** * 展示应用的所有用户列表。 * * @return Response */ public function index() { $value = Cache::get('key'); // } } ``` #### 访问多个缓存存储 通过 `store` 方法来访问在 `cache` 配置文件中的 `stores` 存储选项。 ``` $value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10 分钟 ``` ### 从缓存中获取数据 通过 `get` 方法从缓存中获取数据。如果该数据在缓存中不存在,那么将返回 `null` 。使用 `get` 方法第二个参数设置默认值,第二个参数也可以使用闭包。 ``` $value = Cache::get('key'); $value = Cache::get('key', 'default'); $value = Cache::get('key', function () { return DB::table(...)->get(); }); ``` #### 检查缓存项是否存在 >[info] 通过 `has` 方法判断缓存是否存在。如果为 `null` 或 `false` 则该方法将会返回 `false` 。 ``` if (Cache::has('key')) { // } ``` #### 递增与递减值 通过 `increment` 和 `decrement` 方法调整缓存中整数的值。使用第二个可选参数设置递增或递减的步长。 ``` Cache::increment('key'); Cache::increment('key', $amount); Cache::decrement('key'); Cache::decrement('key', $amount); ``` #### 获取和存储 通过 `remember` 方法在未获取到缓存时,设置默认值,并存入缓存,第二参数为缓存时间秒。 ``` $value = Cache::remember('users', $seconds, function () { return DB::table('users')->get(); }); ``` 通过 `rememberForever` 方法从缓存中获取数据或者永久存储。 ``` $value = Cache::rememberForever('users', function () { return DB::table('users')->get(); }); ``` #### 获取和删除 通过 `pull` 方法在获取缓存并删除,如果缓存不存在,则返回 `null`。 ``` $value = Cache::pull('key'); ``` ### 在缓存中存储数据 通过 `put` 方法将数据存储到缓存中,未设置过期时间缓存永久有效。 ``` Cache::put('key', 'value', $seconds); // 单位秒 Cache::put('key', 'value'); ``` 可以传递 `DateTime` 实例来表示该数据的过期时间。 ``` Cache::put('key', 'value', now()->addMinutes(10)); ``` #### 只存储没有的数据 通过 `add` 方法将只存储缓存中不存在的数据。如果存储成功,将返回 `true` ,否则返回 `false`。 ``` Cache::add('key', 'value', $seconds); ``` #### 数据永久存储 通过 `forever` 方法将数据永久存储到缓存中,通过 `forget` 方法手动删除。 ``` Cache::forever('key', 'value'); ``` >[danger] 注意:如果你使用 Memcached 驱动,当缓存数据达到存储上限时,「永久存储」 的数据可能会被删除。 ### 删除缓存中的数据 使用 `forget` 方法从缓存中删除数据。 ``` Cache::forget('key'); ``` 将过期时间设置为零或者负数。 ``` Cache::put('key', 'value', 0); Cache::put('key', 'value', -5); ``` 使用 `flush` 方法清空所有的缓存。 ``` Cache::flush(); ``` >[danger] 注意:清空缓存的方法并不会考虑缓存前缀,会将缓存中的所有内容删除。因此在清除与其它应用程序共享的缓存时,请慎重考虑。 ### 原子锁 >[success] 要使用该特性,必须使用`memcached`或`dynamodb`或`redis`缓存驱动作为应用的默认缓存驱动。此外,所有服务器必须与同一中央缓存服务器进行通信。 原子锁允许对分布式锁进行操作而不必担心竞争条件。例如 [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(); } Cache::lock('foo', 10)->block(5, function () { // 等待最多5秒后获取的锁... }); ``` #### 管理跨进程的锁 有时,你希望在一个进程中获取锁并在另外一个进程中释放它。例如,你可以在 Web 请求期间获取锁,并希望在该请求触发的队列作业结束时释放锁。在这种情况下,你应该将锁的作用域「owner token」传递给队列作业,以便作业可以使用给定的 token 重新实例化锁。 ``` // 控制器里面... $podcast = Podcast::find($id); if ($lock = Cache::lock('foo', 120)->get()) { ProcessPodcast::dispatch($podcast, $lock->owner()); } // ProcessPodcast Job 里面... Cache::restoreLock('foo', $this->owner)->release(); ``` 如果要释放锁而不考虑其当前所有者,可以使用 `forceRelease` 方法。 ``` Cache::lock('foo')->forceRelease(); ``` ### 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(); }); ``` ## 缓存标记 >[info] 缓存标记不支持使用 `file` 和 `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(); ``` ## 增加自定义的缓存驱动 ### 编写驱动 要创建自定义的缓存驱动,首先需要实现 `Illuminate\Contracts\Cache\Store` [契约](/docs/{{version}}/contracts)。因此, MongoDB 的缓存实现看起来会像这样: ``` <?php namespace App\Extensions; use Illuminate\Contracts\Cache\Store; class MongoStore implements Store { public function get($key) {} public function many(array $keys); public function put($key, $value, $seconds) {} public function putMany(array $values, $seconds); public function increment($key, $value = 1) {} public function decrement($key, $value = 1) {} public function forever($key, $value) {} public function forget($key) {} public function flush() {} public function getPrefix() {} } ``` 我们只需要 MongoDB 的连接来实现这些方法。关于如何实现这些方法的实例,可以参阅框架源代码中的 `Illuminate\Cache\MemcachedStore` 。一旦完成契约额实现后,就可以像下面这样完成自定义驱动的注册了。 ``` Cache::extend('mongo', function ($app) { return Cache::repository(new MongoStore); }); ``` >[success] 如果你不知道将缓存驱动代码放在哪,你可以在 `app` 目录下创建一个 `Extensions` 命名空间。然而,Laravel 并没有硬性规定应用程序的结构,你可以根据自己的喜好自由组织你的应用程序。 ### 注册驱动 要使用 Laravel 来注册自定义缓存驱动,就要在 `Cache` Facade 上使用 `extend` 方法。 对 `Cache::extend` 的调用可以在新的 Laravel 应用程序中自带的 `App\Providers\AppServiceProvider` 的 `boot` 方法中完成,或者你也可以创建自己的服务提供者来存放扩展,只是不要忘记在 `config/app.php` 的 provider 数组中注册服务提供者: ``` <?php namespace App\Providers; use App\Extensions\MongoStore; use Illuminate\Support\Facades\Cache; use Illuminate\Support\ServiceProvider; class CacheServiceProvider extends ServiceProvider { /** * 执行服务的注册后引导。 * * @return void */ public function boot() { Cache::extend('mongo', function ($app) { return Cache::repository(new MongoStore); }); } /** * 在容器中注册绑定。 * * @return void */ public function register() { // } } ``` 传递给 `extend` 方法的第一个参数是驱动程序的名称。这将与 `config/cache.php` 配置文件的 `driver` 选项相对应。第二个参数是一个应该返回 `Illuminate\Cache\Repository` 实例的闭包。该闭包将传递一个 [服务容器](/docs/{{version}}/container) 的 `$app` 实例。 一旦你的扩展程序注册后,需要将 `config/cache.php` 配置文件中的 `driver` 选项更新为你的扩展名称。 ## 事件 >[info] 要在每次缓存操作时执行代码,你可以监听缓存触发的 [事件](/docs/{{version}}/events) 。通常,你应该将这些事件监听器放在 `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', ], ]; ```