缓存击穿是应为Redis某个缓存数据设置了过期时间,而刚好有大并发数据请求这个数据,导致DB有大量请求,引发DB崩溃。
当缓存失效时不立即删除缓存而是用setnx设置一个互斥锁,当操作完成后在load db,并回设缓存,否则重试get缓存方法,这样就减少了直接大量访问DB的请求。
- @Service
- public class SysRoleServiceImpl extends ServiceImpl
implements SysRoleService { -
- @Resource
- private RedissonClient redissonClient;
-
- @Override
- public List
test() throws Exception { - Object roles = redissonClient.getBucket("role").get();
- // 先查询缓存,缓存中有则直接返回
- if (Objects.nonNull(roles)) {
- return JSON.parseArray(roles.toString(), SysRoleDO.class);
- }
- RLock lock = redissonClient.getLock("role-lock");
- boolean isLock = lock.tryLock();
- if (isLock) {
- // 获取到锁查询数据库,并将查询结果放入缓存
- try {
- Object roleList = redissonClient.getBucket("role").get();
- // 双重检查锁,当多个线程同时判断到缓存中取不到值,上一个获取到锁的线程已经将数据放入缓存,下一个线程直接取缓存
- if (Objects.nonNull(roleList)) {
- return JSON.parseArray(roleList.toString(), SysRoleDO.class);
- }
- // 查询数据库
- List
list = this.list(); - // 将数据放入缓存
- redissonClient.getBucket("role").set(list, 60L, TimeUnit.SECONDS);
- return list;
- } finally {
- lock.unlock();
- }
- }
- int retryTimes = 3;
- Object roleList = null;
- // 当缓存中取不到值时sleep300毫秒,最多循环3次
- while (Objects.isNull(roleList) && retryTimes > 0) {
- // 休眠300ms后递归
- TimeUnit.MILLISECONDS.sleep(300L);
- roleList = redissonClient.getBucket("role").get();
- retryTimes--;
- }
- // 循环等待后缓存中取到值直接返回,仍然取不到值则抛异常
- if (Objects.nonNull(roleList)) {
- return JSON.parseArray(roleList.toString(), SysRoleDO.class);
- }
- throw new RuntimeException("查询异常");
- }
- }
1.在设置key的时候过期时间字段并一块存入缓存,不给当前key设置过期时间。
2.当查询的时候在redis中判断是否过期,条件就是字段设置时间与当前时间对比。
3.如果过期就开通另一个线程进行数据同步,当前线程正常返回数据,但数据就不是最新的时老的数据不能保证强一致。
- //逻辑过期
- public Shop queryWithLogicalExpire(Long id) {
- String key = CACHE_SHOP_KEY + id;
- //1.从redis查询商铺缓存
- String shopJson = stringRedisTemplate.opsForValue().get(key);
- //2.判断是否存在
- if (StrUtil.isBlank(shopJson)) {
- //3.未命中
- return null;
- }
- //4.命中,需要先把json反序列化为对象
- RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
- Shop shop = (Shop) redisData.getData();
- LocalDateTime expireTime = redisData.getExpireTime();
- //5.判断是否过期
- if (expireTime.isAfter(LocalDateTime.now())) {
- //5.1还未过期
- return shop;
- }
- //5.2已经过期,需要缓存重建
- //6.缓存重建
- //6.1获取互斥锁
- String lockKey = LOCK_SHOP_KEY + id;
- boolean isLock = tryLock(lockKey);
- //6.2判断是否获取锁成功
- if (isLock) {
- // 6.3成功,开启独立线程,实现缓存重建
- CACHE_REBUILD_EXECUTOR.submit(() -> {
- try {
- //重建缓存
- this.saveShop2Redis(id, 20L);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- //释放锁
- unlock(lockKey);
- }
- });
- }
- //6.4返回过期的店铺信息
- //7.返回
- return shop;
- }
如果要求数据的强一致测使用分布式锁,如果要求高可用就使用逻辑过期就可以了。