缓存击穿只会逻辑过期 OR 互斥锁?深入思考 == 鹤立鸡群

news2025/7/17 18:24:35

        网上但凡看得见的文章,大部分在说缓存穿透时都是无脑分布式锁 / 逻辑过期,分布式锁一点问题都没有么?逻辑过期一点问题都没有么?还能不能再进一步优化?

        在聊聊缓存击穿的双重判定锁之前,我们将按照循循渐进的方式讲解,什么是缓存击穿?常规解决方案的弊端是啥?缓存击穿解决方案能不能进一步优化?

什么是缓存击穿?

        缓存击穿是指一个缓存中的数据因为某种原因(通常是缓存中的数据过期或者被删除),在短时间内遭受大量的请求,导致这些请求直接穿透到数据库或其他后端存储系统,增加了后端系统的负载。

缓存击穿通常在以下情况下发生:

  1. 热点数据过期:当缓存中存储的热门数据过期时,大量的请求会同时查询后端数据库。
  2. 第一次请求:对于一个之前从未被请求过的数据,当它第一次被请求时,缓存中没有这个数据,从而导致请求穿透到后端存储。

image.png

常规解决方案的弊端

为了解决Redis缓存击穿问题,八股文都是直接采取以下方案:

  1. 设置热点数据永不过期:对于一些热点数据,可以将其永不过期,确保即使数据过期后,仍然可以从缓存中获取。
  2. 使用互斥锁:在获取数据时,使用分布式锁(如 Redis 的分布式锁)来控制同时只有一个请求可以去后端获取数据,其他请求需要等待锁释放。这样可以防止多个请求同时穿透到后端存储。

思考一

        设置热点数据永不过期属于是业务范围应该考虑的事情,这个数据是否应该永不过期?或者说活动时设置过期时间为 -1,活动后再执行程序删除。有一点可以确认,缓存数据不可能全部都是永不过期,因为缓存的存储压力会比较大,所以该方案无法作为通用方案。

那我们可以从互斥锁上面下下文章,如何通过互斥锁完成缓存击穿场景解决方案?

思考二

        在获取数据时,使用分布式锁(如 Redis 的分布式锁)来控制同时只有一个请求可以去后端获取数据,其他请求需要等待锁释放。这样可以防止多个请求同时穿透到后端存储。

image.png

伪代码如下:

public String selectTrain(String id) {
	String cacheData = cache.get(id);
	if (StrUtil.isBlank(cacheData)) {
        Lock lock = getLock(id);
        lock.lock();
        try {
            String dbData = trainMapper.selectId(id);
            if (StrUtil.isNotBlank(dbData)) {
        		cahce.set(id, dbData);
                cacheData = dbData;
            }
        } finally {
            lock.unlock();
        }
    }
	return cacheData;
}

        这种方案有效地避免了缓存穿透问题,因为只有一个线程能够在同一时间内查询数据库,其他线程需要等待,不会同时穿透到后端存储系统。但是!!!其实上面的方案还是有个小细节滴~~

双重判定锁

        上边还有一个问题就是,假如 100w 的请求读取一个缓存,100w 的请求全部卡在 lock.lock 获取分布式锁处,只有一个线程会执行逻辑请求数据库并放入缓存。问题来了,因为接下来的所有请求,99.99...w 还是会继续请求数据库,大家读一下上面的伪代码就明白了。(其实就是因为99.99...个请求都阻塞在Lock lock = getLock(id)这一步,而不是if (StrUtil.isBlank(cacheData))这一步。

这会造成两个实际的问题:

  1. 全部用户获取锁后查询数据库,会对数据库造成无用的性能浪费,因为这 100w 的请求,只有第一次是有效的。
  2. 查询数据库会造成用户响应时间变长,接口吞吐量下降。

双重判断:获取锁后,在查询数据库之前,再次检查一下缓存中是否存在数据。这是一个双重判断,如果缓存中存在数据,就直接返回;如果不存在,才继续执行查询数据库的操作。

image.png

伪代码如下:

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

下面是这种解决方案的一般步骤:

  1. 获取锁:在查询数据库前,首先尝试获取一个分布式锁。只有一个线程能够成功获取锁,其他线程需要等待。
  2. 查询数据库:如果双重判断确认数据确实不存在于缓存中,那么就执行查询数据库的操作,获取数据。
  3. 写入缓存:获取到数据后,将数据写入缓存,并设置一个合适的过期时间,以防止缓存永远不会被更新。
  4. 释放锁:最后,释放获取的锁,以便其他线程可以继续使用这个锁。

文末总结

        本文深入探讨了缓存击穿问题以及解决方案。首先,我们了解了缓存击穿是指一个缓存中的数据因为某种原因(通常是缓存中的数据过期或者被删除),在短时间内遭受大量的请求,从而直接查询数据库,对系统性能造成了巨大的压力。然后,我们介绍了解决缓存击穿问题的一种有效方式——分布式互斥锁,以及分布式互斥锁的升级版本——双重判定锁。

总结一下:

  • 缓存击穿问题是指某些请求查询缓存中不存在的数据,导致大量请求直接穿透到后端数据库,对系统性能造成严重影响。
  • 分布式互斥锁是一种解决缓存击穿的有效方式。它通过在查询缓存之前尝试获取锁,只有一个线程能够成功获取锁,其他线程需要等待。这确保了只有一个线程可以查询数据库,避免了大规模的穿透。
  • 双重判定锁是分布式互斥锁的升级版本。它在获取锁后,再次检查缓存中是否存在数据,以避免重复查询数据库。只有在确认缓存中不存在数据时,才继续查询数据库。

        这两种解决方案都能够有效地应对缓存击穿问题,但需要根据实际情况选择合适的方式。分布式互斥锁适用于大多数场景,而双重判定锁则提供了更高的效率,适用于一些特定的场景。选择合适的解决方案,可以保护系统免受缓存击穿问题的困扰,提高性能和可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1157718.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java基础之IO操作

用户进程发起请求,内核接收到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端。 数据输入到buffer需要时间,从buffer复制数据至进程也需要时间…

社区参展招募| 与新兴初创企业一起!

一般来说,创业公司的生存率较低,失败率较高。根据不同的数据来源,创业公司的失败率高达 80%-90%。据统计,在中国每年新注册的企业数量超过 100 万家,但能够存活到 5 年以上的企业不足 7%,10 年以上不足 2%。…

路径复杂度(环形回路的复杂度计算)

路径复杂度 1、通用公式: (EF) - N12、非环形回路的复杂度计算公式为什么1?公式为什么(EF)-N? 3、类推到环形回路的复杂度演示区分下纯环形回路 和 不是纯粹的环形回路 3、特殊情况:自旋公式化理解:此时将B自旋回路看成一个环形回…

混合编程 ATPCS规范及案例(汇编调用C、C调用汇编、内联汇编)

1.混合编程的规范 2.汇编调用C 2.C调用汇编 3.内联汇编 例子:

高速视觉筛选机PCIe实时运动控制卡XPCIE1028亮相深圳慕尼黑华南电子展

本次的深圳慕尼黑华南电子展正运动技术将携高速视觉筛选机PCIe实时运动控制卡XPCIE1028亮相。此外,我们还为您准备了的新互动模式,您将有机会赢得超值礼品! 产品导读 正运动技术的PCI Express总线运动控制卡XPCIE1028,具备位置…

二叉树初次的整体过程

1.计算二叉树节点的范围 2.计算用父亲下标找出儿子的下标&#xff0c;用儿子的下标找出父亲的下标方法 2.向下调整算法 void AdjustUp(HPDataType*a,int child) {int parent (child - 1) / 2;while (0 < child){if (a[child] < a[parent]){HPDataType* tem a[child];a[…

Linux常用命令——chgrp命令

在线Linux命令查询工具 chgrp 用来变更文件或目录的所属群组 补充说明 chgrp命令用来改变文件或目录所属的用户组。该命令用来改变指定文件所属的用户组。其中&#xff0c;组名可以是用户组的id&#xff0c;也可以是用户组的组名。文件名可以 是由空格分开的要改变属组的文…

mpp解码详解

解码器数据流接口 一. decode_put_packet 输入码流的形式&#xff1a;分帧与不分帧 MPP 的输入都是没有封装信息的裸码流&#xff0c;裸码流输入有两种形式&#xff1a; 不分帧 这种方式是已经按帧分段的数据&#xff0c;即每一包输入给 decode_put_packet 函数的 MppPacket 数…

PCIe 的 MSI 中断详解,寄存器级别的详细流程分析,完全搞懂硬件的工作流程

PCIe 的 MSI 中断 前言 什么是 MSI 中断 (Message Signaled Interrupts) 概念与内容介绍待补充 正文 对 EP 的初始化 需要对 EP 的配置空间 MSI 相关功能的寄存器进行初始化&#xff0c;主要有两个寄存器 Message Address 和 Message Data。它们分别的含义是 EP 产生 MSI …

关于JSON 字符串或对象互转

目录 1、 识别不了空数组&#xff0c;只能打印出来{__ob__: Observer} 2、遇到把字符串解析为对象 1、 识别不了空数组&#xff0c;只能打印出来{__ob__: Observer} 遇到了这种&#xff0c;可以使用 "JSON.stringify(JSON.parse(JSON.stringify(obj))" 解析成空对象…

B站数据质量保障体系建设与实践

本文将分享 B 站数据质量保障体系的建设和实践。文章将关注数仓和建模的相关方法论&#xff0c;讲解 B 站数仓平台团队在数仓建设和建模过程中所做的工作&#xff0c;并分享质量保障方面取得的成果。 一、背景目标 首先&#xff0c;分享一下 B 站数据质量保障的背景和目标。 …

onnx 模型加载部署运行方式

1.onnx文件加载方式在onnxruntime下是: env Ort::Env(OrtLoggingLevel::ORT_LOGGING_LEVEL_WARNING, "YOLOV8");sessionOptions Ort::SessionOptions();std::wstring w_modelPath charToWstring(mnnModelPath.c_str());session Ort::Session(env, w_modelPath.c_…

输入日期是当年的第n天

从键盘输入正确日期&#xff0c;程序输出是当年的第n天。 (本笔记适合熟悉循环和列表的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么…

Springboot整合Minio实现文件上传和下载

目录 1. Minio 1.1 Minio下载 2. Springboot和Minio实现文件存储 1. Minio Minio是一个灵活、高性能、开源的对象存储解决方案&#xff0c;适用于各种存储需求&#xff0c;并可以与云计算、容器化、大数据和应用程序集成。它为用户提供了自主控制和可扩展性&#xff0c;使其…

Redis系统学习(高级篇)-Redis持久化-RDB方式

目录 一、RDB是什么&#xff1f; 二、RDB的执行时机 三、RDB的其他命令 四、RDB的执行原理 五、RDB的优缺点 一、RDB是什么&#xff1f; RDB全称叫Redis Database Backup file&#xff08;Redis数据备份文件&#xff09; &#xff0c;也叫Redis数据快照&#xff0c;就是将…

针对zkVM中Memory Consistency Checks的Polynomial IOPs

1. 引言 主要参考Yuncong Zhang等人2023年论文《Polynomial IOPs for Memory Consistency Checks in Zero-Knowledge Virtual Machines》。 在设计zkvm时&#xff0c;需检查其所有组件的功能一致性&#xff0c;包括&#xff1a; instruction fetcher寄存器文件算术化逻辑单元…

模型实际训练笔记1—AlexNet

1、AlexNet网络模型介绍&#xff1a; AlexNet 是一种深度卷积神经网络&#xff0c;由Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 在2012年开发。它是深度学习领域的重要里程碑&#xff0c;因为它在当年的 ImageNet 大规模图像分类竞赛&#xff08;ILSVRC&#xff09…

【深度学习】pytorch——快速入门

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ pytorch快速入门 简介张量&#xff08;Tensor&#xff09;操作创建张量向量拷贝张量维度张量加法函数名后面带下划线 _ 的函数索引和切片Tensor和Numpy的数组之间的转换张量&#xff08;tensor&#xff09;与标量…

使用 for 和 mv 批量修改文件名

我们知道&#xff0c;linux 中可以使用 mv 命令来移动或者重命名文件和目录&#xff0c;且不会改变 inode 编号和时间戳。其语法如下&#xff1a; mv [-f | -i | -n] [-hv] source target mv [-f | -I | -n] [-v] source … directory 但是&#xff0c;mv 命令一次只能操作一个…

AXI总线实操

1.源码来源vivado xilinx官方实例 2.[2:0]M_AXI_ARSIZE:指的是一个 data transfer 里面有多少 Bytes——这里我写的是3&#xff08;即2^38B&#xff09;——8*864bit代表你想往axi总线的搬移数据的位宽。 3. Burst length: ARLEN[7:0]和AWLEN[7:0] 突发传输长度是指在一次突发…