本文目录导读:

- 避免重复的高成本数据库查询
- 减轻数据库的写入压力
- 缓存 PHP 会话(Session)
- 缓存复杂的计算结果(数据聚合/视图块)
- 为什么是 Memcached 或 Redis?而不是其他?
- 一个简单的例子(未使用 VS 使用缓存)
- 性能提升的关键
在 PHP 项目中,合理使用缓存(如 Memcached 或 Redis)能够大幅提升性能,核心原因在于它有效解决了 “I/O 瓶颈” 和 “计算重复” 这两个主要问题。
我们可以从以下几个角度来理解:
避免重复的高成本数据库查询
这是最直观、也是效益最大的应用场景。
- 问题: 对于一个典型的 PHP 项目,每次用户请求一个页面,都可能会执行多次数据库查询,查询用户信息、文章列表、分类导航等,即使是同一个用户,每次刷新页面,这些 SQL 查询都会重新执行一次。
- 数据库查询是磁盘 I/O 操作(即使有内存缓冲,也有网络开销和 SQL 解析成本)。
- 数据库连接数是有限的,高并发时,PHP 进程会等待数据库响应,导致响应时间急剧上升。
- 缓存的解决方案: 将查询结果(如用户信息、文章列表)存储在 Redis 或 Memcached 的内存中,当下一个请求到来时,PHP 代码优先从缓存中读取。
- 如果缓存命中(Cache Hit):PHP 直接从内存中获取数据,耗时通常在 毫秒甚至微秒级,完全避免了一次数据库查询。
- 如果缓存未命中(Cache Miss):再去查询数据库,然后将结果写入缓存,并设置一个过期时间(TTL)。
- 效果: 将原本需要 50-100ms 的数据库查询,缩短到 1-5ms 的缓存读取,对于首页、热门文章等高频访问页面,缓存命中率可能高达 90% 以上,大幅降低了数据库负载和应用响应时间。
减轻数据库的写入压力
不仅仅是读,缓存也能优化写操作。
- 问题: 高并发场景下(如秒杀、点赞、计数器),每次用户操作都直接更新数据库的同一行记录,会造成锁竞争(行锁、表锁),导致数据库性能急剧下降,甚至崩溃。
- 缓存的解决方案:
- 计数器功能(Redis 特有): 使用 Redis 的
INCR命令操作内存中的计数器,操作是原子性的且极快,PHP 进程可以先把计数结果更新到 Redis,然后定时或批量地同步到 MySQL 数据库中。 - 异步队列: 将用户的写操作(如下单、发送邮件)放入 Redis 队列中,PHP 进程立即返回“提交成功”给用户,然后由后端的守护进程(Worker)慢慢从队列中取出数据,写入数据库。
- 计数器功能(Redis 特有): 使用 Redis 的
- 效果: 将高并发的直接写库操作,变成了对内存的无锁或低锁操作,将峰值流量“削峰填谷”,保护了数据库,并提升了用户请求的响应速度。
缓存 PHP 会话(Session)
PHP 默认的 Session 数据存储在文件系统(磁盘 I/O)中。
- 问题: 在分布式部署或多服务器场景下,基于文件的 Session 无法跨服务器共享,用户请求被分发到不同服务器时,Session 会丢失,文件系统在高并发时也有性能瓶颈和文件锁问题。
- 缓存的解决方案: 配置 PHP 将 Session 存储在 Redis 或 Memcached 中。
Redis 的设计天生支持高并发读写,且数据全在内存中,速度远快于文件。
- 效果: 实现了 Session 的无缝共享(Session 数据独立于 PHP 服务器),支持应用水平扩展(加服务器),同时大幅提升 Session 读写性能。
缓存复杂的计算结果(数据聚合/视图块)
- 问题: PHP 中有些操作非常消耗 CPU 和内存,
- 复杂的算法: 用户画像匹配、推荐系统计算、汇总统计。
- 循环分页: 从一个对账系统获取数百万条记录,然后在内存中循环筛选、排序、分页。
- 模板渲染: 渲染一个复杂的 Dashboard 页面,需要多次查询和多层循环,生成几千行 HTML。
- 外部 API 调用: 调用第三方服务(如天气预报、汇率查询),这些接口本身有访问频率限制且速度慢。
- 缓存的解决方案: 将经过大量计算后的最终结果(渲染好的 HTML 片段、或者一个复杂的 JSON 对象)缓存起来,下次请求时,直接返回缓存的内容,PHP 无需再次执行那些耗时的代码。
- 效果: 节省了大量 CPU 时间和内存使用,PHP 进程可以处理更多的并发请求。
为什么是 Memcached 或 Redis?而不是其他?
- 内存型存储: 它们都运行在服务器的内存中,内存的访问速度(纳秒级)比磁盘(毫秒级)快几个数量级。
- 键值对模型: 简单的 Key-Value 结构,查询复杂度是 O(1),非常适合做缓存。
- 网络服务: 独立于 PHP 进程运行,可以通过 TCP 或 Unix Socket 访问,使得 PHP 应用可以轻松扩展,多台 PHP 服务器可以共享同一个缓存实例。
- Memcached vs. Redis:
- Memcached: 简单、高效,专注于纯缓存,不支持持久化。
- Redis: 功能更丰富,除了缓存,还支持数据结构(List、Set、Sorted Set)、发布/订阅、事务、持久化、Lua 脚本等,在需要队列、计数器、数据一致性等复杂场景时,Redis 是更好的选择。
一个简单的例子(未使用 VS 使用缓存)
场景: 用户访问个人资料页面 profile.php?uid=123
未使用缓存(传统方式):
// 1. 连接到数据库 (100ms)
$db = new PDO(...);
// 2. 执行 SQL 查询 (50ms)
$stmt = $db->query("SELECT * FROM users WHERE id = 123");
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// 3. 给用户展示 (视图部分)
echo "Username: " . $user['name'];
// 总耗时 ~150ms, 每次请求都查库
使用 Redis 缓存(优化后):
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$uid = 123;
$cacheKey = "user:profile:" . $uid;
// 1. 尝试从 Redis 读取 (<5ms)
$user = $redis->get($cacheKey);
if ($user === false) { // 缓存未命中
// 2. 数据库查询 (50ms + 网络开销)
$db = new PDO(...);
$stmt = $db->query("SELECT * FROM users WHERE id = $uid");
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// 3. 将结果存入 Redis, 设置 1 小时过期
$redis->setex($cacheKey, 3600, json_encode($user));
} else {
$user = json_decode($user, true);
}
echo "Username: " . $user['name'];
// 总耗时:
// 缓存命中时: ~5ms (极快)
// 缓存未命中时: ~55ms (仍然比一直查库快, 且可能包含了过期后的更新)
性能提升的关键
| 未使用缓存 | 使用缓存 | 性能提升来源 |
|---|---|---|
| 每次请求都查数据库 | 绝大多数请求读缓存 | 减少磁盘 I/O 和网络开销 |
| 高并发写入数据库 | 写入 Redis(计数器/队列) | 降低数据库锁竞争和负载 |
| Session 存储在文件 | Session 存储在 Redis | 消除文件 I/O 瓶颈,支持分布式 |
| 重复执行耗时的计算 | 直接返回缓存的计算结果 | 节省 CPU 和内存资源 |
| 依赖外部慢速 API | 缓存 API 返回结果 | 避免外部服务延迟阻塞 PHP 进程 |
一句话概括: 合理使用缓存(Memcached/Redis),本质上是用廉价的内存空间(MB级)和快速的网络访问,换取昂贵的数据库磁盘 I/O(秒级)和高成本的 CPU 计算(毫秒级),从而让 PHP 项目能够以更低的延迟、更高的并发处理更多请求。