缓存系统 配置 Laravel 为各种后端缓存提供了丰富而统一的 API,其缓存配置位于 config/cache.php 文件中。在该文件中你可以指定应用默认使用哪个缓存驱动。Laravel 支持当前流行的后端缓存,例如 Memcached 和 Redis。 缓存配置文件还包含各种其他选项,这些选项都记录在文件中,因此请确保阅读这些选项。默认情况下,Laravel 配置为使用 file 缓存驱动,它将序列化的缓存对象存储在文件系统中。对于较大型应用,建议使用更强大的驱动程序,例如 Memcached 或 Redis。你甚至可以为同一个驱动程序配置多个缓存配置。 驱动的前提条件 数据库 当使用 database 缓存驱动时,你需要配置一个表来存放缓存数据。下面是构建缓存数据表结构的 Schema 声明示例: ``` Schema::create('cache', function ($table) { $table->string('key')->unique(); $table->text('value'); $table->integer('expiration'); }); ``` 提示:你也可以使用 Artisan 命令 php artisan cache:table 来生成合适的迁移。 Memcached 使用 Memcached 驱动需要安装 Memcached PECL 扩展包 。你可以把所有的 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 在使用 Laravel 的 Redis 缓存之前,你需要通过 PECL 安装 PhpRedis PHP 扩展,或者通过 Composer 安装 predis/predis 包(~1.0)。 如需了解更多关于 Redis 的配置,请参考 Laravel Redis 文档。 缓存使用 获取缓存实例 Illuminate\Contracts\Cache\Factory 和 Illuminate\Contracts\Cache\Repository 契约 提供了 Laravel 缓存服务的访问机制。 Factory 契约为你的应用程序定义了访问所有缓存驱动的机制。 Repository 契约通常是由你的 cache 配置文件指定的默认缓存驱动实现的。 当然,你也可以使用 Cache Facade,我们将在后续的文档中介绍。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'); // } } ``` 访问多个缓存存储 使用 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'); ``` 你甚至可以传递一个 Closure 作为默认值。如果指定的数据在缓存中不存在,将返回 Closure 的结果。传递闭包的方法允许你从数据库或其他外部服务中获取默认值。 ``` $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); ``` 如果缓存的过期时间没有传递给 put 方法, 则缓存将永久有效: ``` 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'); ``` 技巧:如果你使用 Memcached 驱动,当缓存数据达到存储上限时,「永久存储」的数据可能会被删除。 从缓存中删除数据 你可以使用 forget 方法从缓存中删除这些数据: ``` Cache::forget('key'); ``` 你也可以通过提供零或者负的 TTL 值来删除这些数据: ``` Cache::put('key', 'value', 0); Cache::put('key', 'value', -5); ``` 你可以使用 flush 方法清空所有的缓存: ``` Cache::flush(); ``` 注意:清空缓存的方法并不会考虑缓存前缀,会将缓存中的所有内容删除。因此在清除与其它应用程序共享的缓存时,请慎重考虑。 Cache 辅助函数 除了可以使用 Cache 的门面以及 Cache 契约 外,你也可以使用全局辅助函数 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 方法就像 测试 Facade。 缓存标记 注意:当使用 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 使用原子锁来确保在一台服务器上每次只有一个远程任务在执行。你可以使用 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); $lock = Cache::lock('foo', 120); if ($result = $lock->get()) { ProcessPodcast::dispatch($podcast, $lock->owner()); } // ProcessPodcast Job 里面... Cache::restoreLock('foo', $this->owner)->release(); ``` 如果你想在无视当前锁的所有者的情况下释放锁,你可以使用 forceRelease 方法: ``` Cache::lock('foo')->forceRelease(); ``` 添加自定义缓存驱动 编写驱动 要创建自定义的缓存驱动,首先需要实现 Illuminate\Contracts\Cache\Store contract 契约。因此, 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); }); ``` 技巧:如果你不知道该将缓存驱动的代码放在什么地方,你可以在你的 app 文件夹下创建一个 Extensions 的命名空间。 值得注意的是,Laravel 并没有硬性规定应用程序的结构。因此你可以根据自己的喜好自由的组织你的应用程序。 注册驱动 在 Laravel 注册一个自定义的缓存驱动,我们需要在 Cache 门面上使用 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 register() { // } /** * 引导应用服务 * * @return void */ public function boot() { Cache::extend('mongo', function ($app) { return Cache::repository(new MongoStore); }); } } ``` 传递给 extend 方法的第一个参数是驱动程序的名称。 该值对应 config/cache.php 配置文件中的 driver 选项。 第二个参数是返回 Illuminate\Cache\Repository 实例的闭包。该闭包中被传入一个 $app 的实例, 也就是 服务容器 的一个实例。 一旦你的扩展被注册以后,只需要更新配置文件 config/cache.php 的 driver 选项作为自定义扩展名称即可。 事件 要在每次缓存操作时执行代码,你可以监听缓存触发的 事件。 通常,你应该将这些事件***放在 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', ], ]; ```