
为什么PHP项目需要引入缓存技术
当PHP应用并发量上升时,大量重复的数据库查询会占用数据库连接资源,延长请求响应时间。比如在电商首页展示商品分类、热门商品列表这类变化频率低但访问量极高的场景,每次请求都查询数据库会造成不必要的性能损耗。引入缓存后,首次查询的结果会存储到缓存介质中,后续相同请求直接读取缓存返回,无需再次访问数据库,大幅降低数据库压力。
PHP常用的缓存方案对比
不同的缓存介质适用不同的业务场景,下面是几种常见方案的对比:
| 缓存方案 | 存储介质 | 适用场景 | 优缺点 |
|---|---|---|---|
| 文件缓存 | 服务器本地文件 | 小型项目、单机部署的低频缓存需求 | 实现简单无需额外组件,但是读写速度慢,不支持分布式 |
| Memcached | 内存 | 简单的键值缓存、分布式缓存场景 | 读写速度快,支持分布式,但是功能单一,不支持持久化 |
| Redis | 内存+持久化存储 | 复杂缓存逻辑、需要数据结构支持的场景 | 功能丰富支持多种数据结构,可持久化,但是配置相对复杂 |
文件缓存实现示例
文件缓存是最容易落地的方案,适合没有额外缓存组件支持的小型项目,下面是一段通用的文件缓存操作代码:
<?php
class FileCache {
// 缓存文件存放目录
private $cacheDir;
public function __construct($cacheDir = './cache/') {
$this->cacheDir = rtrim($cacheDir, '/') . '/';
// 如果目录不存在则创建
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
/**
* 设置缓存
* @param string $key 缓存键
* @param mixed $value 缓存值
* @param int $expire 过期时间(秒)
* @return bool
*/
public function set($key, $value, $expire = 3600) {
$cacheFile = $this->getCacheFile($key);
$data = [
'expire' => time() + $expire,
'value' => $value
];
// 写入文件,锁机制避免并发冲突
return file_put_contents($cacheFile, serialize($data), LOCK_EX) !== false;
}
/**
* 获取缓存
* @param string $key 缓存键
* @return mixed 缓存不存在或过期返回false
*/
public function get($key) {
$cacheFile = $this->getCacheFile($key);
if (!file_exists($cacheFile)) {
return false;
}
$data = unserialize(file_get_contents($cacheFile));
// 判断缓存是否过期
if (time() > $data['expire']) {
unlink($cacheFile);
return false;
}
return $data['value'];
}
/**
* 删除指定缓存
* @param string $key 缓存键
* @return bool
*/
public function delete($key) {
$cacheFile = $this->getCacheFile($key);
if (file_exists($cacheFile)) {
return unlink($cacheFile);
}
return true;
}
// 生成缓存文件路径
private function getCacheFile($key) {
return $this->cacheDir . md5($key) . '.cache';
}
}
// 使用示例
$cache = new FileCache();
// 模拟数据库查询结果
$dbResult = ['id' => 1, 'name' => '测试商品', 'price' => 99.9];
// 将结果存入缓存,过期时间1小时
$cache->set('goods_1', $dbResult, 3600);
// 后续请求先读缓存
$result = $cache->get('goods_1');
if ($result === false) {
// 缓存不存在,查询数据库
$result = $dbResult;
$cache->set('goods_1', $result, 3600);
}
var_dump($result);
?>Redis缓存实现示例
Redis是PHP项目中最常用的缓存组件,支持丰富的数据结构,适合中大型项目的缓存需求,下面是基于Predis客户端的Redis缓存操作示例:
<?php
require 'predis/autoload.php';
class RedisCache {
private $redis;
public function __construct($host = '127.0.0.1', $port = 6379) {
$this->redis = new Predis\Client([
'scheme' => 'tcp',
'host' => $host,
'port' => $port,
]);
}
/**
* 设置缓存,支持过期时间
* @param string $key 缓存键
* @param mixed $value 缓存值,会自动序列化
* @param int $expire 过期时间(秒)
* @return bool
*/
public function set($key, $value, $expire = 3600) {
$serialized = serialize($value);
if ($expire > 0) {
return $this->redis->setex($key, $expire, $serialized);
} else {
return $this->redis->set($key, $serialized);
}
}
/**
* 获取缓存
* @param string $key 缓存键
* @return mixed 缓存不存在返回false
*/
public function get($key) {
$value = $this->redis->get($key);
if ($value === null) {
return false;
}
return unserialize($value);
}
/**
* 删除缓存
* @param string $key 缓存键
* @return int 删除的数量
*/
public function delete($key) {
return $this->redis->del([$key]);
}
}
// 使用示例
$cache = new RedisCache();
// 模拟商品列表查询结果
$goodsList = [
['id' => 1, 'name' => '商品A'],
['id' => 2, 'name' => '商品B']
];
// 存入缓存,过期时间半小时
$cache->set('hot_goods_list', $goodsList, 1800);
// 读取时先查缓存
$list = $cache->get('hot_goods_list');
if ($list === false) {
// 缓存未命中,查询数据库
$list = $goodsList;
$cache->set('hot_goods_list', $list, 1800);
}
var_dump($list);
?>缓存使用中的常见问题规避
引入缓存后需要注意几个常见问题,避免引发线上故障:
- 缓存穿透:指查询不存在的数据,导致每次都穿透缓存查询数据库。可以在缓存中存储空值并设置短过期时间,或者使用布隆过滤器提前过滤不存在的键。
- 缓存击穿:指热点key过期瞬间,大量请求同时查询数据库。可以使用互斥锁,第一个请求查询数据库时加锁,其他请求等待缓存更新后再读取。
- 缓存雪崩:指大量缓存同时过期,导致数据库压力骤增。可以给缓存过期时间加上随机值,避免同一时间大量key过期。
- 缓存更新策略:数据库数据更新后,需要及时更新或删除对应缓存,避免读到脏数据,通常采用删除缓存再更新数据库,或者更新数据库后同步更新缓存的方案。
总结
PHP项目中合理运用缓存技术,可以大幅降低数据库查询压力,提升系统响应速度。开发者需要根据项目规模和业务场景选择合适的缓存方案,小型项目可以使用文件缓存快速落地,中大型项目建议使用Redis这类专业的缓存组件。同时要注意缓存的常见问题,设计合理的读写和更新逻辑,才能发挥缓存的最大价值。