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


    条件

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

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

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

    实现

    1. @Service
    2. public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService {
    3. @Resource
    4. private RedissonClient redissonClient;
    5. @Override
    6. public List test() throws Exception {
    7. Object roles = redissonClient.getBucket("role").get();
    8. // 先查询缓存,缓存中有则直接返回
    9. if (Objects.nonNull(roles)) {
    10. return JSON.parseArray(roles.toString(), SysRoleDO.class);
    11. }
    12. RLock lock = redissonClient.getLock("role-lock");
    13. boolean isLock = lock.tryLock();
    14. if (isLock) {
    15. // 获取到锁查询数据库,并将查询结果放入缓存
    16. try {
    17. Object roleList = redissonClient.getBucket("role").get();
    18. // 双重检查锁,当多个线程同时判断到缓存中取不到值,上一个获取到锁的线程已经将数据放入缓存,下一个线程直接取缓存
    19. if (Objects.nonNull(roleList)) {
    20. return JSON.parseArray(roleList.toString(), SysRoleDO.class);
    21. }
    22. // 查询数据库
    23. List list = this.list();
    24. // 将数据放入缓存
    25. redissonClient.getBucket("role").set(list, 60L, TimeUnit.SECONDS);
    26. return list;
    27. } finally {
    28. lock.unlock();
    29. }
    30. }
    31. int retryTimes = 3;
    32. Object roleList = null;
    33. // 当缓存中取不到值时sleep300毫秒,最多循环3次
    34. while (Objects.isNull(roleList) && retryTimes > 0) {
    35. // 休眠300ms后递归
    36. TimeUnit.MILLISECONDS.sleep(300L);
    37. roleList = redissonClient.getBucket("role").get();
    38. retryTimes--;
    39. }
    40. // 循环等待后缓存中取到值直接返回,仍然取不到值则抛异常
    41. if (Objects.nonNull(roleList)) {
    42. return JSON.parseArray(roleList.toString(), SysRoleDO.class);
    43. }
    44. throw new RuntimeException("查询异常");
    45. }
    46. }

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

    1.在设置key的时候过期时间字段并一块存入缓存,不给当前key设置过期时间。

    2.当查询的时候在redis中判断是否过期,条件就是字段设置时间与当前时间对比。

    3.如果过期就开通另一个线程进行数据同步,当前线程正常返回数据,但数据就不是最新的时老的数据不能保证强一致。

    实现

    1. //逻辑过期
    2. public Shop queryWithLogicalExpire(Long id) {
    3. String key = CACHE_SHOP_KEY + id;
    4. //1.从redis查询商铺缓存
    5. String shopJson = stringRedisTemplate.opsForValue().get(key);
    6. //2.判断是否存在
    7. if (StrUtil.isBlank(shopJson)) {
    8. //3.未命中
    9. return null;
    10. }
    11. //4.命中,需要先把json反序列化为对象
    12. RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
    13. Shop shop = (Shop) redisData.getData();
    14. LocalDateTime expireTime = redisData.getExpireTime();
    15. //5.判断是否过期
    16. if (expireTime.isAfter(LocalDateTime.now())) {
    17. //5.1还未过期
    18. return shop;
    19. }
    20. //5.2已经过期,需要缓存重建
    21. //6.缓存重建
    22. //6.1获取互斥锁
    23. String lockKey = LOCK_SHOP_KEY + id;
    24. boolean isLock = tryLock(lockKey);
    25. //6.2判断是否获取锁成功
    26. if (isLock) {
    27. // 6.3成功,开启独立线程,实现缓存重建
    28. CACHE_REBUILD_EXECUTOR.submit(() -> {
    29. try {
    30. //重建缓存
    31. this.saveShop2Redis(id, 20L);
    32. } catch (Exception e) {
    33. e.printStackTrace();
    34. } finally {
    35. //释放锁
    36. unlock(lockKey);
    37. }
    38. });
    39. }
    40. //6.4返回过期的店铺信息
    41. //7.返回
    42. return shop;
    43. }

    总结

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

  • 相关阅读:
    【Android Studio Gradle】发布aar到私有Artifactory仓库
    C++ 多线程 线程安全关联性map设计
    记一次 .NET 差旅管理后台 CPU 爆高分析
    花了几百万,仍然无法消除「数据孤岛」,这份数字化建设方案下载
    Linux的简单使用
    射频信号处理知识点点滴滴
    信息隐藏与探索 中职网络安全
    【K8S】Kubernetes常用命令
    JXLS2同一个sheet多个表格循环覆盖下面表格数据问题
    Flutter快学快用10 路由设计:Flutter 中是如何实现 Scheme 跳转的
  • 原文地址:https://blog.csdn.net/2301_77181435/article/details/133050765