apcu比redis快10倍以上,因无网络io和序列化开销,但无法替代redis:它不跨进程、不持久、无精确ttl、不支持集群通知,仅适合作为redis的本地只读副本用于读多写少场景。

直接上结论:单机 PHP 应用或读多写少的配置类数据,优先用 APCu;需要跨进程/多服务器共享、高一致性要求的数据(如用户会话、商品库存),必须用 Redis;Memcached 可以用,但除非已有运维习惯,否则没必要主动选它。
为什么 APCu 比 Redis 快得多,却不能替代它?
APCu 是 PHP 进程内共享内存缓存,所有操作都在同一块内存地址完成,没有序列化、网络 IO、TCP 握手这些开销。实测读取一个 1KB 的字符串,APCu 耗时通常在 0.01–0.03ms,而本地部署的 Redis(127.0.0.1)也要 0.2–0.5ms —— 差 10 倍以上。
但它有硬伤:
- PHP-FPM 多 worker 场景下,每个 worker 有独立的
APCu内存空间,worker A写入的缓存,worker B看不见 - 重启 PHP-FPM 或 Apache/Nginx 后,全部缓存清空
- 不支持过期时间的精确控制(
apcu_cache_info()返回的 TTL 是估算值,实际清理依赖 LRU 和内存压力) - 无法做集群间通知,比如你用 Swoole 长连接 + 多个 HTTP worker,更新了配置,其他 worker 不会自动失效旧缓存
Redis setex 和 setnx 在缓存穿透/击穿场景下怎么选?
缓存穿透(查不存在的 key)和缓存击穿(热点 key 过期瞬间大量请求打穿)是真实压测中高频出问题的点,光靠 setex 不够。
正确姿势:
- 对「确定不存在」的数据(比如查不到的用户 ID),也写入一个空值 + 短过期(如 60 秒),用
setex('user:999999', 60, 'null'),避免反复穿透 - 对热点 key 过期问题,不要只依赖
setex,改用set('key', $value, ['NX', 'EX' => 300])(即SET key value NX EX 300),保证只有一个请求能写入新值,其余阻塞后直接读缓存 - 别用
get + expire两步操作来更新过期时间 —— 中间可能被其他请求清掉,造成逻辑错乱
本地缓存 + Redis 组合时,如何避免双写不一致?
常见错误是:先写数据库,再删 Redis,最后更新 APCu —— 三步之间任何一步失败或重排序,就会导致某一层缓存脏了。
更稳的做法是「懒更新 + 版本戳」:
- 所有写操作只更新数据库,然后 删掉 Redis 和 APCu 中对应 key(不是更新,是删)
- 读请求进来时,先查 APCu;没命中,再查 Redis;都没命中,才查 DB,并把结果同时写入 Redis(带
EX)和 APCu(不带过期,靠apcu_clear_cache()或信号触发清理) - 关键字段加
version字段,比如用户表加cache_version,每次更新 DB 时自增;读缓存时带上这个版本号拼成 key,如user:123:v2,避免旧缓存残留
这个模式下,APCu 其实只是 Redis 的“本地副本”,不承担一致性责任,只负责加速重复读 —— 所以它脏了也没关系,下次自然重建。
小规模项目里,用文件缓存真比不上 APCu 吗?
用 file_put_contents('cache/user_123.json', json_encode($data)) 这种方式,在并发写入时大概率丢数据,因为 PHP 默认不加文件锁。即使加了 flock(),也会变成串行写,QPS 上不去。
而 APCu 是基于共享内存的原子操作,天生支持并发读写。只要你的 PHP 是 7.4+、启用了 apcu.enabled=1 且没禁用 apcu.slam_defense=0(默认已关),它就是最轻量又可靠的本地缓存选择。
唯一要注意的是:别把大对象(比如 >1MB 的数组)往 APCu 里塞,它会显著拖慢 GC,甚至触发 OOM —— 这种数据该进 Redis,别贪快硬塞。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











