多级缓存是人为设计的层级策略,非php内置概念;redis作主缓存(快、易淘汰),APCu/OPcache作从缓存(进程内、零网络开销),需手动控制读写路径,不可依赖redis主从复制实现。

PHP 多级缓存本身没有内置“主从”概念,所谓“多级主从缓存”是人为设计的缓存层级策略,核心在于让 Redis(或 memcached)作主缓存(快、易淘汰),APCu 或 OPcache 作从缓存(进程内、零网络开销),二者不自动同步,需手动控制读写路径。
为什么不能直接用 Redis 主从复制来实现“多级缓存”
Redis 主从复制解决的是高可用和读扩展,不是缓存层级。它让多个 Redis 实例内容一致,但仍是同一层(远程存储层)。而“多级缓存”的“级”指访问路径的远近:L1(内存内)→ L2(本地 socket)→ L3(远程网络)。混淆这两者会导致缓存一致性失控、更新遗漏。
-
Redis主从节点间有复制延迟,SET后立刻GET可能读到旧值,不适合做“从”来兜底 - APCu 不支持跨进程共享,无法被其他 PHP worker 直接读取,天然只能做“本进程专属从缓存”
- OPcache 缓存的是 PHP 字节码,不能存业务数据,别误当成通用缓存层用
典型三级结构怎么搭:APCu + Redis + mysql
这是最实用、落地最多的组合:APCu 抗热点读、Redis 做共享状态、MySQL 为源。关键不在“搭”,而在“路由逻辑”——每次读写都得明确走哪一层。
- 读流程:
apcu_fetch($key)→ 命中则返回;未命中 →$redis->get($key)→ 命中则写回 APCu 并返回;都未命中 → 查 DB,写入 Redis 和 APCu - 写流程:必须穿透所有相关层:先
$redis->del($key),再apcu_delete($key),最后更新 DB;不要只删 Redis 留着 APCu 脏数据 - 注意 TTL 设置:APCu 的过期时间应 ≤ Redis 的,否则会出现“Redis 已过期,APCu 还在返回脏数据”的情况
APCu 作为“从”时的坑:进程隔离与预热失效
APCu 是 per-process 的,FPM 模式下每个 worker 都有一份独立副本。这意味着:
立即学习“PHP免费学习笔记(深入)”;
- 刚启动的 worker 不会自动继承其他 worker 的 APCu 数据,冷启动时大量请求会穿透到 Redis/DB
- 不能依赖
apcu_store()后另一个请求就一定能apcu_fetch()到,除非是同一个 worker 处理的连续请求 - 若用
opcache.preload预加载脚本,它不影响 APCu 用户缓存,别指望 preload 能“预热”业务数据
缓解办法:对超高频 key(如配置项),可在 FPM 启动时用 apcu_store() 批量初始化;或改用 apcu_bc(兼容旧版 API)配合 apcu_cache_info() 监控命中率。
要不要加本地文件缓存做“第四级”?
一般不建议。文件 I/O 慢、并发锁麻烦、清理机制难做,且和 APCu 定位重叠。唯一适用场景是:需要跨 PHP 进程共享、又不想上 Redis 的极简部署(如单机 CLI 脚本集群)。此时用 file_get_contents() + flock() + 序列化,但务必加 clearstatcache() 避免文件修改时间缓存导致误判过期。
真正容易被忽略的是缓存键的设计:同一业务数据在 APCu、Redis 中的 $key 必须完全一致(包括前缀、序列化方式、大小写),否则层级就断了。比如 Redis 用 user:123,APCu 却用 USER_123,那就不是多级,是两套独立缓存。