PHP 推荐结果缓存

wen PHP项目 3

本文目录导读:

PHP 推荐结果缓存

  1. 缓存架构设计
  2. 多级缓存实现
  3. 缓存更新策略
  4. 缓存穿透解决方案
  5. 性能优化技巧
  6. 使用示例

我来详细介绍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);
            }
        );
    }
}
  1. 合理设置缓存时间:根据数据热度动态调整TTL
  2. 使用多级缓存:内存→Redis→数据库的降级顺序
  3. 防止缓存击穿:使用互斥锁保证只有一个请求重建数据
  4. 防止缓存穿透:使用布隆过滤器或空值缓存
  5. 防止缓存雪崩:随机化TTL,错开过期时间
  6. 及时更新:用户行为或其他数据变更时主动失效
  7. 监控告警:监控缓存命中率、延迟等指标
  8. 序列化选择:优先使用MessagePack或Protocol Buffers替代JSON,减少存储空间和序列化开销

这样可以构建一个高性能、稳定的推荐结果缓存系统。

抱歉,评论功能暂时关闭!