PHP 缓存策略大全:文件缓存 / Redis / Memcached 对比

不语
不语 正式会员正式会员认证极客认证极客
发布于 2026-09-28 15:06 ·5 浏览 ·2 回复

学会这篇,你就能搞清 PHP 里文件缓存、Redis、Memcached 三种方案各自适合什么场景,并能在自己的项目里选出合适的一种落地。

PHP 缓存的本质很简单:把「算一次很贵、但短时间内不会变」的结果存起来,下次直接读。难点不在存,而在选哪种存法。下面按「先动手跑起来,再对比选型」的顺序讲。

第一步:先想清楚缓存什么、键怎么设计

缓存对象通常三类:SQL 查询结果、远程 API 响应、渲染好的 HTML 片段。

键名建议用「模块:对象:参数」的格式,例如 `post:detail:1024`,再统一加个前缀 `mysite:`,方便按前缀批量清理。键名不要塞中文和超长字符串,用 `md5()` 压缩一层更稳。

注意:键名设计错了,后期清理缓存只能整库 flush,会误伤其他业务。

第二步:文件缓存——零依赖,先跑起来

适合单机、访问量不大、缓存内容体积小的场景。核心就是写文件 + 存过期时间:

function cache_get(string $key, int $ttl = 3600) {
    $file = __DIR__ . '/cache/' . md5($key) . '.php';
    if (!is_file($file)) return null;
    $data = include $file;
    if ($data['expire'] < time()) { @unlink($file); return null; }
    return $data['value'];
}

function cache_set(string $key, $value, int $ttl = 3600): void {
    $file = __DIR__ . '/cache/' . md5($key) . '.php';
    $data = ['expire' => time() + $ttl, 'value' => $value];
    file_put_contents($file, '<?php return ' . var_export($data, true) . ';', LOCK_EX);
}

几个实操要点:

  • 缓存目录必须可写(`chmod 755`,属主与 PHP 运行用户一致);
  • 文件名用 `md5()` 后按前两位建子目录,避免单目录几万个文件拖慢 IO;
  • 配套开 OPcache,`.php` 缓存文件会被编译缓存命中,比读纯文本再 `unserialize` 更快。

注意:文件缓存天生不支持多机共享。一旦上了负载均衡,两台机器缓存不一致,脏数据问题会非常难查。

第三步:Redis——功能最全,能当缓存也能当队列

安装(以 Ubuntu 为例):

sudo apt install redis-server php-redis
sudo systemctl enable --now redis-server
redis-cli ping   # 返回 PONG 即成功

宝塔面板用户直接在「软件商店」装 Redis,再到「PHP 扩展」里勾选 redis 即可。装完 `php -m | grep redis` 确认扩展加载。

$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1.0);

$redis->setex('post:detail:1024', 3600, json_encode($post));
$post = json_decode($redis->get('post:detail:1024'), true);

$redis->incr('post:views:1024');   // 计数类场景,Redis 是首选

Redis 的优势在于数据结构丰富:String、Hash、List、Set、ZSet 全都有,点赞集合、排行榜、延迟队列都能直接做;还支持 RDB/AOF 持久化,重启不丢数据。

注意:别把 Redis 当数据库用。缓存一定要设 TTL,否则内存被慢慢吃满,触发 `maxmemory` 淘汰策略时会开始删你的数据。

第四步:Memcached——最纯粹的 KV,胜在简单

sudo apt install memcached php-memcached
sudo systemctl enable --now memcached
$mc = new Memcached();
$mc->addServer('127.0.0.1', 11211);
$mc->set('post:detail:1024', $post, 3600);
$post = $mc->get('post:detail:1024');

Memcached 只做一件事:内存 KV 缓存,多线程模型,纯读写场景下延迟极低。但它没有持久化(重启即清空)、没有复杂数据结构、单条 value 默认上限 1MB。

注意:Memcached 的 key 最长 250 字节,超长 key 直接写入失败且不报错,务必先 `md5()`。

第五步:三者横向对比与选型

维度文件缓存RedisMemcached
依赖无需装服务 + 扩展需装服务 + 扩展
多机共享不支持支持支持
数据结构无(自行序列化)丰富仅 KV
持久化天然持久支持不支持
典型场景单机小站、配置缓存会话、计数、队列、榜单纯页面/查询结果缓存

选型口诀:**单机小流量选文件,要共享或要复杂结构选 Redis,只想要最简单最快的 KV 选 Memcached**。新项目若无历史包袱,直接上 Redis,省得以后迁移。

第六步:写一层统一封装,别把驱动写死

在业务代码里直接 `new Redis()` 是灾难的开始。建议定义 `Cache::get()/set()/delete()` 三个方法,底层驱动可切换。

如果你维护的是 Clara BBS 这类无框架系统,插件里已经内置了 `Cache::remember(键, TTL, 回调)` 这类公共设施,直接调用即可,不必自己造轮子;后台「系统工具 → 缓存清理」用于清数据缓存,而插件是运行时钩子加载、保存即生效的,不存在编译缓存要清的问题。

注意:无论用哪种,都要防缓存穿透(查不到也缓存空值一小会儿)和雪崩(TTL 加随机抖动,别整点集体过期)。

小结

  • 文件缓存零依赖,适合单机;记得分目录 + 开 OPcache。
  • Redis 功能最全、支持持久化,是多机部署的默认答案;装完用 `redis-cli ping` 验证。
  • Memcached 简单快,但无持久化、无复杂结构、value 有 1MB 上限。
  • 缓存 key 统一前缀 + `md5()`,TTL 必设且加随机抖动。
  • 业务层包一层 `Cache` 封装,换驱动不改业务代码。
本文转载自 Clara轻量论坛系统 - 轻量级 PHP 论坛系统,原文地址:https://www.leleweb.cn/thread-623.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
CLARA轻量论坛系统

全部回复 2

ipzh
ipzh 正式会员正式会员认证极客认证极客 Lv2 1楼 2026-09-28 15:16

选型就一句话:单机小站「文件缓存 + OPcache」足够,多机或需要原子操作/队列直接上 Redis,Memcached 只在你已有存量运维、又纯做 KV 高并发读时才值得留,新项目没必要选它。

补一下 Memcached 这块的边界,帖子里刚好断在这:它的优势是多线程 + 纯内存,读并发确实猛,做 HTML 片段和计数器很合适;但它是纯 KV,value 默认上限 1MB,没有 scan、没有持久化、没有复杂结构,一旦你要排行榜、分布式锁、消息队列,就得再搭一套 Redis,等于维护两套中间件。所以除非团队已有积累,直接 Redis 全家桶更省事。

帖子里文件缓存那段有个隐患值得补:`file_put_contents(..., LOCK_EX)` 只保证写端互斥,读端的 `include` 并不参与锁。A 进程正在写、B 进程 include 读到半个文件,会直接 parse error,而且 include 的报错很难优雅兜住。正确姿势是先写临时文件再 `rename()` 原子替换(同分区 rename 是原子的),读端永远只能看到完整文件。另外 md5 建议取两层子目录(前两位 / 次两位),单目录文件数才控得住。

Redis 侧三个必踩的:生产禁用 `keys *`,一律 `scan` 游标;大 key 删除会阻塞,用 `unlink()` 异步删;雪崩给 TTL 加随机抖动、击穿用 setnx 单飞重建、穿透缓存空值。还有 `maxmemory` 一定要设死并配 `allkeys-lru`,不然内存打满触发 swap,全站一起拖死。

最后提一句:如果是在 Clara 上写插件,不用自己造这套轮子,它内置了 `Cache::remember` 公共设施,键名前缀和清理交给框架处理就行。另外 OPcache 上线后记得把 `validate_timestamps` 关掉(发布时 reload php-fpm),否则每次请求都 stat 一遍文件,缓存收益要大打折扣。

shandian
shandian 见习用户见习用户 #228 2楼 2026-09-28 15:25
ipzh:选型就一句话:单机小站「文件缓存 + OPcache」足够,多机或需要原子操作/队列直接上 Redis,Memcached 只在你已有存量运维、又纯做 KV 高…

ipzh 这几条基本都对,但有个隐藏冲突需要点出来:帖子前面推荐的 `.php` 文件缓存 + 后面建议的关闭 `validate_timestamps`,两个一起用会让缓存永不更新。

原因是 OPcache 是按 realpath 缓存编译结果的,`rename()` 换掉了 inode 但路径没变;关掉 `validate_timestamps` 后它不再 stat 文件,于是永远吐旧字节码。你那套「原子替换」写入逻辑是对的,但读端如果走 `include`,必须配 `opcache_invalidate($file, true)`,或者干脆别用 `.php` 存缓存——改成 `serialize()` 写 `.txt`/`.dat`,读出来 `unserialize()`,就绕开了 OPcache 这层,代价是多一次反序列化。真要用 `.php` 又想要 OPcache 收益,也可以保留 `validate_timestamps`,把 `revalidate_freq` 设成 2~5 秒,用一点点 stat 换正确性。

`rename()` 那点再补一句:原子性只在同分区成立,临时文件必须建在缓存目录同一层,别用系统 `/tmp`(很多机器上是独立分区或 tmpfs,跨设备 rename 会退化成拷贝)。另外多个进程并发写时各写各的临时文件、最后一个 rename 赢,这个"无锁竞争"是安全的,但临时文件名要么带 pid,要么 `tempnam()`,别复用固定名。

Clara 这边 `Cache::remember` 确实是内置的公共设施,插件里直接调就行,不用自己糊一套;不过它内部具体的前缀规则和清理粒度我没翻过源码,不敢替它打包票,接入前建议自己压一遍过期行为。定时清理也可以挂 `Cron::register`,不用额外配 crontab。

最后一个小坑:Redis 如果只当缓存用,别开 AOF,缓存丢了重建就行,持久化纯属白白吃 IO 和 fork 开销——只有当它同时兼任队列或计数账本时才值得开。