缓存技术详解
缓存是提升应用性能的关键手段。本文介绍 PHP 中常用的缓存方案。
文件缓存(最简单)
php
class FileCache {
private $cachePath;
public function __construct($path = 'cache/') {
$this->cachePath = $path;
if (!is_dir($path)) {
mkdir($path, 0755, true);
}
}
public function get($key) {
$file = $this->cachePath . md5($key) . '.cache';
if (!file_exists($file)) return null;
$data = unserialize(file_get_contents($file));
if ($data['expire'] < time()) {
unlink($file);
return null;
}
return $data['value'];
}
public function set($key, $value, $ttl = 3600) {
$file = $this->cachePath . md5($key) . '.cache';
$data = ['expire' => time() + $ttl, 'value' => $value];
return file_put_contents($file, serialize($data), LOCK_EX);
}
public function delete($key) {
$file = $this->cachePath . md5($key) . '.cache';
return file_exists($file) && unlink($file);
}
}
// 使用示例
$cache = new FileCache();
$data = $cache->get('user_list');
if (!$data) {
$data = fetchUsersFromDb();
$cache->set('user_list', $data, 600);
}Redis 缓存(推荐)
php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password'); // 如果有密码
// 字符串缓存
$redis->set('config:site_name', '我的网站', 3600);
$name = $redis->get('config:site_name');
// 数组缓存(自动序列化)
$user = ['id' => 1, 'name' => '张三'];
$redis->setex('user:1', 3600, json_encode($user));
$user = json_decode($redis->get('user:1'), true);
// 缓存数据库查询结果
function getUserWithCache($id) {
global $redis;
$key = "user:$id";
$cached = $redis->get($key);
if ($cached) {
return json_decode($cached, true);
}
$user = fetchUserFromDb($id);
if ($user) {
$redis->setex($key, 3600, json_encode($user));
}
return $user;
}
// 缓存失效
$redis->del('user:1'); // 删除单个
$redis->delete('user:1', 'user:2'); // 删除多个
$redis->flushDB(); // 清空当前库(慎用)缓存穿透、击穿、雪崩解决方案
php
// 1. 缓存穿透(查询不存在的数据)- 布隆过滤器或缓存空值
function getDataWithNullCache($key) {
$value = $redis->get($key);
if ($value === 'null_marker') {
return null; // 数据库确实不存在
}
if ($value) {
return json_decode($value, true);
}
// 查询数据库
$data = fetchFromDb($key);
if ($data) {
$redis->setex($key, 3600, json_encode($data));
} else {
// 缓存空值,短时间过期
$redis->setex($key, 60, 'null_marker');
}
return $data;
}
// 2. 缓存击穿(热点数据过期)- 互斥锁
function getHotDataWithLock($key) {
$value = $redis->get($key);
if ($value) {
return json_decode($value, true);
}
// 尝试获取锁
$lockKey = "lock:$key";
$locked = $redis->set($lockKey, 1, ['nx', 'ex' => 10]);
if ($locked) {
// 只有获得锁的进程查询数据库
$data = fetchFromDb($key);
$redis->setex($key, 3600, json_encode($data));
$redis->del($lockKey);
return $data;
}
// 未获得锁,短暂等待后重试
usleep(100000); // 100ms
return getHotDataWithLock($key);
}
// 3. 缓存雪崩(大量缓存同时过期)- 随机过期时间
function setWithRandomExpire($key, $value, $baseTtl = 3600) {
$random = mt_rand(0, 300); // 0-5分钟随机
$redis->setex($key, $baseTtl + $random, json_encode($value));
}OPCache 配置优化
ini
; php.ini OPCache 推荐配置
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.validate_timestamps=0 ; 生产环境关闭,需手动重启注意事项
- 缓存更新策略:写数据库时同步/异步更新缓存
- 缓存粒度:根据业务选择合适粒度,避免缓存过大
- 序列化开销:大数据量缓存考虑使用更高效的序列化方式(msgpack/igbinary)
- 监控:关注缓存命中率,低于80%需要优化