缓存驱动
Viswoole 缓存采用驱动(Driver)架构:所有存储后端实现统一的 Viswoole\Cache\Contract\CacheDriverInterface 契约,由 CacheManager 注册为「商店(Store)」并提供统一调用入口。本篇说明内置 File / Redis 驱动的配置参数与扩展方式。
驱动体系
CacheDriverInterface(契约,定义全部缓存操作)
▲
Driver(抽象基类:键前缀、标签、序列化、过期换算等通用能力)
├── Driver\File 文件驱动
└── Driver\Redis Redis 驱动(协程连接池)抽象基类 Viswoole\Cache\Driver 已封装标签(tag() / getTags())、键前缀(getCacheKey())、序列化定制(setSerialize())等横切逻辑,具体驱动只需实现存储差异部分。自定义驱动实现接口或继承基类均可。
配置驱动商店
驱动在 config/cache.php 的 stores 中注册,default 指定默认商店。每个商店支持三种定义形式:
use Viswoole\Cache\Driver\Redis;
use Viswoole\Cache\Facade\Cache;
return [
'default' => env('cache.store', 'file'),
'stores' => [
// 形式一:驱动类名(无参构造)
'file' => Cache::FILE_DRIVER,
// 形式二:配置数组,options 为驱动构造参数(键名与构造参数一致)
'redis' => [
'driver' => Cache::REDIS_DRIVER,
'options' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
],
],
// 形式三:驱动实例
'memory' => new Redis(host: '127.0.0.1'),
],
];| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| default | string | 首个商店名 | 默认商店名称,不区分大小写 |
| stores | array | [] | 商店列表,键为商店名,值为上述三种形式之一 |
商店名内部统一按小写索引;驱动类不存在、options 格式错误或未实现 CacheDriverInterface 时抛出 CacheErrorException。
File 驱动
Viswoole\Cache\Driver\File 将缓存以序列化形式写入本地文件,无需外部依赖。TTL 通过文件头部嵌入的 expire(时间戳) 标记实现,读取时惰性检测过期并删除文件;竞争锁基于 flock 文件锁实现。
构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| storage | string | BASE_PATH.'/runtime/cache' | 缓存文件存储根目录 |
| prefix | string | '' | 缓存键前缀,用于命名空间隔离 |
| tag_prefix | string | tag: | 标签键前缀 |
| tag_store | string | TAG_STORE | 标签仓库键名 |
| expire | int | 0 | 默认过期时间(秒),0 表示永不过期 |
配置示例:
'stores' => [
'file' => [
'driver' => Cache::FILE_DRIVER,
'options' => [
'storage' => BASE_PATH . '/runtime/cache',
'prefix' => 'app:',
'expire' => 3600,
],
],
],存储结构
缓存键即文件名,键中可用 / 建立子目录层级:
runtime/cache/
├── user:1 # 普通缓存文件
├── sub/
│ └── config:site # 键含 / 时自动建立子目录
├── lock/ # 竞争锁目录
│ └── lock_order:create # 锁文件名为 lock_{scene}
└── tag:user:1 # 标签集合文件缓存键限制
File 驱动禁止缓存键中出现 .. 与反斜杠 \(路径穿越防护),命中时抛出 InvalidArgumentException。
适用场景
- 开发与测试环境快速验证
- 单机部署的小型应用
- 无 Redis 等外部依赖的轻量场景
Redis 驱动
Viswoole\Cache\Driver\Redis 基于 Redis 原生命令实现 TTL、原子自增自减与分布式锁(SET NX EX + Lua 比对删除),并自带协程连接池。
构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| host | string | 127.0.0.1 | Redis 服务器地址 |
| port | int | 6379 | Redis 服务器端口 |
| password | string | '' | 认证密码,空字符串表示无密码 |
| db_index | int | 0 | 数据库索引(0-15) |
| timeout | float | 0 | 连接超时时间(秒),0 表示不限制 |
| retry_interval | int | 1000 | 重连间隔(毫秒) |
| read_timeout | float | 0 | 读取超时时间(秒) |
| prefix | string | '' | 缓存键前缀 |
| tag_prefix | string | tag: | 标签键前缀 |
| expire | int | 0 | 默认过期时间(秒),0 表示永不过期 |
| tag_store | string | TAG_STORE | 标签仓库键名 |
| pool_max_size | int | 10 | 连接池最大连接数 |
| pool_fill_size | int | 0 | 连接池预填充连接数 |
配置示例:
'redis' => [
'driver' => Cache::REDIS_DRIVER,
'options' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => 6379,
'password' => env('REDIS_PASSWORD', ''),
'db_index' => 0,
'prefix' => 'app:',
'pool_max_size' => 64,
'pool_fill_size' => 10,
],
],连接池与协程隔离
驱动底层通过 Viswoole\Cache\RedisPool(继承通用连接池 ConnectionPool)管理连接,容量由 pool_max_size / pool_fill_size 控制:
- 连接按协程借出:同一协程内的多次缓存操作复用同一条连接,避免协议串包
- 协程结束(含异常退出)时通过
defer自动归还连接,杜绝连接滞留 - 锁的持有记录同样按协程隔离,并发协程不会误释放彼此的锁
Master/Manager 进程短连接
连接池归 Worker 进程所有:连接只在 Worker/TaskWorker 触发 workerStart 时独立建立与计数,Master/Manager 进程不持有池连接。若在服务启动阶段或主进程回调(onStart / onManagerStart 等)中使用 Cache,每次操作走一次性短连接(用完即毁),总连接数不会额外增加。命令行脚本中的 Redis 连接复用为进程级,复用前自动检测存活,断线后自动重建(含密码认证与库选择的会话状态恢复)。高频缓存操作场景请通过 Task::emit 投递到工作进程执行。
连接参数(主机、密码、超时等)由 Viswoole\Cache\RedisConfig 值对象承载,可脱离门面单独使用 new RedisPool(new RedisConfig(...)) 构建连接池。
数据类型处理
Cache::set('counter', 42); // 整数跳过序列化,由 Redis 原生存储
Cache::get('counter'); // int(42)
Cache::set('price', 99.99); // 浮点数走标准序列化
Cache::get('price'); // float(99.99)
Cache::set('user', ['n' => '张三']); // 复杂结构走标准序列化数字字符串会被还原为数值
Redis 以字符串承载所有值,驱动读取时会将数字字符串自动转为 int / float。因此写入纯数字字符串(如 '123')读回将得到 int(123),此类数据请改用数组或加前缀存储。
适用场景
- 生产环境高性能缓存
- 多实例分布式部署共享缓存
- 计数器、集合等需要原子操作的模型
- 高并发分布式锁
驱动对比
| 特性 | File | Redis |
|---|---|---|
| 读写性能 | 低(磁盘 IO) | 高(内存操作) |
| 外部依赖 | 无 | Redis 服务 + phpredis 扩展 |
| 分布式共享 | 不支持 | 原生支持 |
| 集合操作原子性 | 模拟(读改写) | 原生 Set 命令 |
| 连接管理 | 无 | 协程连接池 |
| 推荐环境 | 开发 / 测试 | 生产 |
自定义驱动
当内置驱动无法满足需求(如 Memcached、本地内存缓存)时可自行扩展,有两种方式:
方式一:继承 Driver 基类(推荐)
基类已处理标签、前缀、序列化等通用逻辑,只需实现存储相关的抽象方法:get、set、inc、dec、pull、has、delete、clear、ttl、lock、unlock、close、connect、sAddArray、getArray、sRemoveArray。
namespace App\Cache;
use DateTime;
use Viswoole\Cache\Driver;
class MemoryDriver extends Driver
{
/** @var array<string, mixed> 进程内存储(仅示例) */
private array $items = [];
public function get(string $key, mixed $default = null): mixed
{
$realKey = $this->getCacheKey($key); // 基类提供:拼接键前缀
return isset($this->items[$realKey]) ? $this->items[$realKey] : $default;
}
public function set(
string $key,
mixed $value,
DateTime|int|null $expire = null,
bool $NX = false
): bool {
// 按 $NX 与过期时间实现写入逻辑
return true;
}
// 其余抽象方法按接口契约逐一实现...
}方式二:直接实现 CacheDriverInterface
不继承基类、完全自行实现 CacheDriverInterface 的全部方法(含标签与锁)。适合需要彻底自定义键规则或标签语义的场景。
注册与使用
// 方式一:写入 config/cache.php(推荐)
'stores' => [
'memory' => [
'driver' => \App\Cache\MemoryDriver::class,
'options' => ['prefix' => 'app:'],
],
],
// 方式二:运行时注册
Cache::addStore('memory', new \App\Cache\MemoryDriver());Cache::store('memory')->set('key', 'value', 60);序列化定制
默认使用 PHP serialize / unserialize。出于对象注入(CWE-502)防护或跨语言读取的考虑,可通过 setSerialize() 切换为 JSON 等格式:
Cache::store('memory')->setSerialize(
fn(mixed $data): string => json_encode($data, JSON_UNESCAPED_UNICODE),
fn(string $data): mixed => json_decode($data, true)
);存储介质安全
缓存介质(runtime 目录、Redis)一旦被同权限进程篡改,基于 unserialize 的默认方案可能构成反序列化攻击面。请确保存储介质的访问控制,安全敏感场景建议切换为 JSON 序列化。
