缓存使用指南

本篇介绍通过 Cache 门面完成日常缓存操作:读写删除、过期控制、数值增减、数组集合、多商店切换与竞争锁。驱动选型与配置见 缓存驱动,标签分组见 缓存标签

基本读写

写入 set()

php
use Viswoole\Cache\Facade\Cache;

// 未指定过期时间,使用驱动默认值(默认永不过期)
Cache::set('config:site', $config);

// 指定过期时间(秒)
Cache::set('user:1', ['name' => '张三'], 3600);

// NX 模式:仅当键不存在(或已过期)时写入,常用于抢占
Cache::set('job:report:today', $payload, 3600, true);

set() 的完整签名:

参数类型默认值说明
keystring必填缓存键
valuemixed必填缓存值,任意可序列化数据
expireDateTime|int|nullnull过期秒数;传 DateTime 表示绝对时间点;null 使用驱动默认值
NXboolfalse为 true 时仅当键不存在时才写入

读取 get()

php
// 不存在时返回 null
$user = Cache::get('user:1');

// 不存在时返回默认值
$page = Cache::get('user:1:page', 1);

get() 不支持闭包默认值

第二个参数是「默认值」而非回调:即使传入闭包,缓存不存在时也会原样返回闭包对象本身。「不存在时自动计算并回填」需自行 has() 判断后再 set()

判断与删除

php
Cache::has('user:1');              // 是否存在(已过期视为不存在)

Cache::delete('user:1');           // 删除单个
Cache::delete(['k1', 'k2', 'k3']); // 批量删除,返回删除数量

Cache::clear();                    // 清除当前驱动下的所有缓存

clear() 的作用范围

File 驱动的 clear() 会递归清空整个缓存存储目录;Redis 驱动的 clear() 执行的是 flushDB,会清空所配置 db_index 下的全部数据(包括非缓存用途的键)。共享 Redis 实例时请通过 prefix 与独立 db_index 隔离。

读取并删除 pull()

php
// 原子性不强,等价于 get + delete 的组合,适合一次性消费的场景
$job = Cache::pull('job:pending');

剩余有效期 ttl()

php
$ttl = Cache::ttl('user:1');
// int  剩余秒数
// -1   长期有效(未设置过期时间)
// false 不存在或已过期

过期时间

set()$expire 支持三种形态:

php
Cache::set('a', 1, 600);                          // 相对秒数
Cache::set('b', 2, new DateTime('+1 hour'));      // 绝对时间点
Cache::set('c', 3, 0);                            // 0 表示永不过期

驱动可在配置中设置 expire 作为默认过期时间(见 缓存驱动),此后 set() 不传 $expire 时按默认值处理。

数值增减

针对数值型缓存的自增/自减操作,返回操作后的新值:

php
Cache::set('views', 100);

Cache::inc('views');       // 101,步长默认 1
Cache::inc('views', 10);   // 111
Cache::dec('views', 5);    // 106

仅适用于数值缓存

File 驱动要求缓存值为 int / float,否则抛出 CacheErrorException;Redis 驱动底层使用 INCRBY / DECRBY,要求值为整数。

cache() 助手函数

全局助手 cache() 用于快速读取缓存;不传键时返回 CacheManager 实例,可继续链式调用任意门面方法:

php
$value = cache('user:1');            // 等价于 Cache::get('user:1')
$value = cache('user:1', '默认值');   // 带默认值读取

cache()->set('user:2', $data, 60);   // 返回 CacheManager,可继续完整操作

更多助手函数见 助手函数

数组集合操作

以集合(Set)语义维护一个字符串数组:追加时自动去重,File 驱动用序列化数组模拟,Redis 驱动直接使用原生 Set 结构:

php
// 追加(已存在的值不会重复添加),返回新增数量,全部已存在返回 false
Cache::sAddArray('permissions', ['read', 'write']);
Cache::sAddArray('permissions', 'execute');

// 读取全部成员,集合不存在返回 false
$perms = Cache::getArray('permissions');

// 移除成员,返回移除数量
Cache::sRemoveArray('permissions', ['execute']);

并发注意

File 驱动的集合操作是「读取-合并-回写」三步,并发写入可能互相覆盖;需要严格原子性的集合场景请使用 Redis 驱动。

多缓存商店

config/cache.php 中可注册多个商店(store),通过 store() 按名称切换,商店名不区分大小写:

php
// 操作指定商店
Cache::store('redis')->set('hot:key', $value, 60);

// 运行时注册新商店(支持驱动实例 / 类名 / 配置数组三种形式)
Cache::addStore('memcached', new MemcachedDriver());

// 判断商店是否已注册
Cache::hasStore('redis'); // true

未调用 store() 的所有静态调用(Cache::set()Cache::get() 等)均转发到 default 指定的默认商店。

竞争锁(分布式锁)

lock() 用于串行化并发访问同一临界资源,取锁失败会自动重试,重试耗尽抛出 Viswoole\Cache\Exception\CacheErrorException

参数类型默认值说明
scenestring必填锁场景标识,如 order:create
expireint10锁过期时间(秒),作为进程崩溃未解锁时的兜底
autoUnlockboolfalse驱动连接关闭(close())时是否自动解锁
retryint5取锁失败的重试次数
sleepint|float0.2每次重试的间隔秒数
php
use Viswoole\Cache\Exception\CacheErrorException;
use Viswoole\Cache\Facade\Cache;

try {
    // 取锁:10 秒过期,最多重试 5 次,间隔 0.2 秒
    $lockId = Cache::lock('order:create', 10);

    try {
        // 临界区业务,例如防重复下单
    } finally {
        Cache::unlock($lockId); // 仅锁持有者能解锁
    }
} catch (CacheErrorException) {
    // 重试耗尽仍未取到锁
}

使用要点:

  • 必须用 lock() 返回的锁 ID 解锁unlock($id) 会在持有记录(Redis 驱动按协程隔离)中校验锁 ID,不匹配或已释放返回 false,避免误释放他人的锁。
  • expire 是安全网:即使业务异常且未解锁,锁也会在过期后自动失效,防止死锁。
  • finally 中解锁:保证异常路径也能释放锁;autoUnlock 仅作为进程级兜底。
  • Redis 驱动的锁基于 SET NX EX + Lua 脚本实现「比对锁 ID 再删除」,解锁是原子操作。

最佳实践

Cache-Aside 模式

php
public function getUserProfile(int $userId): array
{
    $cacheKey = "profile:{$userId}";

    $cached = Cache::get($cacheKey);
    if ($cached !== null) {
        return $cached;
    }

    $profile = $this->profileRepo->find($userId); // 回源数据库

    // 空结果也缓存较短时间,缓解缓存穿透
    Cache::tag("user:{$userId}")->set($cacheKey, $profile, $profile === null ? 60 : 3600);

    return $profile;
}

写入时关联标签(tag()),用户资料更新时 Cache::tag("user:{$userId}")->clear() 即可精准失效该用户的全部缓存,详见 缓存标签

热点行防击穿

对「读库 + 回填缓存」的热点接口,可用 set() 的 NX 模式或 lock() 保证同一时刻只有一个请求回源数据库,其余请求等待或读取旧值。

下一步

  • 缓存驱动 —— File / Redis 驱动参数、连接池行为与自定义驱动
  • 缓存标签 —— 按标签分组批量管理缓存
  • 异步任务 —— 结合缓存队列化处理耗时业务