您的位置:首页 > 健康 > 美食 > 缓存击穿问题

缓存击穿问题

2024/10/14 2:15:49 来源:https://blog.csdn.net/qq_46637011/article/details/142055294  浏览:    关键词:缓存击穿问题

缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。

解决方案:

1、使用同步锁或分布式锁控制。

2、热点数据永不过期。

3、缓存预热,分为提前预热、定时预热

4、降级处理

1. 什么是缓存击穿

缓存击穿是指当大量的请求同时访问某个热点数据,而该数据在缓存中失效或到期的瞬间,所有请求同时绕过缓存直接请求数据库,导致数据库瞬间负载过高,严重时可能导致数据库宕机或服务崩溃。

缓存击穿通常发生在以下场景:

  • 某个热点数据有大量的请求,而这个数据正好在缓存中失效。
  • 缓存失效后,所有请求同时到达数据库,导致数据库压力瞬间暴增。

2. 解决方案

2.1 使用同步锁或分布式锁控制

为了解决缓存失效后多个线程同时访问数据库的问题,可以通过加锁来控制访问。在数据失效时,只有一个线程能去数据库查询,其他线程等待这个线程将数据加载到缓存后,再从缓存读取数据。常见的两种锁机制有:

(1)单体架构下(单进程内)可以使用同步锁控制查询数据库的代码,只允许有一个线程去查询数据库,查询得到数据库存入缓存。

synchronized(obj){//查询数据库//存入缓存
}

(2)分布式架构下(多个进程之间)可以使用分布式锁进行控制。分布式锁确保同一时刻只有一个实例可以去数据库查询,其他实例等待查询结果。

// 获取分布式锁对象
RLock lock = redisson.getLock("myLock");
try {// 尝试加锁,最多等待100秒,加锁后自动解锁时间为30秒boolean isLocked = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);if (isLocked) {//查询数据库//存入缓存} else {System.out.println("获取锁失败,可能有其他线程持有锁");}
} catch (InterruptedException e) {e.printStackTrace();
} finally {// 释放锁lock.unlock();System.out.println("释放锁...");
}

2.2 热点数据永不过期

对于一些非常重要且访问量大的数据,可以通过设置这些热点数据永不过期的方式,避免其从缓存中失效。这样的话,缓存击穿的问题就不会发生。

redisTemplate.opsForValue().set("hotData", value, Duration.ofDays(Long.MAX_VALUE));

需要注意的是,虽然设置了永不过期,但还是要设计相应的后台机制来手动更新缓存中的数据,以保持数据的及时性。

2.3 缓存预热

缓存预热是指在系统启动或缓存失效之前,提前将热点数据加载到缓存中,避免缓存失效时发生大量请求同时访问数据库的情况。缓存预热可以通过以下两种方式实现:

(1)提前预热:在系统上线前,提前将已知的热点数据加载到缓存中。通常可以通过后台程序或者脚本,预先将这些热点数据从数据库中查询出来并缓存。

@PostConstruct
public void preheatCache() {List<User> hotUsers = database.getHotUsers();  // 获取热点数据for (User user : hotUsers) {cache.put(user.getId(), user);  // 预热缓存}
}

(2)定时预热:对于某些数据可以设置定时任务,在缓存即将到期时,自动刷新缓存,避免缓存过期导致的击穿。

@Scheduled(fixedDelay = 60000)  // 每隔一分钟执行一次
public void refreshCache() {List<User> hotUsers = database.getHotUsers();for (User user : hotUsers) {cache.put(user.getId(), user);  // 更新缓存}
}

2.4 降级处理

当缓存失效并且数据库负载过高,系统可选择进行降级处理,避免继续增加数据库压力。这种策略可以通过以下方式实现:

  • 返回默认值:当缓存和数据库都不可用时,返回一些默认的静态数据,或者告知用户系统繁忙稍后再试,避免对数据库造成更大压力。

  • 限流:在缓存失效时,对某些重要的接口进行限流,只允许一部分请求通过,其余的请求返回降级响应。这样可以保护数据库不被大量请求压垮。

@Service
public class UserService {private RateLimiter rateLimiter = RateLimiter.create(10);  // 每秒最多允许10个请求public User getUserById(Long id) {if (!rateLimiter.tryAcquire()) {return new User();  // 降级,返回默认值}User user = cache.get(id);if (user == null) {user = database.getUserById(id);cache.put(id, user);}return user;}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com