PHP缓存读写分离指的是将缓存的读操作和写操作按照不同的逻辑处理,核心目标是让读请求尽可能命中缓存减少数据库访问,写请求保证数据库和缓存的数据一致性,从而提升整个系统的运行效率。

缓存读写分离的核心原则
缓存读写分离不需要复杂的架构调整,只需要遵循两个基础原则即可:
- 读请求优先访问缓存,缓存命中直接返回数据,缓存未命中再查询数据库,同时将查询结果写入缓存
- 写请求优先更新数据库,再根据业务场景选择更新缓存或者删除缓存,避免缓存和数据库数据不一致
基于Redis的PHP缓存读写分离实现
Redis是PHP项目中最常用的缓存工具,下面给出完整的读写分离实现示例,假设我们需要缓存用户的基本信息。
读操作实现
读操作的逻辑是先查Redis缓存,命中则返回,未命中查数据库并写入缓存,设置合理的过期时间。
<?php
/**
* 获取用户信息(读操作)
* @param int $userId 用户ID
* @return array 用户信息
*/
function getUserInfo($userId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = 'user_info_' . $userId;
// 先查缓存
$cacheData = $redis->get($cacheKey);
if ($cacheData !== false) {
// 缓存命中,直接返回
return json_decode($cacheData, true);
}
// 缓存未命中,查询数据库
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', '123456');
$stmt = $pdo->prepare('SELECT id, name, age FROM user WHERE id = ?');
$stmt->execute([$userId]);
$userInfo = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($userInfo)) {
// 将查询结果写入缓存,设置3600秒过期
$redis->setex($cacheKey, 3600, json_encode($userInfo));
}
return $userInfo;
}
?>
写操作实现
写操作选择先更新数据库,再删除缓存的方案,避免更新缓存时出现的并发不一致问题。
<?php
/**
* 更新用户信息(写操作)
* @param int $userId 用户ID
* @param array $updateData 要更新的数据
* @return bool 是否更新成功
*/
function updateUserInfo($userId, $updateData) {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', '123456');
// 先更新数据库
$setStr = '';
$params = [];
foreach ($updateData as $key => $value) {
$setStr .= "`$key` = ?,";
$params[] = $value;
}
$setStr = rtrim($setStr, ',');
$params[] = $userId;
$sql = "UPDATE user SET $setStr WHERE id = ?";
$stmt = $pdo->prepare($sql);
$result = $stmt->execute($params);
if ($result) {
// 数据库更新成功,删除对应缓存
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = 'user_info_' . $userId;
$redis->del($cacheKey);
return true;
}
return false;
}
?>
基于Memcached的PHP缓存读写分离实现
Memcached也是常用的缓存工具,其读写分离的实现逻辑和Redis类似,只是调用的扩展方法不同。
读操作实现
<?php
/**
* 基于Memcached获取用户信息
* @param int $userId 用户ID
* @return array 用户信息
*/
function getUserInfoByMemcached($userId) {
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
$cacheKey = 'user_info_' . $userId;
// 先查缓存
$cacheData = $memcached->get($cacheKey);
if ($memcached->getResultCode() == Memcached::RES_SUCCESS) {
return json_decode($cacheData, true);
}
// 缓存未命中查数据库
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', '123456');
$stmt = $pdo->prepare('SELECT id, name, age FROM user WHERE id = ?');
$stmt->execute([$userId]);
$userInfo = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($userInfo)) {
// 写入缓存,设置3600秒过期
$memcached->set($cacheKey, json_encode($userInfo), 3600);
}
return $userInfo;
}
?>
写操作实现
<?php
/**
* 基于Memcached更新用户信息
* @param int $userId 用户ID
* @param array $updateData 更新数据
* @return bool 是否成功
*/
function updateUserInfoByMemcached($userId, $updateData) {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', '123456');
// 先更新数据库
$setStr = '';
$params = [];
foreach ($updateData as $key => $value) {
$setStr .= "`$key` = ?,";
$params[] = $value;
}
$setStr = rtrim($setStr, ',');
$params[] = $userId;
$sql = "UPDATE user SET $setStr WHERE id = ?";
$stmt = $pdo->prepare($sql);
$result = $stmt->execute($params);
if ($result) {
// 删除缓存
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
$cacheKey = 'user_info_' . $userId;
$memcached->delete($cacheKey);
return true;
}
return false;
}
?>
常见问题及应对方案
缓存穿透
指的是查询不存在的数据,缓存和数据库都没有,导致每次请求都打到数据库。应对方案可以是在缓存中存储空值,设置较短的过期时间,或者对请求参数做合法性校验。
缓存击穿
指的是热点key过期瞬间,大量请求同时打到数据库。应对方案可以是设置热点key永不过期,或者加互斥锁,只有一个请求去查数据库,其他请求等待缓存更新后再获取。
缓存雪崩
指的是大量缓存同时过期,导致数据库压力骤增。应对方案是给缓存过期时间加随机值,避免同时过期,也可以采用多级缓存架构分散风险。
总结
PHP缓存读写分离的实现并不复杂,核心是遵循读缓存优先、写数据库优先的原则,根据业务场景选择合适的缓存更新策略。在实际开发中,还需要结合项目的并发量、数据一致性要求调整方案,同时注意处理缓存相关的异常情况,才能保证系统的稳定运行。