本文目录导读:

我来详细介绍PHP中推荐结果缓存的最佳实践方案。
缓存架构设计
基础缓存类
<?php
namespace App\Cache;
use Redis;
use Predis\Client;
class RecommendationCache
{
private $redis;
private $ttl;
// 缓存键前缀
const KEY_PREFIX = 'rec:';
// 不同场景的缓存时间
const TTL_HOT = 3600; // 1小时
const TTL_NORMAL = 1800; // 30分钟
const TTL_COLD = 7200; // 2小时
public function __construct(Redis $redis, $ttl = self::TTL_NORMAL)
{
$this->redis = $redis;
$this->ttl = $ttl;
}
/**
* 获取推荐结果(带缓存击穿保护)
*/
public function getRecommendations($userId, $type = 'home', $limit = 10)
{
$cacheKey = $this->buildKey($userId, $type, $limit);
$cacheKeyLock = $cacheKey . ':lock';
// 1. 尝试从缓存获取
$cached = $this->redis->get($cacheKey);
if ($cached !== false) {
return json_decode($cached, true);
}
// 2. 缓存未命中,检查锁
if ($this->redis->setnx($cacheKeyLock, 1)) {
// 获取锁成功,设置过期时间防止死锁
$this->redis->expire($cacheKeyLock, 10);
try {
// 3. 执行查询算法
$data = $this->getRecommendationsFromAlgorithm($userId, $type, $limit);
// 4. 写入缓存
$this->redis->setex(
$cacheKey,
$this->ttl,
json_encode($data)
);
// 5. 删除锁
$this->redis->del($cacheKeyLock);
return $data;
} catch (\Exception $e) {
// 发生异常,删除锁
$this->redis->del($cacheKeyLock);
// 返回兜底数据
return $this->getFallbackData($userId, $type, $limit);
}
} else {
// 等待锁释放后重试
usleep(200000); // 200毫秒
return $this->getRecommendations($userId, $type, $limit);
}
}
/**
* 构建缓存键
*/
private function buildKey($userId, $type, $limit)
{
return self::KEY_PREFIX . $type . ':' . $userId . ':' . $limit;
}
/**
* 获取备选数据(降级策略)
*/
private function getFallbackData($userId, $type, $limit)
{
// 返回热门内容或默认推荐
return $this->redis->get(self::KEY_PREFIX . 'hot:' . $type);
}
}
多级缓存实现
<?php
namespace App\Cache;
class MultiLevelCache
{
private $memoryCache; // 内存缓存
private $redisCache; // Redis缓存
private $databaseCache; // 数据库缓存
private $memoryTTL = 60; // 内存缓存60秒
private $redisTTL = 600; // Redis缓存10分钟
/**
* 三级缓存获取数据
*/
public function getData($key)
{
// 1. 内存缓存(最快)
$data = $this->getFromMemory($key);
if ($data !== null) {
return $data;
}
// 2. Redis缓存
$data = $this->getFromRedis($key);
if ($data) {
// 回填内存缓存
$this->setToMemory($key, $data);
return $data;
}
// 3. 数据库查询
$data = $this->getFromDatabase($key);
if ($data) {
$this->setToMemory($key, $data);
$this->setToRedis($key, $data);
}
return $data;
}
/**
* 内存缓存
*/
private function getFromMemory($key)
{
// 使用APCu或本地数组
if (function_exists('apcu_fetch')) {
$data = apcu_fetch('rec_' . $key);
return $data !== false ? $data : null;
}
// 简单的内存数组(单进程)
static $memory = [];
if (isset($memory[$key])) {
if (time() - $memory[$key]['time'] < $this->memoryTTL) {
return $memory[$key]['data'];
}
}
return null;
}
private function setToMemory($key, $data)
{
if (function_exists('apcu_store')) {
apcu_store('rec_' . $key, $data, $this->memoryTTL);
} else {
static $memory = [];
$memory[$key] = ['data' => $data, 'time' => time()];
}
}
/**
* Redis缓存
*/
private function getFromRedis($key)
{
try {
$redis = $this->getRedis();
$data = $redis->get('rec_redis_' . $key);
return $data ? unserialize($data) : null;
} catch (\Exception $e) {
// Redis异常时返回null,触发数据库查询
error_log('Redis error: ' . $e->getMessage());
return null;
}
}
private function setToRedis($key, $data)
{
try {
$redis = $this->getRedis();
$redis->setex(
'rec_redis_' . $key,
$this->redisTTL,
serialize($data)
);
} catch (\Exception $e) {
error_log('Redis error: ' . $e->getMessage());
}
}
/**
* 数据库查询
*/
private function getFromDatabase($key)
{
// 伪代码:数据库查询逻辑
return ['data' => 'database result'];
}
}
缓存更新策略
<?php
namespace App\Cache;
class CacheManager
{
private $redis;
/**
* 主动清除缓存
*/
public function invalidateUserCache($userId)
{
$pattern = "rec:*:{$userId}:*";
$keys = $this->redis->keys($pattern);
if (!empty($keys)) {
$this->redis->del($keys);
}
}
/**
* 异步更新缓存
*/
public function asyncUpdateCache($key, $callable)
{
// 使用消息队列异步更新
$queue = 'cache_update_queue';
$this->redis->lpush($queue, json_encode([
'key' => $key,
'type' => 'update',
'callback' => $callable
]));
}
/**
* 批量更新缓存
*/
public function batchUpdate($items)
{
$pipeline = $this->redis->pipeline();
foreach ($items as $item) {
$key = $item['key'];
$data = $item['data'];
$ttl = isset($item['ttl']) ? $item['ttl'] : 3600;
$pipeline->setex($key, $ttl, json_encode($data));
}
return $pipeline->exec();
}
/**
* 预热缓存
*/
public function warmUp($keys)
{
foreach ($keys as $key) {
if (!$this->redis->exists($key)) {
// 生成数据并写入缓存
$this->generateData($key);
}
}
}
}
缓存穿透解决方案
<?php
namespace App\Cache;
class CacheProtection
{
private $redis;
/**
* 布隆过滤器防穿透
*/
public function getAndFilter($userId, $type)
{
$key = "rec_filter:{$type}:{$userId}";
// 使用布隆过滤器检查是否可能存在
if (!$this->bloomFilterCheck($userId)) {
return [];
}
$cacheKey = "rec:{$type}:{$userId}";
$data = $this->redis->get($cacheKey);
if (!$data) {
// 空值缓存,防止穿透
$this->redis->setex($cacheKey, 300, json_encode([]));
return [];
}
return json_decode($data, true);
}
/**
* 布隆过滤器实现
*/
private function bloomFilterCheck($userId)
{
$bloomKey = 'user_bloom_filter';
$bitmapSize = 5000000; // 5百万位
// 简单的哈希函数
$hash1 = crc32($userId) % $bitmapSize;
$hash2 = md5($userId) % $bitmapSize;
$hash3 = sha1($userId) % $bitmapSize;
return $this->redis->getbit($bloomKey, $hash1) &&
$this->redis->getbit($bloomKey, $hash2) &&
$this->redis->getbit($bloomKey, $hash3);
}
/**
* 随机过期时间防止雪崩
*/
public function getWithRandomTTL($key, $ttlBase = 3600)
{
$data = $this->redis->get($key);
if ($data === false) {
// 在TTL基础上增加随机时间
$randomTTL = $ttlBase + mt_rand(0, 300);
// 重建数据...
$data = $this->rebuildData($key);
$this->redis->setex($key, $randomTTL, $data);
}
return $data;
}
}
性能优化技巧
<?php
namespace App\Cache;
class PerformanceOptimizer
{
private $redis;
/**
* 批量获取推荐
*/
public function batchGetRecommendations($userIds, $type = 'home')
{
$keys = [];
foreach ($userIds as $userId) {
$keys[] = "rec:{$type}:{$userId}";
}
// 使用mget批量获取
$results = $this->redis->mget($keys);
// 处理未命中的请求
$missKeys = [];
$missIndices = [];
foreach ($results as $index => $result) {
if ($result === false) {
$missKeys[] = $keys[$index];
$missIndices[] = $index;
}
}
// 批量生成并缓存
if (!empty($missKeys)) {
$newData = $this->generateBatch($userIds, $type);
$pipeline = $this->redis->pipeline();
foreach ($newData as $i => $data) {
$pipeline->setex($keys[$missIndices[$i]], 3600, $data);
}
$pipeline->exec();
// 合并结果
foreach ($newData as $i => $data) {
$results[$missIndices[$i]] = $data;
}
}
return $results;
}
/**
* 使用hash结构缓存复杂数据结构
*/
public function storeDetailedRecommendation($userId, $type, $recommendations)
{
$hashKey = "rec_detail:{$type}:{$userId}";
$pipeline = $this->redis->pipeline();
// 存储为hash,提高访问性能
foreach ($recommendations as $item) {
$pipeline->hset($hashKey, $item['id'], json_encode($item));
}
$pipeline->expire($hashKey, 3600);
return $pipeline->exec();
}
/**
* 压缩存储
*/
public function compressAndStore($key, $data)
{
$compressed = gzcompress(json_encode($data), 9);
$this->redis->setex($key, 3600, $compressed);
}
public function getAndDecompress($key)
{
$compressed = $this->redis->get($key);
if ($compressed) {
return json_decode(gzuncompress($compressed), true);
}
return null;
}
}
使用示例
<?php
// 使用示例
class RecommendationController
{
private $cacheService;
public function getRecommendations($userId)
{
$cache = new MultiLevelCache();
// 构建缓存键
$cacheKey = "user:{$userId}:recommendations:1";
// 获取数据(自动多级查找)
$data = $cache->getData($cacheKey);
if (!$data) {
// 如果缓存未命中,进行数据查询
$data = $this->generateRecommendations($userId);
// 写入各级缓存
$cache->setData($cacheKey, $data);
}
return $data;
}
/**
* 定时预热热门推荐
*/
public function prewarmHotRecommendations()
{
$hotUsers = $this->getHotUsers(100); // 获取热门用户
$cache = new CacheProtection();
foreach ($hotUsers as $userId) {
$cache->warmUp("rec:{$userId}");
}
return count($hotUsers);
}
/**
* 用户行为触发更新
*/
public function onUserAction($userId, $action, $itemId)
{
// 清除相关缓存
$cacheManager = new CacheManager();
$cacheManager->invalidateUserCache($userId);
// 异步更新
$cacheManager->asyncUpdateCache(
"rec:{$userId}",
function() use ($userId) {
return $this->generateRecommendations($userId);
}
);
}
}
- 合理设置缓存时间:根据数据热度动态调整TTL
- 使用多级缓存:内存→Redis→数据库的降级顺序
- 防止缓存击穿:使用互斥锁保证只有一个请求重建数据
- 防止缓存穿透:使用布隆过滤器或空值缓存
- 防止缓存雪崩:随机化TTL,错开过期时间
- 及时更新:用户行为或其他数据变更时主动失效
- 监控告警:监控缓存命中率、延迟等指标
- 序列化选择:优先使用MessagePack或Protocol Buffers替代JSON,减少存储空间和序列化开销
这样可以构建一个高性能、稳定的推荐结果缓存系统。