本文目录导读:

在 PHP 项目中实现数据缓存机制,主要有以下几种常见方案,从简单到复杂、从单机到分布式,你可以根据项目规模和需求选择:
文件缓存(最简单,适合小型项目)
将数据序列化后存储到文件中。
class FileCache {
private $cacheDir;
public function __construct($cacheDir = '/tmp/cache') {
$this->cacheDir = $cacheDir;
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
public function set($key, $data, $ttl = 3600) {
$cacheData = [
'expire' => time() + $ttl,
'data' => $data
];
$filename = $this->getFilename($key);
return file_put_contents($filename, serialize($cacheData)) !== false;
}
public function get($key) {
$filename = $this->getFilename($key);
if (!file_exists($filename)) {
return null;
}
$cacheData = unserialize(file_get_contents($filename));
if (time() > $cacheData['expire']) {
unlink($filename);
return null;
}
return $cacheData['data'];
}
private function getFilename($key) {
return $this->cacheDir . '/' . md5($key) . '.cache';
}
}
// 使用示例
$cache = new FileCache();
$cache->set('user_123', ['name' => '张三', 'age' => 30], 300); // 缓存5分钟
$user = $cache->get('user_123');
APCu 缓存(适合单机、快速)
APCu 是 PHP 的内置缓存扩展,直接在内存中操作。
// 安装:pecl install apcu
class ApcuCache {
public function set($key, $data, $ttl = 3600) {
return apcu_store($key, $data, $ttl);
}
public function get($key) {
return apcu_fetch($key) ?: null;
}
public function delete($key) {
return apcu_delete($key);
}
public function clear() {
return apcu_clear_cache();
}
}
// 使用示例
$cache = new ApcuCache();
$cache->set('product_456', ['price' => 99.9, 'stock' => 100], 600);
$product = $cache->get('product_456');
Redis 缓存(主流方案,支持分布式)
Redis 是生产环境最常用的缓存数据库。
安装 predis/predis 或 phpredis 扩展
// composer require predis/predis
use Predis\Client;
class RedisCache {
private $client;
public function __construct($config = []) {
$this->client = new Client($config ?: [
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
}
public function set($key, $data, $ttl = 3600) {
$data = json_encode($data);
return $this->client->setex($key, $ttl, $data);
}
public function get($key) {
$data = $this->client->get($key);
return $data ? json_decode($data, true) : null;
}
public function delete($key) {
return $this->client->del([$key]) > 0;
}
public function exists($key) {
return $this->client->exists($key) === 1;
}
}
// 使用示例
$cache = new RedisCache();
$cache->set('session_abc123', ['user_id' => 1, 'login_time' => time()], 1800);
$session = $cache->get('session_abc123');
常用 Redis 命令及缓存场景
// 1. 基本缓存
$redis->setex('key', 3600, 'value');
// 2. 列表缓存(排行榜)
$redis->lpush('recent_posts', json_encode($post));
$redis->ltrim('recent_posts', 0, 99); // 只保留前100条
// 3. 哈希缓存(对象)
$redis->hmset('user:123', ['name' => '张三', 'email' => 'test@example.com']);
$user = $redis->hgetall('user:123');
// 4. 分布式锁
$lockKey = 'lock:order:123';
$locked = $redis->setnx($lockKey, time() + 10); // 10秒超时
if ($locked) {
// 执行业务逻辑
$redis->del($lockKey); // 释放锁
}
// 5. 计数
$redis->incr('page_views:2024-01-01');
$views = $redis->get('page_views:2024-01-01');
缓存策略模式(通用封装)
将缓存操作抽象为策略模式,便于切换缓存驱动:
interface CacheInterface {
public function set($key, $data, $ttl);
public function get($key);
public function delete($key);
public function clear();
}
class CacheManager {
private $driver;
public function __construct(CacheInterface $driver) {
$this->driver = $driver;
}
// 封装缓存穿透保护
public function remember($key, $ttl, callable $callback) {
$data = $this->driver->get($key);
if ($data !== null) {
return $data;
}
// 避免缓存击穿:加锁
$lockKey = 'lock:' . $key;
if ($this->driver->setnx($lockKey, time() + 5)) {
try {
$data = $callback();
$this->driver->set($key, $data, $ttl);
return $data;
} finally {
$this->driver->delete($lockKey);
}
}
// 等待锁释放
usleep(100000); // 100ms
return $this->remember($key, $ttl, $callback);
}
}
// 使用示例
$manager = new CacheManager(new RedisCache());
$users = $manager->remember('all_users', 300, function() {
// 如果缓存不存在,从数据库查询
return User::all()->toArray();
});
常见问题及解决方案
缓存穿透
问题:大量请求查询不存在的 key,导致数据库压力。
解决:缓存空值(Null Object)或使用布隆过滤器。
function getWithNullObject($key, $ttl, callable $callback) {
$data = $cache->get($key);
if ($data === 'NULL_OBJECT') {
return null;
}
if ($data !== null) {
return $data;
}
$data = $callback();
if ($data === null) {
$cache->set($key, 'NULL_OBJECT', 60); // 短时间缓存空值
} else {
$cache->set($key, $data, $ttl);
}
return $data;
}
缓存雪崩
问题:大量缓存同时过期,导致数据库压力暴增。
解决:设置随机过期时间、多级缓存。
// 添加随机时间避免同时过期
function setWithRandomExpire($key, $data, $baseTtl = 3600) {
$random = mt_rand(0, 300); // 0-5分钟随机偏移
return $cache->set($key, $data, $baseTtl + $random);
}
缓存击穿
问题:热点 key 过期时,大量并发请求访问数据库。
解决:互斥锁(如上文的 remember 方法)或永不过期 + 异步更新。
缓存最佳实践
- 合理设置 TTL:根据数据更新频率
- 缓存粒度控制:不要整体缓存大对象
- 使用缓存前缀:便于批量清除
- 监控缓存命中率:命中率低于 80% 需要优化
- 缓存预热:系统启动时加载热点数据
// 缓存前缀
const CACHE_PREFIX = 'myapp:';
function buildCacheKey($key) {
return CACHE_PREFIX . $key;
}
// 批量删除匹配的缓存(需要 SCAN 命令)
function clearByPattern($pattern) {
$iterator = null;
while ($keys = $redis->scan($iterator, $pattern)) {
if (!empty($keys)) {
$redis->del($keys);
}
}
}
| 方案 | 适用场景 | 性能 | 分布式支持 |
|---|---|---|---|
| 文件缓存 | 小型项目、单机 | 低 | 否 |
| APCu | 单机、需要极快响应 | 极高 | 否 |
| Redis | 中大型项目、集群 | 高 | 是 |
| Memcached | 纯 KV 缓存 | 高 | 是 |
推荐顺序:对于新项目,直接使用 Redis(最通用),小型项目用 APCu 或文件缓存即可。