APCu 缓存本身不会直接导致内存溢出,但配置不当或滥用会加速内存耗尽;其共享内存独立于 memory_limit,需合理设置 apcu.shm_size、apcu.ttl 和 apcu.gc_ttl,并限制单条缓存大小、监控使用率及主动清理。

APCu 缓存本身不会直接导致内存溢出,但配置不当或滥用会加速内存耗尽
APCu 是用户级内存缓存(apcu_store / apcu_fetch),它把数据存在 PHP 进程共享内存里,不经过序列化、无网络开销,快是真快——但它的内存是“吃进去就不吐”的。一旦你缓存了 10 万个 2MB 的数组,哪怕只存 1 分钟,也立刻压爆 memory_limit。这不是 APCu 的 bug,而是你没给它划边界。
- APCu 不受
memory_limit管控:它用的是独立共享内存段(由apc.shm_size或apcu.shm_size控制),PHP 脚本的内存限制对它无效 - 默认配置极危险:很多发行版预装 APCu 时设
apcu.shm_size=32M,但没配apcu.ttl和apcu.gc_ttl,缓存永不回收 - 缓存键爆炸式增长:比如用时间戳+用户ID拼 key:
"user_{$uid}_{$time}",每秒生成新 key,旧 key 永不淘汰 → 内存只增不减
必须改的三个 APCu 配置项(php.ini)
别只调 memory_limit,那是治脚痛医头。真正要动的是 APCu 自己的“胃容量”和“消化规则”:
-
apcu.shm_size=64M:共享内存总大小,建议 64M–256M(根据服务器总内存按 5%–10% 设)。超过此值写入失败,但不会报错,apcu_store()返回false—— 很多代码没判这个,就默默失效 -
apcu.ttl=3600:每个缓存项默认存活时间(秒)。设为 0 表示永不过期,等于埋雷;生产环境务必设合理值(如 10 分钟=600,1 小时=3600) -
apcu.gc_ttl=3600:垃圾回收触发间隔(秒)。APCu 不是实时删过期 key,而是每gc_ttl秒扫一次内存。若gc_ttl > ttl,过期 key 可能卡住内存数小时
改完记得重启 PHP-FPM 或 Apache:APCu 共享内存段在进程启动时分配,热加载不生效。
代码层防溢出:存之前先问“值够小吗?”
APCu 存的是原始 PHP 变量,大数组、对象、资源句柄全照单全收。一个 file_get_contents('big.log') 直接塞进去,30MB 内存瞬间占满。
立即学习“PHP免费学习笔记(深入)”;
- 强制限制单条缓存大小:用
strlen(serialize($data))或memory_get_usage(true)估算(注意:后者不准,仅作粗筛) - 拒绝超限写入:
if (strlen(serialize($data)) > 1024 * 1024) { // 超 1MB 不缓存 return $data; } apcu_store($key, $data, $ttl); - 避免缓存动态生成的大结构:比如
get_defined_vars()、debug_backtrace()、未分页的mysqli_fetch_all()结果集
监控与兜底:怎么知道 APCu 快撑爆了?
靠日志猜不行,得看实时指标。APCu 提供 apcu_cache_info(),返回当前内存使用详情:
-
apcu_cache_info()['mem_size']:已用字节数;对比apcu_cache_info()['mem_size'] / apcu_cache_info()['num_slots']可看平均槽位占用,过高说明碎片严重 -
apcu_sma_info():更底层的共享内存段信息(含碎片率seg_size和avail_mem) - 加个健康检查路由:
// /health/apcu $info = apcu_cache_info(); if ($info['mem_size'] > 0.8 * $info['mem_size_total']) { http_response_code(503); echo "APCu memory usage > 80%"; }
最易被忽略的一点:APCu 的内存不随 PHP 请求结束而释放,它跨请求、跨进程存在。所以“脚本执行完内存就清了”这种想法,在 APCu 场景下完全不成立——你得靠 ttl + gc_ttl + 主动 apcu_delete() 三重控制,缺一不可。











