缓存

简介

Laravel 为各种缓存后端提供了丰富而统一的 API。缓存配置位于 config/cache.php。在此文件中可以指定整个应用默认使用的缓存驱动。Laravel 支持流行的缓存后端,如开箱即用的 MemcachedRedis

缓存配置文件还包含各种其它选项,这些选项都记录在文件中,因此请务必阅读这些选项。默认情况下,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 扩展包。可以在 config/cache.php 配置文件中列出所有的 Memcached 服务器:

'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 缓存之前,需要使用 Composer 安装 predis/predis 扩展包(~1.0)或使用 PECL 安装 PhpRedis PHP 扩展。

关于配置 Redis 的更多信息,请查看 Laravel 文档

缓存使用

获取缓存实例

Illuminate\Contracts\Cache\FactoryIlluminate\Contracts\Cache\Repository Contracts 提供访问 Laravel 缓存服务的方法。Factory Contract 提供了访问应用中定义的所有缓存驱动的方法。Repository Contract 通常是 cache 配置文件中指定的应用默认缓存驱动的实现。

不过,也可以使用 Cache Facade,我们会在本文档中使用它。Cache Facade 为访问底层 Laravel 缓存 Contracts 实现提供了方便简洁的方法:

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', 10);

从缓存获取数据

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 方法可用于判断缓存项是否在缓存中存在。如果缓存项的值是 nullfalse,此方法会返回 false

if (Cache::has('key')) {
    //
}

增加/减少值

incrementdecrement 方法可用于调整缓存中值为整数的缓存项。两个方法都接收一个可选的第二个参数表示缓存的值增加或减少的具体数量:

Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

获取 & 存储

有时可能希望从缓存中获取缓存项,但还希望如果请求的缓存项不存在时存储默认值。例如,从缓存中获取所有用户,或者,当缓存不存在时,从数据库中获取所有用户,并将其添加到缓存中。可以使用 Cache::remember 方法完成此操作:

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

如果缓存项在缓存中不存在,传递给 remember 方法的 Closure 会执行,并将其结果放到缓存中。

可以使用 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', $minutes);

除了传递整数值的分钟数之外,还可以传递表示缓存数据过期时间的 DateTime 实例:

$expiresAt = now()->addMinutes(10);

Cache::put('key', 'value', $expiresAt);

不存在时存储

add 只有在缓存项在缓存中不存在时,才会将其添加到缓存。如果缓存项实际添加到了缓存,会返回 true。否则,返回 false

Cache::add('key', 'value', $minutes);

永久存储

forever 方法可以将缓存项永久存储到缓存中。由于这些数据不会过期,因此需要使用 forget 方法手动将其从缓存中移除:

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

如果使用 Memcached 驱动,「永久」存储的数据会在缓存达到最大大小限制时被移除。

从缓存删除数据

可以使用 forget 方法从缓存中移除数据:

Cache::forget('key');

可以使用 flush 方法清空所有缓存:

Cache::flush();

清空缓存不关心缓存前缀,而是从缓存中移除所有数据。因此在清空与其它应用共享的缓存时,请慎重考虑。

原子锁

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

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

if (Cache::lock('foo', 10)->get()) {
    // 获得锁 10 秒

    Cache::lock('foo')->release();
}

get 方法还接收一个闭包。闭包执行后,Laravel 会自动释放锁:

Cache::lock('foo')->get(function () {
    // 获得无期限锁并自动释放
});

如果请求时不能获得锁,可以指示 Laravel 等待指定秒数。如果在指定的时间限制内不能获得锁,会抛出一个 Illuminate\Contracts\Cache\LockTimeoutException

if (Cache::lock('foo', 10)->block(5)) {
    // 最多等待 5 秒后获取锁
}

Cache::lock('foo', 10)->block(5, function () {
    // 最多等待 5 秒后获取锁
});

缓存辅助函数

除了使用 Cache Facade 或 Cache Contract,还可以使用全局辅助函数 cache 在缓存中获取和存储数据。当使用单个字符串参数调用 cache 函数时,会返回给定键的值:

$value = cache('key');

如果提供键/值对和一个过期时间给该函数,它会按指定的期限将值存储到缓存中:

cache(['key' => 'value'], $minutes);

cache(['key' => 'value'], now()->addSeconds(10));

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

cache()->remember('users', $minutes, function () {
    return DB::table('users')->get();
});

当测试全局辅助函数 cache 时,可以使用 Cache::shouldReceive 方法,就像 测试 Facade 一样。

缓存标签

使用 filedatabase 缓存驱动时不支持缓存标签。此外,对「永久」存储的缓存使用多个标签时,类似 memcached 驱动的性能会是最好的,它会自动清除旧的数据。

存储打标签的数据

缓存标签允许您在缓存中为相关的缓存项打上标签,然后清空所有指定了给定标签的缓存数据。可以通过传递有序排列的标签名称的数组获取打标签的缓存数据。例如,我们获取打标签的缓存数据并 put 值到缓存中:

Cache::tags(['people', 'artists'])->put('John', $john, $minutes);

Cache::tags(['people', 'authors'])->put('Anne', $anne, $minutes);

获取打标签的数据

要获取打标签的缓存项,传递同样有序排列的标签列表给 tags 方法,然后使用希望获取的键名调用 get 方法:

$john = Cache::tags(['people', 'artists'])->get('John');

$anne = Cache::tags(['people', 'authors'])->get('Anne');

删除打标签的数据

可以清空被指定了标签或标签列表的所有缓存项。例如,如下语句会移除所有打了 peopleauthors 或者同时两个标签的缓存项。因此,AnneJohn 两项都会从缓存中移除:

Cache::tags(['people', 'authors'])->flush();

相反,如下语句只会移除打了 authors 标签的缓存项,因此 Anne 会被移除,而 John 不会:

Cache::tags('authors')->flush();

添加自定义缓存驱动

编写驱动

创建自定义缓存驱动,首先要实现 Illuminate\Contracts\Cache\Store Contract。因此,一个 MongoDB 缓存的实现应该看起来像这样:

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, $minutes) {}
    public function putMany(array $values, $minutes);
    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 Facade 的 extend 方法。可以在 Laravel 应用默认的 App\Providers\AppServiceProvider 文件的 boot 方法中调用 Cache::extend 来完成,或者创建一个自己的服务提供者来放置扩展代码 —— 只是不要忘了在 config/app.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 实例的闭包。此闭包会传递给 $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',
    ],
];