缓存雪崩导致接口大量超时怎么办?

极客雨诺 阅读 10

我们线上有个商品详情页,用了 Redis 缓存,结果昨天缓存集体过期,瞬间数据库被打爆,接口基本都超时了。之前设置了统一的过期时间,比如都是 3600 秒,现在想优化但不知道咋下手。

听说可以加随机过期时间?但我试了下感觉治标不治本。有没有更稳妥的做法?比如用互斥锁或者永不过期策略?具体该怎么写啊?

现在临时加了限流,但用户体验很差。求真实项目里怎么处理这种缓存雪崩问题?

我来解答 赞 1 收藏
二维码
手机扫码查看
1 条解答
シ明艳
シ明艳 Lv1
缓存雪崩这破事我之前也遇到过,说白了就是同一时间大量key过期,请求全打DB上去了。

给你几个实战中常用的方案:

方案一:永不过期 + 逻辑过期

把Redis的过期时间去掉,改成在value里存一个过期时间戳,读取时判断是否需要刷新:

class CacheService {
private $redis;

// 缓存永不过期,靠逻辑判断
public function getProduct($productId) {
$cacheKey = "product:{$productId}";
$data = $this->redis->get($cacheKey);

if (!$data) {
return $this->getProductFromDB($productId);
}

$cache = json_decode($data, true);

// 逻辑过期:超过30分钟就重新计算
if (time() - $cache['timestamp'] > 1800) {
// 丢给后台异步更新,不阻塞请求
go(function() use ($productId) {
$this->refreshProductCache($productId);
});
return $cache['data']; // 返回旧数据先扛着
}

return $cache['data'];
}

public function refreshProductCache($productId) {
$data = $this->getProductFromDB($productId);
$this->redis->set("product:{$productId}", json_encode([
'data' => $data,
'timestamp' => time()
]));
}
}


这个方案最稳,请求不会被阻塞,短暂返回旧数据也没事。

方案二:互斥锁重建

如果一定要最新数据,用锁来控制只有一个请求去DB查:

public function getProduct($productId) {
$cacheKey = "product:{$productId}";
$data = $this->redis->get($cacheKey);

if ($data) {
return json_decode($data, true);
}

// 加锁,只有抢到锁的请求去查DB
$lockKey = "lock:product:{$productId}";
$lock = $this->redis->set($lockKey, 1, ['NX', 'EX' => 10]); // 10秒锁

if ($lock) {
try {
$data = $this->getProductFromDB($productId);
$this->redis->setex($cacheKey, 3600, json_encode($data));
return $data;
} finally {
$this->redis->del($lockKey);
}
} else {
// 没抢到锁,睡一下再重试读取
usleep(50000); // 50ms
return $this->getProduct($productId);
}
}


方案三:随机过期时间 + 预热

随机过期时间确实治标,但配合预热就好多了:

// 预热:系统启动时把热点数据先加载进去
public function warmupCache() {
$productIds = $this->getHotProductIds(); // 热门商品ID

foreach ($productIds as $id) {
$data = $this->getProductFromDB($id);
// 随机过期时间 3600 ~ 7200 秒,分散开
$ttl = 3600 + rand(0, 3600);
$this->redis->setex("product:{$id}", $ttl, json_encode($data));
}
}


建议用crontab定时任务每天凌晨流量低峰期跑一次预热。



我的经验是:优先用方案一(永不过期),这是目前最稳的方案,基本不会出问题。互斥锁适合对数据实时性要求高的场景,但会增加响应时间。

另外,别忘了给DB加个连接池和慢查询监控,别让DB本身成为瓶颈。
点赞
2026-03-13 03:02