当前位置: 首页 > news >正文

【SpringBoot篇】解决缓存击穿问题① — 基于互斥锁方式

文章目录

  • 🌹什么是缓存击穿
  • 🌺基于互斥锁解决问题
    • 🛸思路
  • 🏳️‍🌈代码实现

在这里插入图片描述

🌹什么是缓存击穿

缓存击穿是指在使用缓存系统时,对一个热点数据的高并发请求导致缓存失效,多个请求同时访问数据库,造成数据库压力过大,性能下降。

具体来说,缓存击穿通常发生在以下情况下:

  • 热点数据失效:当某个热点数据的缓存过期或被删除时,此时如果有大量的并发请求同时访问该数据,缓存系统无法命中缓存,每个请求都会直接访问数据库。
  • 频繁更新数据:某个数据被频繁地修改,导致缓存频繁失效,而此时大量的请求同时访问该数据,造成缓存击穿。

缓存击穿会严重影响系统的性能和可用性,因为数据库无法处理如此高的并发请求,导致系统响应变慢甚至崩溃。

但是对于缓存击穿,我们有什么方法可以解决呢

🌺基于互斥锁解决问题

互斥锁(Mutex)是一种并发编程中用于保护共享资源的机制,它可以确保在同一时刻只有一个线程可以访问共享资源,从而避免多个线程同时对共享资源进行读写操作而导致的数据竞争和不确定性行为。

互斥锁的主要特点包括:

  • 独占性:当一个线程获得了互斥锁后,其他线程就无法再获得该互斥锁,直到持有该锁的线程释放它。
  • 阻塞和等待:如果一个线程尝试获取已被其他线程持有的互斥锁,那么它会被阻塞,直到该互斥锁被释放。
  • 原子性:互斥锁的获取和释放操作是原子的,不会被打断。

互斥锁通常用于以下场景:

  • 在多线程环境下保护共享资源,如共享变量、共享数据结构等,防止多个线程同时修改造成数据不一致。
  • 控制对临界区的访问,确保同一时间只有一个线程能够执行临界区代码,以避免竞态条件(Race Condition)的发生。

🛸思路

使用互斥锁来解决缓存击穿问题的思路是通过对关键代码块进行加锁保证在同一时间只有一个线程能够执行这段代码。这样可以有效地避免多个线程同时访问数据库,减轻数据库的压力,提高系统的性能和可用性。

在解决缓存击穿问题时,通常会使用互斥锁锁住以下几个关键步骤:

  • 检查缓存:首先检查缓存中是否存在所需数据。
  • 缓存失效处理:如果缓存中不存在所需数据,即缓存失效,需要进行进一步处理。
  • 加锁:在进行缓存失效处理之前,获取互斥锁,确保只有一个线程能够执行后续的数据库查询和缓存更新操作。
  • 数据查询和缓存更新:在成功获得互斥锁之后,执行数据库查询操作,获取所需数据,并将数据更新到缓存中。
  • 释放锁:缓存更新完成后,释放互斥锁,允许其他等待的线程获得锁并从缓存中获取数据。

通过加锁的方式,保证了同一时间只有一个线程能够执行关键代码块,避免了缓存击穿问题。其他线程在等待期间可以从缓存中获取旧数据,而不会直接访问数据库。这样可以减少数据库的并发访问压力,提升了系统的并发能力和性能。

需要注意的是,互斥锁的使用应该谨慎,避免持有锁的时间过长,否则可能会导致其他线程的延迟和性能下降。在设计时,要权衡锁的粒度和性能需求,确保互斥锁的使用场景合理,并根据具体情况选择合适的锁机制(如读写锁、分布式锁等)进行优化。

🏳️‍🌈代码实现

我们看下面的例子
请添加图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        //缓存穿透
//        Shop shop=queryWithPassThrough(id);

        //互斥锁解决缓存击穿
        Shop shop=queryWithMutex(id);
        if(shop==null){
            return Result.fail("店铺不存在");
        }

        //返回
        return Result.ok(shop);
    }

    public Shop queryWithMutex(Long id){
        String key=CACHE_SHOP_KEY+":"+id;
        //从redis中查询缓存
        String shopJson=stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if(StrUtil.isNotBlank(shopJson)){
            //存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }

        //实现缓存重建
        //获取互斥锁
        String lockKey="lock:shop"+id;
        Shop shop=null;

        try {
            boolean isLock=tryLock(lockKey);
            //判断是否获取成功
            if (!isLock){
                //失败,那么休眠并且重试
                Thread.sleep(100);
                return queryWithMutex(id);
            }
            //成功,则根据id查询数据库
            shop=getById(id);
            //不存在,返回错误
            if(shop==null){
                //将空值写入到redis
                stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
                return null;
            }
            //存在,写入到redis里面
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);

        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            //释放互斥锁
            unlock(lockKey);
        }


        //返回
        return shop;
    }

        //存在,写入到redis里面
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
        //返回
        return shop;
    }

    //获取锁
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }
}

在技术的道路上,我们不断探索、不断前行,不断面对挑战、不断突破自我。科技的发展改变着世界,而我们作为技术人员,也在这个过程中书写着自己的篇章。让我们携手并进,共同努力,开创美好的未来!愿我们在科技的征途上不断奋进,创造出更加美好、更加智能的明天!

在这里插入图片描述

相关文章:

  • 开发语言:ArkTS
  • Redis基础篇-003 Redis数据结构及常用命令
  • Netty RPC 实现(二)
  • beaglebone black狗板,交叉编译Qt5(eglfs)
  • IntelliJ IDE 插件开发 | (三)消息通知与事件监听
  • UE5 Landscape 制作GIS卫星图地形
  • 在使用mapstruct,想忽略掉List<DTO>字段里面的,`data` 字段的映射, 如何写ignore: 使用@IterableMapping
  • 自定义Taro上传图片hooks(useUploadImg)
  • 让生活更智能,P1600边缘智能网关带你进入智能家居新时代
  • C#学习笔记 - C#基础知识 - C#从入门到放弃 - C# 结构、类与属性
  • 【起草】【第六章】ChatGPT 在软件测试的应用场景
  • 物联网主机E6000:引领智能安防新时代
  • display:grid
  • 【Java之数据结构与算法】
  • .NET面试题(二)
  • 在 Go 语言中使用 regexp 包处理正则表达式
  • node实现简单的数据爬虫
  • Python轴承故障诊断 (八)基于EMD-CNN-GRU并行模型的故障分类
  • 【C语言】动态内存管理基础知识——动态通讯录,如何实现通讯录容量的动态化
  • 【JavaWeb学习笔记】14 - 三大组件其二 Listener Filter
  • 赵作海因病离世,妻子李素兰希望过平静生活
  • 毕赣新作《狂野时代》入围戛纳主竞赛单元,易烊千玺舒淇主演
  • 江苏省人社厅党组书记、厅长王斌接受审查调查
  • 水利部:山西、陕西等地旱情将持续
  • 吴勇强、高颜已任南京市委常委
  • 夜读丨母亲的手擀面