php无内置读写分离缓存,实为开发者设计的两级缓存策略:APCu作本地读缓存、redis作中心写缓存,需同步清理多进程APCu并避免误用OPcache。

PHP 本身没有内置的“读写分离缓存”机制,所谓“读写分离缓存”不是标准术语,而是开发者为缓解缓存雪崩、穿透或主存储压力,人为将 read 和 write 操作路由到不同缓存层(比如本地内存 + 分布式 Redis)的一种策略。它不等于数据库读写分离,也不依赖 PHP 语言特性,关键在逻辑设计和缓存选型。
什么时候该考虑两级缓存架构(本地 + 远程)
单靠 Redis 或 memcached 在高并发读场景下容易成为网络瓶颈;而全用 apcu 或 opcache 又无法跨进程共享,更新不一致。两级缓存本质是用本地缓存扛读、远程缓存保一致:
- 适合读多写少、数据变更不频繁但对读响应要求极高的场景(如商品基础信息、配置项)
- 不适合实时性极强的数据(如秒杀库存),因为本地缓存会延迟失效
- 必须配合合理的失效策略(主动删除远端 + 清空本地,或设置短 TTL + 后台异步刷新)
apcu 作本地读缓存 + redis 作中心写缓存怎么配
典型组合:用 apcu 存高频只读副本,所有写操作先落 redis,再清除本机 apcu 对应 key;读时优先查 apcu,未命中再查 redis 并回填。
注意点:
立即学习“PHP免费学习笔记(深入)”;
-
apcu是 per-process 的,FPM 下每个 worker 有独立内存空间,apcu_clear_cache()只清当前进程,不能广播清理 - 写操作后不能只删
rediskey,必须同步触发本机apcu_delete($key)(否则其他请求仍可能读到旧值) - 推荐用
apcu_entry()带回调的方式读取,避免重复查redis和重复写apcu - 示例片段:
function get_user_info($uid) { $key = "user:{$uid}"; return apcu_entry($key, function() use ($key, $uid) { // 回调里查 redis,失败则返回 null,apcu 不缓存 $data = redis()->get($key); if ($data === false) { $data = fetch_from_db($uid); // 回源 if ($data) { redis()->setex($key, 3600, json_encode($data)); } } return $data ? json_decode($data, true) : null; }, 600); // apcu 缓存 10 分钟 }
为什么不要用 opcache 当缓存容器
opcache 是为加速 PHP 脚本编译而设的,不是通用键值缓存。它不支持按 key 删除、不提供 TTL、无法序列化复杂结构,且启用 opcache.file_cache_only=1 时甚至不支持运行时写入。
- 误用
opcache_get_status()['cache_full']判断缓存容量?那是 opcode 缓存区满载状态,和业务数据无关 - 试图用
opcache_compile_file()缓存 JSON?会直接报错或静默失败 - 真正需要的是
apcu(共享内存)或memcached(分布式),不是opcache
缓存失效时最容易漏掉的一步
多数人记得删 Redis,却忘了通知所有 PHP-FPM worker 清除本地 apcu。没有跨进程通信机制的话,只能靠“懒清除”——让下次读请求发现过期后自动回源更新,但这会造成短暂不一致。
- 可行解法之一:用 Redis 的
PUB/SUB通道广播失效事件,各 worker 订阅后执行apcu_delete() - 更轻量做法:写操作后,在 Redis 写一个带时间戳的
version:user:123key,读时先比对版本号再决定是否跳过apcu - 绝对别用
sleep(1)等几毫秒再删本地缓存——既不可靠又拖慢响应
真正的难点不在代码怎么写,而在判断哪些数据值得两级缓存、TTL 设多长、失效广播要不要加、以及能否接受几秒内的脏读。这些没法套模板,得看监控里的缓存命中率、Redis 延迟、APCU 内存占用三个指标来回调。