本文目录导读:

在 PHP 项目中选择缓存策略,核心原则是按数据特征分层、按读写比例选择介质,不存在“万能药”,需要根据数据的变化频率、访问频率和一致性要求来决定。
以下是选择缓存策略的完整决策指南:
第一步:明确缓存分层(由快到慢)
PHP 缓存通常分为四个层级,速度递减,容量递增:
- 第一层:进程内缓存(OPcache + 内存变量)
- PHP OPcache:缓存编译后的字节码,必开,这是性价比最高的优化,能提升 30%-50% 的 CPU 开销。
- 运行时内存(如
apcu):缓存热点数据,如配置项、语言包,速度最快(纳秒级),但单机内存有限,且不跨进程同步。
- 第二层:本地集中式缓存(Redis/Memcached/Tair)
- Redis:首选,支持丰富的数据结构(String、Hash、List、Set、ZSet),支持持久化和过期策略。
- Memcached:纯 KV 存储,内存利用率极高,适合存简单字符串,但无法持久化。
- 第三层:数据库查询缓存 / 物化视图
针对复杂的 SQL 查询,将查询结果缓存在 Redis 中(手动实现),或者利用 MySQL 的 Query Cache(8.0 已废弃,不建议依赖)。
- 第四层:HTTP 缓存(CDN + 浏览器)
- 针对静态资源(CSS/JS/图片)和页面片段,通过 HTTP 头(
Cache-Control、ETag)交给 Nginx 或 CDN 缓存,甚至直接在浏览器端缓存。
- 针对静态资源(CSS/JS/图片)和页面片段,通过 HTTP 头(
第二步:按业务场景选择策略(核心决策)
| 业务场景 | 数据特征 | 推荐策略 | 实现要点 |
|---|---|---|---|
| 配置信息 (站点设置、支付参数) |
读多写极少,全局一致 | 进程内缓存 (APCu) | 启动时加载,写入时清空 APCu。 若有多台服务器,用 Redis 兜底。 |
| 用户会话 (Session) | 高频读写,可容忍极短延迟 | Redis / Memcached | 替换默认文件存储,解决多服务器登录状态同步问题。 建议设置 TTL(30分钟)。 |
| 热点新闻/商品详情 | 读多写少,允许短暂延迟 | Redis 全量缓存 + 失效重建 | 调用时取缓存,若不存在则查库并写入。 防击穿:使用互斥锁(Redis SETNX)控制重建过程。 |
| 排行榜 (热门文章、销量) |
写频繁,读密集,实时性要求高 | Redis ZSet(有序集合) | 写入时直接 ZADD 更新分数,读取时 ZREVRANGE 获取前 N 名。无需数据库排序,性能极高。 |
| 计数器 (点赞、浏览量、库存) |
高并发写,不允许超卖 | Redis Lua 脚本 / INCR 原子操作 | 使用 INCR 直接操作 Redis 计数,定期(如每5分钟)异步同步到 MySQL。高并发秒杀需配合 Lua 保证原子性。 |
| 复杂列表/分页 (用户订单列表) |
查询条件多,数据量大 | Redis String/Hash 序列化 + 最近最少使用(LRU)淘汰 | 将 SQL 的 MD5 作为 Key,结果 JSON 序列化存入。 防雪崩:设置随机过期时间(基础 TTL + 随机数)。 |
| 完整 HTML 页面 (整页静态化) |
极端读多写少,内容变化慢 | Redis 存储 HTML 片段 / Nginx 静态文件 | 伪静态化:后台编辑保存时,生成 HTML 存入 Redis 或写文件。 一旦写入,后续 HTTP 请求直接返回,PHP 几乎不参与。 |
| 数据库连接/查询结果 | 查询复杂,耗时较长 | Redis 查询结果缓存 (手动) | 仅缓存高频且结果集小于 1MB 的查询。 必须建立缓存失效机制(删除、更新操作时主动清理相关 Key)。 |
第三步:必须解决的三个缓存难题(陷阱)
如果选择了 Redis 缓存,以下三个问题决定了系统的稳定性:
-
缓存穿透(查询不存在的数据)
- 现象:请求一个不存在的商品 ID,每次都打数据库。
- 方案:
- 空值缓存:缓存一个空对象,TTL 设为 5 分钟。
- 布隆过滤器:启动时加载所有存在的 ID,过滤掉肯定不存在的请求。
-
缓存击穿(热点 Key 失效)
- 现象:某个超级热点 Key(如首页数据)在过期瞬间,大量请求同时打到数据库。
- 方案:
- 互斥锁:进程内判断 Key 不存在时,先加锁(
SET NX EX),只有拿到锁的进程去查库并重建,其他进程等待锁释放后直接读缓存。
- 互斥锁:进程内判断 Key 不存在时,先加锁(
-
缓存雪崩(大量 Key 同时失效)
- 现象:设置的 TTL 相同,导致同一时间大量缓存过期。
- 方案:
- 随机 TTL:
TTL = base_time + random(0, 300)秒。 - 多级缓存:本地进程缓存(APCu) 作为第一级,Redis 作为第二级,即使 Redis 挂了,本地缓存也能扛住流量。
- 随机 TTL:
第四步:技术选型清单(落地参考)
- PHP 8.x + OpCache:确保
opcache.enable=1,并设置合理的opcache.memory_consumption=128。 - Redis 6.0+:
- 存储复杂结构用 Redis(Hash/ZSet)。
- 必须开启持久化(RDB + AOF)。
- 设置
maxmemory-policy allkeys-lru作为兜底策略。
- APCu 拓展:如果服务器内存 > 2GB,建议开启 APCu 用于本地缓存。
- 依赖注入容器:使用 PHP-DI 或 Laravel Container,但不要将缓存容器本身放进容器,以免引起循环依赖。
第五步:监控与调优(用什么来判断好坏)
- 命中率监控:Redis 的
INFO stats查看keyspace_hits和keyspace_misses,命中率低于 80% 说明缓存策略需要优化(可能是 TTL 太短或 Key 设计不合理)。 - Redis 内存峰值:使用
redis-cli --stat监控,内存超过 80% 时应考虑集群扩容。 - 慢日志:开启 Redis 的
slowlog,排查是否有大 Key 操作(如HGETALL大 Hash)。
选择顺序建议
如果你是中小项目,按以下顺序逐个加缓存,不要一开始就全上:
- 第一优先级:开启 PHP OPcache(零成本,立竿见影)。
- 第二优先级:用 Redis 缓存 Session 和 热点查询(用户相关数据、商品列表)。
- 第三优先级:当高并发出现时,引入 APCu 缓存配置和语言包,减轻 Redis 网络开销。
- 第四优先级:对于稳定的页面,做 整页静态化 或 CDN 加速。
最重要的是:缓存永远只是加速器,数据库(MySQL)才是数据源,在设计缓存 Key 时,务必确保修改数据(写/删)时,能够准确通知并清除相关的缓存 Key,避免脏读。