缓存常见问题总结

news2024/10/1 3:24:45

目录

一:缓存穿透

解决方案

1.对请求增加校验机制

2.缓存空值或者特殊值

3.使用布隆过滤器 

布隆过滤器原理图​编辑 

哈希函数的基本特性

布隆过滤器为什么会存在误判

如何降低误判率

如何使用布隆过滤器

布隆过滤器的应用场景

如何使用布隆过滤器

第一步:引入依赖 

第二步,编写测试代码

 二:缓存击穿

解决方案 

1.加锁

2.合理设置热点数据过期时间 

 3.缓存预热

4.限流和熔断

三:缓存雪崩 

解决方案 

1.合理设置同一时期的key的过期时间

2.缓存定时预热

 3.加锁

4.使用降级或者限流策略 

四:缓存不一致 

缓存不一致有什么后果 

并发缓存不一致案例

解决方案 

1.使用分布式锁

2. 延迟双删

3.异步同步 

4.定时任务 


一:缓存穿透

缓存穿透:请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。
查询一个缓存中不存在的数据将会执行方法查询数据库,数据库也不存在此数据,查询完数据库也没有缓存数据,缓存没有起到作用。

解决方案

1.对请求增加校验机制

比如:查询的Id是长整型并且是19位,如果发来的不是长整型或不符合位数则直接返回不再查询数据库。

2.缓存空值或者特殊值

当查询数据库得到的数据不存在,此时我们仍然去缓存数据,缓存一个空值或一个特殊值的数据,避免每次都会查询数据库,避免缓存穿透。

流程如下:

3.使用布隆过滤器 

布隆过滤器(Bloom Filter)是一种数据结构,用于快速判断一个元素是否属于一个集合中。

它使用多个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点,将Bit array理解为一个二进制数组,数组元素是0或1。

当一个元素加入集合时,通过N个散列函数将这个元素映射到一个Bit array中的N个点,把它们设置为1。

检索某个元素时再通过这N个散列函数对这个元素进行映射,根据映射找到具体位置的元素,如果这些位置有任何一个0,则该元素一定不存在,如果都是1很可能存在误判。

布隆过滤器原理图
 
哈希函数的基本特性

同一个数使用同一个哈希函数计算哈希值,其哈希值总是一样的。

对不同的数用相同的哈希函数计算哈希值,其哈希值可能一样,这称为哈希冲突。

哈希函数通常是单向的不可逆的,即从哈希值不能逆向推导出原始输入。这使得哈希函数适用于加密和安全应用。

布隆过滤器为什么会存在误判

主要原因是哈希冲突。布隆过滤器使用多个哈希函数将输入的元素映射到位数组中的多个位置,当多个不同的元素通过不同的哈希函数映射到相同的位数组位置时就发生了哈希冲突。

由于哈希函数的有限性,不同的元素可能会映射到相同的位置上,这种情况下即使元素不在布隆过滤器中可能产生误判,即布隆过滤器判断元素在集合中。

如何降低误判率

增加Bit array空间,减少哈希冲突,优化散列函数,使用更多的散列函数。 

如何使用布隆过滤器

将要查询的元素通过N个散列函数提前全部映射到Bit array中,比如:查询服务信息,需要将全部服务的id提前映射到Bit array中,当去查询元素是否在数据库存在时从布隆过滤器查询即可,如果哈希函数返回0则表示肯定不存在。

布隆过滤器的优点是:二进制数组占用空间少,插入和查询效率高效。

缺点是存在误判率,并且删除困难,因为同一个位置由于哈希冲突可能存在多个元素,删除某个元素可能删除了其它元素。

布隆过滤器的应用场景

1、海量数据去重,比如URL去重,搜索引擎爬虫抓取网页,使用布隆过滤器可以快速判定一个URL是否已经被爬取过,避免重复爬取。

2、垃圾邮件过滤:使用布隆过滤器可以用于快速判断一个邮件地址是否是垃圾邮件发送者,对于海量的邮件地址,布隆过滤器可以提供高效的判定。

3、安全领域:在网络安全中,布隆过滤器可以用于检查一个输入值是否在黑名单中,用于快速拦截一些潜在的恶意请求。

4、避免缓存穿透:通过布隆过滤器判断是否不存在,如果不存在则直接返回。

如何使用布隆过滤器

使用redit的bitmap位图结构实现。

使用redisson实现。

使用google的Guava库实现。

第一步:引入依赖 

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.2-jre</version>
</dependency>
第二步,编写测试代码
public class BloomFilterExample {
    public static void main(String[] args) {
        // 创建一个布隆过滤器,预期元素数量为1000,误判率为0.01
        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000, 0.01);

        // 添加元素到布隆过滤器
        bloomFilter.put("example1");
        bloomFilter.put("example2");
        bloomFilter.put("example3");

        // 测试元素是否在布隆过滤器中
        System.out.println(bloomFilter.mightContain("example1")); // true
        System.out.println(bloomFilter.mightContain("example4")); // false
    }
}

在上述代码中,创建了一个预期包含1000个元素、误判率为0.01的布隆过滤器。然后,向布隆过滤器中添加了三个元素("example1"、"example2" 和 "example3"),并测试了几个元素是否在布隆过滤器中。

请注意,误判率是你可以调整的一个参数。较低的误判率通常需要更多的空间和计算资源。

 二:缓存击穿

缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。

比如某手机新品发布,当缓存失效时有大量并发到来导致同时去访问数据库。

解决方案 

1.加锁

单体架构下(单进程内)可以使用同步锁控制查询数据库的代码,只允许有一个线程去查询数据库,查询得到数据库存入缓存。

synchronized(obj){
  //查询数据库
  //存入缓存
}

分布式架构下(多个进程之间)可以使用分布式锁进行控制 

// 获取分布式锁对象
RLock lock = redisson.getLock("myLock");
try {
    // 尝试加锁,最多等待100秒,加锁后自动解锁时间为30秒
    boolean isLocked = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);
    if (isLocked) {
          //查询数据库
          //存入缓存
    } else {
        System.out.println("获取锁失败,可能有其他线程持有锁");
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    // 释放锁
    lock.unlock();
    System.out.println("释放锁...");
}

2.合理设置热点数据过期时间 

可以由后台程序提前将热点数据加入缓存,缓存过期时间不过期,由后台程序做好缓存同步。

例如:当服务上架后将服务信息缓存到redis且永不过期,此时需要使用put注解。

 3.缓存预热

分为提前预热、定时预热。

提前预热就是提前写入缓存。

定时预热是使用定时程序去更新缓存。

4.限流和熔断

对热点数据查询定义单独的接口,当缓存中不存在时走降级方法避免查询数据库。

三:缓存雪崩 

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。

比如对某信息设置缓存过期时间为30分钟,在大量请求同时查询该类信息时,此时就会有大量的同类信息存在相同的过期时间,一旦失效将同时失效,造成雪崩问题。

解决方案 

1.合理设置同一时期的key的过期时间

通常对一类信息的key设置的过期时间是相同的,这里可以在原有固定时间的基础上加上一个随机时间使它们的过期时间都不相同。

2.缓存定时预热

不用等到请求到来再去查询数据库存入缓存,可以提前将数据存入缓存。使用缓存预热机制通常有专门的后台程序去将数据库的数据同步到缓存。 

 3.加锁

此时加锁的策略如同缓存击穿一般

4.使用降级或者限流策略 

四:缓存不一致 

缓存不一致问题是指当发生数据变更后该数据在数据库和缓存中是不一致的,此时查询缓存得到的并不是与数据库一致的数据。

缓存不一致有什么后果 

比如:查看商品信息的价格与真实价格不一致,影响用户体验,如果直接使用缓存中的价格去计算订单金额更会导致计算结果错误。

造成缓存不一致的原因可能是在写数据库和写缓存两步存在异常,也可能是并发所导致。

写数据库和写缓存导致不一致称为双写不一致,比如:先更新数据库成功了,更新缓存时失败了,最终导致不一致。

并发缓存不一致案例

 

线程1先写入数据库X,当去写入缓存X时网络卡顿

线程2先写入数据库Y

线程2再写入缓存Y

线程1 写入缓存旧值X覆盖了新值Y

即使先写入缓存再写数据在并发环境也可能存在问题

线程1先写入缓存X,当去写入数据库X时网络卡顿

线程2先写入缓存Y

线程2再写入数据库Y

线程1 写入数据库旧值X覆盖了新值Y

解决方案 

1.使用分布式锁

线程1申请分布式锁,拿到锁。此时其它线程无法获取同一把锁。

线程1写数据库,写缓存,操作完成释放锁。

线程2申请分布锁成功,写数据库,写缓存。

对双写的操作每个线程顺序执行。

对操作异常问题仍需要解决:写数据库成功写缓存失败了,数据库需要回滚,此时就需要使用分布式事务组件。

使用分布式锁解决双写一致性不仅性能低下,复杂度增加。

2. 延迟双删

既然双写操作存在不一致,我们把写缓存改为删除缓存呢?

先写数据库再删除缓存,如果删除缓存失败了缓存也就不一致了,那我们改为:先删除缓存再写数据库

 执行流程:

线程1删除缓存

线程2读缓存发现没有数据此时查询数据库拿到旧数据写入缓存

线程1写入数据库

即使线程1删除缓存、写数据库操作后线程2再去查询缓存也可能存在问题,

执行流程:

线程1向主数据库写,线程2向从数据库查询,流程如下:

线程1删除缓存

线程1向主数据库写,数据向从数据库同步

线程2查询缓存没有数据,查询从数据库,得到旧数据

线程2将旧数据写入缓存

线程1先删除缓存,再写入主数据库,延迟一定时间再删除缓存。

延迟主数据向从数据库同步的时间间隔,如果延迟时间设置不合理也会导致数据不一致。

3.异步同步 

延迟双删的目的也是为了保证最终一致性,即允许缓存短暂不一致,最终保证一致性。

保证最终一致性的方案有很多,比如:通过MQ、Canal、定时任务都可以实现。

Canal是一个数据同步工具,读取MySQL的binlog日志拿到更新的数据,再通过MQ发送给异步同步程序,最终由异步同步程序写到redis。此方案适用于对数据实时性有一定要求的场景。

 

执行流程:

线程1写数据库

canal读取binlog日志,将数据变化日志写入mq

同步程序监听mq接收到数据变化的消息

同步程序解析消息内容写入redis,写入redis成功正常消费完成,消息从mq删除。

4.定时任务 

专门启动一个数据同步任务定时读取数据同步到redis,此方式适用于对数据实时性要求不强更新不频繁的数据。

 

执行流程:

线程1写入数据库(业务数据表,变化日志表)

同步程序读取数据库(变化日志表),根据变化日志内容写入redis,同步完成删除变化日志。

 总结:

在实际的开发项目中,可以根据具体的业务场景选择合适的缓存解决方案,以便满足高并发的需求和缓存安全的问题。

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

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

相关文章

QT 建立tcp服务端 TcpServer TcpSocket

基于正点原子教程&#xff0c;个人改编一点点(先写着&#xff0c;还没学客户端来验证) QTcpServer 服务端&#xff0c;下控制多个socket QTcpSocket 可以理解为一个TCP连接 使用客户端的流程为 1.包含network和include 2.声明QTcpServer信号 整个流程都要使用QTcpServer对象&a…

搭建pxe网络安装环境实现服务器自动部署

1.首先配置自动化脚本工具 根据自己的主机设置自己的IP 这张图选择红框下面的选则剩余空间 红帽7的初始网卡为ens33&#xff0c;所以部署后新机器的网卡为ens33 根据自己所要部署的版本编写合适的脚本 使用vim.ks.cfg进入脚本编写 2.配置DHCP 使用vim编写/etc/dhcp/dhcpd.conf …

深入研究Java的String常量池

文章目录 一、StringTable分析一段代码示例一示例二示例三 二、 intern1、StringTable位置2、StringTable 性能调优3、intern深入分析3.1 思考3.2 JDK6中的解释3.3 JDK7中的解释3.4 详细分析3.5 intern正确使用的例子3.6 intern使用不当的例子 一、StringTable 常量池中的字符…

清华计算几何-ElementUniqueness, MinMap, MaxGap

ElementUniqueness问题(EU) 给出一组数给出一组数据,, 判断每个数都是唯一性的或者说判断是否存在重复的. 算法思路很简单, 快速排序 遍历判断: Max(O(nlogn) O(n)) O(nlogn)算法复杂度 代码实现 bool IsEelementUniqueness(const vector<float>& Elemnts) {vect…

【数学建模】 机器学习与统计模型

文章目录 机器学习与统计模型1. 统计分布与假设检验1.1 统计量与常见统计分布常见统计分布Python代码示例 1.2 正态性检验Shapiro-Wilk检验Python代码示例 1.3 独立性检验卡方检验Python代码示例 1.4 两组样本的差异性检验独立样本t检验Python代码示例 1.5 方差分析与事后多重比…

基于Django+MySQL球馆场地预约系统的设计与实现(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍&#xff1a;我是程序员阿龙&#xff…

学习STM32(1)--Keil软件安装与基本操作和Keil 软件高级应用

目录 1 引 言 2 实验目的 3 实验内容 3.1 认识单片机和STM32 3.2 安装、认识软件Keil和硬件STM32F103开发板 3.3 学习调试工程 3.4 Keil工程软件的配置 4 深入解析 思考一 1.以项目“12-GPIO输出—使用固件库点亮LED”为例子&#xff0c;认识本地工程文件夹&#xf…

2024下《信息安全工程师》案例简答题,刷这些就够了!

距离2024下半年软考已经越来越近了&#xff0c;不知道今年备考软考信息安全工程师的同学们开始准备了吗&#xff1f; 简答题一直是信安拿分的重点区域&#xff0c;对于许多考生来说&#xff0c;也往往是最具挑战性的部分。今天我就把那些重要的案例简答题类型整理汇总给大家&am…

招聘网站的头像如何上传?

1、某招聘网站 显示的是圆形的&#xff0c;但是上传却需要正方形的300300像素。 2、某某招聘网站 仅支持jpg格式&#xff0c;不支持png 3、某某招聘网站 支持1mb以内图片&#xff0c;格式gif,jpg,png都支持。比较友好 4、某招聘网站 这里仅支持200200像素&#xff0c;图片格…

贪心系列专题篇四

​​​​​​​ 目录 整数替换 解法一 解法二 俄罗斯套娃信封问题 堆箱子 可被三整除的最大和 距离相等的条形码 重构字符串 声明&#xff1a;接下来主要使用贪心法来解决问题&#xff01;&#xff01;&#xff01; 整数替换 题目 思路 下面将使用两种方法来解决这…

【论文阅读】—RTDETR

《DETRs Beat YOLOs on Real-time Object Detection》 最近&#xff0c;基于端到端DETR取得了显著的性能。然而&#xff0c;DETR的高计算成本限制了它们的实际应用&#xff0c;并阻碍了它们充分利用无后处理&#xff08;如非最大抑制&#xff08;NMS&#xff09;&#xff09;的…

Windows下如何像linux一样查看GPU使用情况

在linux下&#xff0c;只要使用 nvidia-smi即可看到服务器中每块卡的使用情况 但是在windows下该如何查看显卡的使用情况呢 通过网上学习发现&#xff0c;windows下有一个叫nvidia-smi.exe的程序 找到它所在的路径&#xff0c;然后在命令行中进入到这个路径&#xff0c;然后…

文件上传漏洞-HackBar使用

介绍 HackBar 是一个用于浏览器的扩展插件&#xff0c;主要用于进行网络渗透测试和安全评估。它提供了一系列方便的工具和功能&#xff0c;可以帮助用户执行各种网络攻击和测试&#xff0c;包括 XSS、SQL 注入、CSRF、路径穿越等 下载地址 可以到github上面去下载&#xff0…

傅里叶级数的C语言验证

目录 概述 1 收敛性 1.1 收敛定理 1.2 理解收敛定理 2 傅里叶级数的应用 2.1 问题描述 2.2 实现方法 3 方波函数的傅里叶验证&#xff08;C语言实现&#xff09; 3.1 方波函数 3.1.1 编写方波函数 3.1.2 程序函数验证 3.2 傅里叶级数函数实现 3.2.1 编写傅里叶级…

Android 实现左侧导航栏:NavigationView是什么?NavigationView和Navigation搭配使用

目录 1&#xff09;左侧导航栏效果图 2&#xff09;NavigationView是什么&#xff1f; 3&#xff09;NavigationView和Navigation搭配使用 4&#xff09;NavigationView的其他方法 一、实现左侧导航栏 由于Android这边没有直接提供左侧导航栏的控件&#xff0c;所以我尝试了…

Frida Hook String构造函数

前言 在实际Android应用开发中&#xff0c;无论是使用多么复杂的算法对字符串进行加密&#xff0c;然而开发者常常会构造出字符串的实例。因此&#xff0c;我们可以通过使用Frida hook String类的构造函数来追踪这些实例的构造位置&#xff0c;然后可以通过构造实例的地方栈回…

Java GUI制作双人对打游戏源码以及应用程序分享

文章目录 前言一、可执行程序二、源码以及图片资源总结 前言 在之前的文章展示了如何构建一款双人对打游戏&#xff0c;博文链接如下&#xff1a; https://blog.csdn.net/qq_43646281/article/details/137748943?spm1001.2014.3001.5501 感兴趣的小伙伴可以再去回顾一下: 原文…

一分钟了解请求转发与重定向

目录 请求转发 重定向 请求转发 为了更好的了解请求转发&#xff0c;我们可以画出关系图&#xff1a; 请求转发的特征: 地址栏的地址不发生改变, 是请求的资源的url请求转发中, 是一次请求request域有效请求转发是在服务器端转发的, 请求转发资源只能是服务器内部资源 重定…

Pointnet++改进即插即用系列:全网首发FMB自调制特征聚合|即插即用,提升特征提取模块性能

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入FMB,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤三 1.理论介…

DS18B20数字温度传感器操作解析

文章目录 引言特点工作原理引脚说明配置寄存器温度寄存器时序初始化时序写时序读时序 引言 DS18B20 是一种广泛使用的数字温度传感器&#xff0c;具有高精度和易用性。是Dallas Semiconductor公司&#xff08;现为Maxim Integrated公司&#xff09;生产的单总线数字温度传感器…