Redis缓存策略与实战应用


Redis缓存策略与实战应用

Redis作为最流行的内存数据库,在缓存、消息队列、排行榜等场景中有着广泛的应用。合理的缓存策略不仅能提升系统性能,还能避免缓存带来的数据一致性问题。本文将介绍Redis常见的缓存策略和实战技巧。

一、缓存更新策略

// Cache Aside - 最常用的缓存策略
function getUser($id) {
    $cacheKey = "user:{$id}";
    $user = $redis->get($cacheKey);
    
    if ($user === null) {
        // 缓存未命中,查询数据库
        $user = $db->query("SELECT * FROM users WHERE id = ?", [$id]);
        if ($user) {
            $redis->setex($cacheKey, 3600, json_encode($user));
        }
    } else {
        $user = json_decode($user, true);
    }
    
    return $user;
}

// 更新数据时
function updateUser($id, $data) {
    // 先更新数据库
    $db->update("users", $data, "id = ?", [$id]);
    // 再删除缓存(比更新缓存更安全)
    $redis->del("user:{$id}");
}

二、缓存穿透、击穿、雪崩

// 1. 缓存穿透 - 查询不存在的数据
// 解决:布隆过滤器 + 空值缓存
function getUser($id) {
    // 布隆过滤器先检查
    if (!$bloomFilter->exists($id)) {
        return null;  // 数据一定不存在
    }
    
    $cacheKey = "user:{$id}";
    $user = $redis->get($cacheKey);
    
    if ($user === null) {
        $user = $db->find($id);
        if ($user) {
            $redis->setex($cacheKey, 3600, json_encode($user));
        } else {
            // 空值缓存,短过期时间
            $redis->setex($cacheKey, 60, "NULL");
        }
    } elseif ($user === "NULL") {
        return null;
    }
    
    return json_decode($user, true);
}

// 2. 缓存击穿 - 热点key过期
// 解决:互斥锁
function getHotData($key) {
    $data = $redis->get($key);
    if ($data === null) {
        $lockKey = "lock:{$key}";
        // 尝试获取锁
        if ($redis->set($lockKey, 1, ['NX', 'EX' => 10])) {
            try {
                $data = $db->find($key);
                $redis->setex($key, 3600, json_encode($data));
            } finally {
                $redis->del($lockKey);
            }
        } else {
            // 等待并重试
            usleep(100000);
            return getHotData($key);
        }
    }
    return json_decode($data, true);
}

// 3. 缓存雪崩 - 大量key同时过期
// 解决:随机过期时间
$expire = 3600 + mt_rand(0, 600);  // 基础1小时 + 随机0-10分钟

三、Redis数据结构实战

// 排行榜 - Sorted Set
$redis->zadd("rank:score", $score, $userId);
$top10 = $redis->zrevrange("rank:score", 0, 9, true);
$userRank = $redis->zrevrank("rank:score", $userId);

// 计数器 - String
$redis->incr("article:views:{$articleId}");
$redis->incrby("user:score:{$userId}", 10);

// 分布式锁
$lockAcquired = $redis->set("lock:order:{$orderId}", $requestId, ['NX', 'EX' => 30]);
if ($lockAcquired) {
    try {
        processOrder($orderId);
    } finally {
        // Lua脚本确保原子释放
        $script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        $redis->eval($script, ["lock:order:{$orderId}", $requestId], 1);
    }
}

// 限流 - 滑动窗口
$now = microtime(true);
$windowKey = "rate_limit:{$userId}";
$redis->zremrangebyscore($windowKey, 0, $now - 60);  // 清除60秒前的
$redis->zadd($windowKey, $now, $now);
$count = $redis->zcard($windowKey);
$redis->expire($windowKey, 60);
if ($count > 100) {
    // 超出限流
}

四、Redis集群方案

// 主从复制 + 哨兵
// sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000

// Redis Cluster - 数据分片
// 最小6节点(3主3从)
redis-cli --cluster create \
  10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \
  10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \
  --cluster-replicas 1

Redis是提升系统性能的利器,但缓存不是银弹。在使用缓存时,必须考虑数据一致性、缓存更新、异常处理等问题。建议根据业务场景选择合适的缓存策略,并建立完善的缓存监控体系。


0.050515s