25人参与 • 2026-04-26 • Redis
缓存击穿,指的是某一个热点key(被高频访问的key),在缓存中过期失效的瞬间,大量的请求直接穿透缓存,涌向数据库,导致数据库瞬时压力骤增,甚至被压垮的现象。
tip:当请求打过来时,未在缓存中命中,去数据库查询数据就可能涉及到了很多表和数据库的查询汇总。这种情况就会出现查询数据时间很长,在这期间大量请求涌入数据库,它可能瞬间扛不住,就会出现响应超时、报错,甚至宕机
举个例子:比如电商平台的“爆款商品详情页”,这个商品的key在redis中缓存了1小时,平时所有请求都走缓存,数据库几乎无压力。但当这个key过期的那一刻,刚好有1000个用户同时访问这个商品,此时缓存中没有数据,所有1000个请求都会直接打向数据库,数据库瞬间扛不住,就会出现响应超时、报错,甚至宕机。
这里要注意区分两个易混淆概念:
缓存穿透:是请求的key本身就不存在于缓存和数据库中,请求一直穿透到数据库。
缓存击穿:是请求的key存在于数据库中,但缓存刚好过期,瞬时请求穿透到数据库。
如果key不是热点,即使缓存过期,也只有少量请求穿透到数据库,不会造成太大影响。只有当key被高频访问,才会在缓存失效瞬间对数据库造成巨大压力。
redis的key都有过期时间,目的是为了释放内存,避免无效数据占用空间。但如果热点key的过期时间设置不合理,或者刚好在请求高峰时过期,就会触发击穿。
比如:把爆款商品的缓存时间设置为1小时,而刚好在晚上8点(用户访问高峰)过期,就极易引发击穿。
如果缓存失效后,没有任何限流、降级、重试的机制,所有请求会毫无阻拦地冲向数据库,而数据库的并发处理能力远低于redis,很容易被压垮。
数据库压力骤增:瞬时大量请求穿透到数据库,导致数据库cpu、内存、io占用率飙升,响应时间大幅延长。
系统响应超时:数据库处理不过来请求,会导致接口响应超时,前端出现加载失败、白屏等问题。
数据库宕机:如果请求量过大,超过数据库的承载极限,会导致数据库宕机,进而引发整个系统服务不可用。
连锁反应:数据库宕机后,即使缓存恢复,后续请求依然无法正常处理,可能导致服务雪崩,依赖该数据库的其他服务也跟着报错。
当大量请求过来后走缓存

// 1. 注入redistemplate(springboot环境)
@autowired
private redistemplate<string, object> redistemplate;
// 2. 互斥锁核心方法(获取锁+查询数据库+更新缓存)
public object getvaluebymutexlock(string key) {
// 第一步:查询缓存
object value = redistemplate.opsforvalue().get(key);
if (value != null) {
return value; // 缓存存在,直接返回
}
// 第二步:缓存不存在,尝试获取分布式锁
string lockkey = "lock:" + key; // 锁key,与业务key绑定,避免锁冲突
string lockvalue = uuid.randomuuid().tostring(); // 唯一值,用于释放锁
boolean islock = redistemplate.opsforvalue()
.setifabsent(lockkey, lockvalue, 3, timeunit.seconds); // 锁过期时间3秒(根据数据库查询耗时调整)
if (islock) {
try {
// 第三步:获取锁成功,查询数据库
value = querydatabase(key); // 自定义方法,查询数据库数据
// 第四步:将数据库数据写入缓存,设置过期时间(避免再次击穿)
redistemplate.opsforvalue().set(key, value, 30, timeunit.minutes);
return value;
} finally {
// 第五步:释放锁(必须在finally中,避免死锁)
// 对比value确保是自己的锁,避免误释放他人的锁
if (lockvalue.equals(redistemplate.opsforvalue().get(lockkey))) {
redistemplate.delete(lockkey);
}
}
} else {
// 第六步:获取锁失败,重试(间隔100ms,避免频繁重试)
try {
thread.sleep(100);
} catch (interruptedexception e) {
thread.currentthread().interrupt();
}
return getvaluebymutexlock(key); // 递归重试,也可使用循环
}
}
// 模拟数据库查询方法
private object querydatabase(string key) {
// 实际业务中替换为真实数据库查询逻辑(如mybatis查询)
return "数据库查询到的" + key + "对应数据";
}我们不再设置key的物理过期时间,而是在缓存数据中嵌入一个逻辑过期时间字段。
当大量请求过来后走缓存判断查询字段是否已经过期

// 1. 定义缓存数据封装类(封装业务数据+逻辑过期时间)
@data
public class cachedata<t> {
// 业务数据
private t data;
// 逻辑过期时间(时间戳,单位:毫秒)
private long expiretime;
}
// 2. 注入依赖(springboot环境)
@autowired
private redistemplate<string, object> redistemplate;
// 异步线程池(用于逻辑过期后异步更新缓存)
@autowired
private threadpooltaskexecutor asynctaskexecutor;
// 3. 逻辑过期核心方法
public object getvaluebylogicalexpire(string key) {
// 第一步:查询redis缓存(获取封装后的cachedata对象)
cachedata<object> cachedata = (cachedata<object>) redistemplate.opsforvalue().get(key);
if (cachedata == null) {
// 缓存不存在(首次请求/缓存被手动删除),此处可返回兜底数据或查询数据库
return querydatabase(key);
}
// 第二步:判断逻辑过期时间是否已到
long currenttime = system.currenttimemillis();
if (currenttime < cachedata.getexpiretime()) {
// 逻辑未过期,直接返回业务数据
return cachedata.getdata();
}
// 第三步:逻辑已过期,返回旧数据,同时异步更新缓存
asynctaskexecutor.execute(() -> {
try {
// 异步查询数据库最新数据
object newdata = querydatabase(key);
// 重新封装cachedata,设置新的逻辑过期时间(如30分钟后)
cachedata<object> newcachedata = new cachedata<>();
newcachedata.setdata(newdata);
newcachedata.setexpiretime(system.currenttimemillis() + 30 * 60 * 1000);
// 更新redis缓存(无物理过期时间)
redistemplate.opsforvalue().set(key, newcachedata);
} catch (exception e) {
// 异常处理(如日志记录),避免异步任务失败导致缓存无法更新
log.error("逻辑过期缓存更新失败,key:{}", key, e);
}
});
// 直接返回旧数据,不阻塞当前请求
return cachedata.getdata();
}
// 模拟数据库查询方法(与互斥锁方案一致)
private object querydatabase(string key) {
return "数据库查询到的" + key + "对应数据";
}
// 4. 初始化缓存(缓存预热,存入带逻辑过期时间的数据)
public void initcache(string key) {
object data = querydatabase(key);
cachedata<object> cachedata = new cachedata<>();
cachedata.setdata(data);
// 设置逻辑过期时间(30分钟)
cachedata.setexpiretime(system.currenttimemillis() + 30 * 60 * 1000);
// 存入redis,不设置物理过期时间
redistemplate.opsforvalue().set(key, cachedata);
}以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论