Redis面试二“缓存击穿是什么”

条件

缓存击穿是应为Redis某个缓存数据设置了过期时间,而刚好有大并发数据请求这个数据,导致DB有大量请求,引发DB崩溃。

第一种方法就是设置互称锁

当缓存失效时不立即删除缓存而是用setnx设置一个互斥锁,当操作完成后在load db,并回设缓存,否则重试get缓存方法,这样就减少了直接大量访问DB的请求。

实现

@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleDao, SysRoleDO> implements SysRoleService {

    @Resource
    private RedissonClient redissonClient;

    @Override
    public List<SysRoleDO> 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<SysRoleDO> 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("查询异常");
    }
}

第二种解决缓存击穿的实现就是设置key逻辑过期时间

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;
    }

总结

如果要求数据的强一致测使用分布式锁,如果要求高可用就使用逻辑过期就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值