thinkphp不支持开箱即用的「本地+redis」二级缓存,type=>'complex'仅定义多驱动而非自动降级;需手动实现先查本地(如数组/apcu)再查redis的读取逻辑,并统一管理key生命周期与失效同步。

ThinkPHP 本身不提供开箱即用的「本地 + Redis」二级缓存组合,必须手动组装。直接调用 Cache::store('redis') 只走 Redis;想先查内存再查 Redis,得自己写逻辑、控制流程和生命周期。
为什么不能直接用 type => 'complex' 实现二级缓存
ThinkPHP 的 type => 'complex' 配置(如 default + redis store)只是定义多个缓存驱动,并不自动串联读取顺序。它本质是「多选一」或「按需指定」,不是「先本地后远程」的 fallback 流程。比如:
Cache::store('redis')->get('key')
永远只查 Redis;Cache::get('key') 默认走 default 驱动(通常是 file),不会自动降级到 redis。
常见错误现象:配置了 complex 类型后仍出现高并发下 Redis 打满、本地缓存未生效,就是因为没重写读取逻辑。
手动实现两级读取:先查 Swoole Table / APCu / Runtime 数组,再查 Redis
推荐在控制器或服务类中封装一个统一读取方法,避开框架默认缓存链路的限制。以「运行时内存数组」为例(轻量、无需扩展):
-
$localCache声明为静态属性或使用think\Cache的driver('simple')(TP6+ 支持) - 读取时按顺序:查本地 → 命中则返回;未命中 → 查 Redis → 命中则写入本地并返回;都未命中则查 DB 并双写
- 注意本地缓存无自动过期,需手动比对 Redis 中的 TTL 或加时间戳字段
示例片段(TP5.1+):
public function getWithLocalFallback($key, $ttl = 3600)
{
// 1. 先查本地(简单数组模拟)
static $local = [];
if (isset($local[$key]) && time() get($key);
if ($value !== null) {
// 3. 写回本地(带过期时间)
$local[$key] = ['value' => $value, 'expire' => time() + $ttl];
return $value;
}
return null;
}
避免 key 冲突与失效不同步的关键参数
本地缓存和 Redis 缓存用同一套 key 是最常见陷阱——删 Redis 时本地还留着脏数据。必须统一管理 key 生命周期:
- 强制所有写操作走同一个封装方法(如
setTwoLevel($key, $val, $ttl)),同时更新两级 - Redis 配置中务必设
prefix(如'prefix' => 'tp5:cache:'),本地缓存 key 也加相同前缀,便于批量清理 - 禁用
persistent => true(长连接)在 CLI 或 FPM 下可能引发连接复用导致 select db 错乱,尤其多数据库场景 - 不要依赖
expire全局配置值,每个set调用显式传 TTL,否则本地缓存过期时间和 Redis 不一致
生产环境更稳妥的选择:APCu + Redis 组合
比数组更可靠,且支持自动过期(APCu 3.0+)。但需确认 PHP 已启用 APCu 扩展(php -m | grep apcu):
- 配置 APCu 驱动(TP6 支持,TP5 需自定义 driver)
- 读取逻辑不变,只是把
$local替换为apcu_fetch()/apcu_store() - APCu 的 key 过期由系统管理,不用手算时间戳,但要注意 APCu 在 CLI 模式下默认不启用(需配
apc.enable_cli=1) - Redis 侧仍需保留完整 key 管理能力,因为 APCu 无法跨进程共享(FPM 多 worker 下各有一份)
真正难的从来不是“怎么写两行缓存”,而是「什么时候该清本地」「Redis 删了但本地忘了删」「Swoole 热更后 local cache 残留」——这些边界情况几乎全靠业务层兜底,框架不介入。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











