首页 > 其他分享 >查询缓存击穿解决方案

查询缓存击穿解决方案

时间:2025-01-19 09:57:00浏览次数:3  
标签:缓存 请求 解决方案 数据库 击穿 分片 id 分布式

查询缓存击穿解决方案

在现代分布式系统中,缓存技术是提高系统性能和降低数据库负载的重要手段。然而,当缓存中的数据过期或不可用时,可能会发生缓存击穿(Cache Breakdown)。缓存击穿会导致大量的请求直接访问数据库,从而给数据库带来较大的压力,甚至可能导致数据库崩溃。为了应对这一问题,我们需要采取一系列有效的缓存击穿解决方案。

1.什么是缓存击穿?

缓存击穿是指缓存中的某个数据在缓存过期或者被删除后,下一次请求在没有命中缓存的情况下,直接访问数据库,导致缓存失效或被清除的瞬间,数据库承受大量的并发请求。简单来说,缓存击穿是缓存不可用情况下,缓存请求集中访问数据库的现象。

示例:

  • 假设某个用户的个人信息被缓存到 Redis 中,但缓存设置了过期时间。
  • 如果在缓存过期时,突然有大量的请求同时访问该缓存,这时就会触发缓存击穿。
  • 所有请求都会绕过缓存,直接访问数据库,可能会导致数据库压力过大,响应延迟甚至崩溃。

2. 缓存击穿解决方案

2.1 预热 + 不过期

解决方案: 预热缓存是指在缓存数据过期之前提前将数据加载到缓存中。这样,在下一次请求时,即使缓存中的数据已经过期,新的数据也已被加载到缓存中,避免了缓存击穿的发生。

具体做法

  • 设置合理的缓存失效时间。
  • 在缓存即将过期时,提前通过后台任务刷新缓存。
  • 可以使用定时任务或者定时触发器在后台定期刷新缓存数据。

2.2 分布式锁之双重判定锁(旁路缓存模式)

解决方案: 双重判定锁(Double-Checked Locking)是一种减少并发访问对数据库影响的解决方案。它通过引入分布式锁,确保在缓存失效时,只有一个线程去查询数据库并更新缓存,其它线程等待缓存更新完毕后再访问缓存。旁路缓存模式是指当缓存无法查询时,先进行数据库查询,再通过分布式锁进行缓存更新。

public String selectTrain(String id) {
    // 查询缓存不存在,去数据库查询并放入到缓存
    String cacheData = cache.get(id);
    if (StrUtil.isBlank(cacheData)) {
        // 为避免大量请求同时访问数据库,通过分布式锁减少数据库访问量
        Lock lock = getLock(id);
        lock.lock();
        try {
            // 获取锁后双重判定
            cacheData = cache.get(id);
            // 理论上只有第一个请求加载数据库是有效的,因为它加载后会把数据放到缓存
            // 后面的请求再请求数据库加载缓存就没有必要了
            if (StrUtil.isBlank(cacheData)) {
                // 获取数据库中存在的数据
                String dbData = trainMapper.selectId(id);
                if (StrUtil.isNotBlank(dbData)) {
                    // 将查询到的数据放入缓存,下次查询就有数据了
                    cahce.set(id, dbData);
                    cacheData = dbData;
                }
            }
        } finally {
            lock.unlock();
        }
    }
  return cacheData;
}

3.高并发极端情况

在高并发情况下,如果请求数过多,可能会导致缓存穿透和击穿问题的恶化。有一万个请求同一时间访问触发了缓存击穿,如果用双重判定锁,逻辑是这样的:

第一个请求加锁、查询缓存是否存在、查询数据库、放入缓存、解锁,假设我们用了50毫秒;

第二个请求拿到锁查询缓存、解锁用了1毫秒;

那最后一个请求需要等待10049毫秒后才能返回,用户等待时间过长,极端情况下可能会触发应用的内存溢出。

3.1 尝试获取锁 tryLock

通过这种方式我们可以快速失败,告诉用户网络异常请稍后再试,等用户再尝试刷新的时候,其实获取锁的线程已经把数据放到了缓存。

因为这种方案对用户操作体验不友好,所以也只是适用于部分场景。在实际开发中,需要灵活变更。

public String selectTrain(String id) {
    // 查询缓存不存在,去数据库查询并放入到缓存
    String cacheData = cache.get(id);
    if (StrUtil.isBlank(cacheData)) {
        // 为避免大量请求同时访问数据库,通过分布式锁减少数据库访问量
        Lock lock = getLock(id);
        // 尝试获取锁,获取失败直接返回用户请求,并提醒用户稍后再试
        if (!lock.tryLock()) {
            throw new RuntimeException("当前访问人数过多,请稍候再试...");
        }
        try {
            // 获取数据库中存在的数据
            String dbData = trainMapper.selectId(id);
            if (StrUtil.isNotBlank(dbData)) {
                // 将查询到的数据放入缓存,下次查询就有数据了
                cahce.set(id, dbData);
                cacheData = dbData;
            }
        } finally {
            lock.unlock();
        }
    }
  return cacheData;
}
3.2 分布式锁分片

在高并发的分布式系统中,单一的分布式锁可能存在性能瓶颈,特别是在高并发请求下,所有请求都集中争夺同一个锁,可能导致阻塞和性能下降。为了避免这种情况,分布式锁分片可以通过将锁分配到多个锁空间中,使得不同请求可以同时获得不同的锁,从而减少竞争,提高系统的吞吐量。

分布式锁分片的基本原理:

分布式锁分片的核心思想是将原本的全局锁拆分为多个局部锁,每个锁对应不同的资源或数据片段。通过这种方式,可以避免所有请求都竞争同一个锁,从而提高并发性能。通常可以根据业务的特点,将数据进行分片(如通过 hashmod 操作),然后根据分片结果将请求分配到不同的锁上

3.2 分布式锁分片

在高并发的分布式系统中,单一的分布式锁可能存在性能瓶颈,特别是在高并发请求下,所有请求都集中争夺同一个锁,可能导致阻塞和性能下降。为了避免这种情况,分布式锁分片可以通过将锁分配到多个锁空间中,使得不同请求可以同时获得不同的锁,从而减少竞争,提高系统的吞吐量。

分布式锁分片的基本原理

分布式锁分片的核心思想是将原本的全局锁拆分为多个局部锁,每个锁对应不同的资源或数据片段。通过这种方式,可以避免所有请求都竞争同一个锁,从而提高并发性能。通常可以根据业务的特点,将数据进行分片(如通过 hashmod 操作),然后根据分片结果将请求分配到不同的锁上。

解决方案

  1. 将请求按某些维度分片:例如,可以按数据的 userIdproductId 等维度进行分片。每个分片拥有一个独立的锁。
  2. 分配每个请求一个分片:每个请求根据分片维度,获取一个对应的分片锁。这样,不同的请求可能会获取到不同的锁,减少锁竞争。
  3. 使用 Redis 实现分片锁:Redis 提供了 setIfAbsent 等命令,非常适合用于实现分布式锁。通过为每个分片加锁,可以实现分布式锁分片的效果。

标签:缓存,请求,解决方案,数据库,击穿,分片,id,分布式
From: https://blog.csdn.net/sjsjsbbsbsn/article/details/145238607

相关文章

  • Redis 深度解析:从基础到进阶,全面掌握高效缓存技术
    Redis深度解析:从基础到进阶,全面掌握高效缓存技术引言:Redis作为现代开发中不可或缺的技术之一Redis(RemoteDictionaryServer)作为一种开源的高性能键值数据库,在实际开发中发挥着至关重要的作用。它以其极高的读写性能、丰富的数据结构、持久化机制以及支持多种编程语言的客......
  • 【鱼皮大佬API开放平台项目】Spring Cloud Gateway HTTPS 配置问题解决方案总结
    问题背景项目架构为前后端分离的微服务架构:前端部署在8000端口API网关部署在9000端口后端服务包括:api-backend(9001端口)api-interface(9002端口)初始状态:前端已配置HTTPS(端口8000)后端服务未配置HTTPS通过Nginx进行反向代理遇到的问题第一阶段:400Ba......
  • 分布式系统架构7:本地缓存
    1.引入缓存的影响我们在开发时,用到缓存的情况,无非就是为了减少客户端对相同资源的重复请求,降低服务器的负载压力。引入缓存后,既有好处也有坏处引入缓存负面影响:开发角度,增加了系统复杂度,需考虑缓存失效、更新、一致性问题运维角度,缓存会掩盖一些缺陷问题安全角度,缓......
  • JS上传文件夹的三种解决方案
    要求:免费,开源,技术支持技术:百度webuploader,分块,切片,断点续传,秒传,MD5验证,纯JS实现,支持第三方软件集成前端:vue2,vue3,vue-cli,html5,webuploader后端:asp.net,.netmvc,.netcore,asp,jsp,java,springboot,php,数据库:MySQL,Oracle,SQLServer,达梦,人大金仓,国产数据库平......
  • 内存和缓存有什么区别?
    内存和缓存之间的区别主要体现在以下几个方面:主体不同:内存是计算机中重要的部件之一,它是外存与CPU进行沟通的桥梁,用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。缓存则是指访问速度比一般随机存取存储器(RAM)快的一种高速存储器,它可以进行高速数据交换,先于内......
  • 高级java每日一道面试题-2025年01月16日-框架篇[Mybatis篇]-说说Mybatis的缓存机制?
    如果有遗漏,评论区告诉我进行补充面试官:说说Mybatis的缓存机制?我回答:在Java高级面试中,MyBatis的缓存机制是一个重要的话题。MyBatis是一个流行的Java持久化框架,它提供了强大的数据库访问能力和灵活的SQL映射配置。为了提高查询性能并减少数据库访问次数,MyBatis引入了......
  • 自主研发驱动程序不仅能为硬件设备提供定制化支持,提升性能和稳定性,还能够增强企业的技
    自主研发驱动程序是指由企业或组织自行设计、开发并实现的硬件设备驱动程序。驱动程序(DeviceDriver)是操作系统和硬件之间的桥梁,用于控制硬件设备的功能和提供操作系统与硬件设备间的通信接口。自主研发驱动程序通常是为了满足特定硬件或操作环境的需求,提升硬件的性能、兼容性和稳......
  • 网站目录中的PHP脚本无法写入,导致缓存文件生成失败
    根据您的描述,您遇到了网站目录中的PHP脚本无法写入的问题,这直接影响了缓存文件的生成,进而导致网站运行不正常。具体来说,espcms_datacache/_templates 和 espcms_datacache/dbcache 目录下的PHP文件无法写入,这对网站性能和功能产生了负面影响。要解决这个问题,您可以按照以下步......
  • 分布式系统架构7:本地缓存
    这是小卷对分布式系统架构学习的第10篇文章,在开始学习分布式缓存之前,先来学习本地缓存的理论基础,了解为什么需要用缓存1.引入缓存的影响我们在开发时,用到缓存的情况,无非就是为了减少客户端对相同资源的重复请求,降低服务器的负载压力。引入缓存后,既有好处也有坏处引入缓存负面......
  • 使用箭头注释增加PDF清晰度-MESCIUS PDF文档解决方案
    使用箭头注释增加PDF的清晰度2025年1月16日方向标记增强了文档审查和技术插图,提供更清晰的交流并提高PDF的可读性。MESCIUS的PDF文档解决方案箭头注释是一种可视化工具,允许用户使用箭头作为方向指示器来吸引用户对PDF中特定元素的注......