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