5 Redis缓存穿透、击穿、雪崩、分布式锁、布隆过滤器

1 Redis 应用问题解决

1.1 缓存穿透

1.1.1 问题描述

key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源(数据库),从而可能压垮数据源。比如

用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

缓存穿透发生的条件:

  • 应用服务器压力变大
  • redis 命中率降低
  • 一直查询数据库,使得数据库压力太大而压垮

其实 redis 在这个过程中一直平稳运行,崩溃的是我们的数据库(如 MySQL)

在这里插入图片描述

1.1.2 解决方案

  • 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短。
  • 设置可访问的名单(白名单):使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。
  • 采用布隆过滤器:布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量 (位图) 和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
  • 进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

1.2 缓存击穿

1.2.1 问题描述

key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来访问当前key,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。属于热点key失效问题

在这里插入图片描述

1.2.2 解决方案

1、设置热点数据永不过期

对于某个需要频繁获取的信息,缓存在Redis中,并设置其永不过期。当然这种方式比较粗暴,对于某些业务场景是不适合的。

2、定时更新

比如这个热点数据的过期时间是1h,那么每到59minutes时,通过定时任务去更新这个热点key,并重新设置其过期时间。

3、互斥锁

这是解决缓存穿透比较常用的方法。

互斥锁简单来说就是在Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试。

1.3 缓存雪崩

1.3.1 问题描述

Redis中缓存的数据大面积同时失效,或者Redis宕机,从而会导致大量请求直接到数据库,压垮数据库。缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存,前者则是某一个 key 正常访问。

在这里插入图片描述

缓存失效瞬间:

在这里插入图片描述

1.3.2 解决方案

1、设置有效期均匀分布:避免缓存设置相近的有效期,我们可以在设置有效期时增加随机值;或者统一规划有效期,使得过期时间均匀分布。

2、数据预热:对于即将来临的大量请求,我们可以提前走一遍系统,将数据提前缓存在Redis中,并设置不同的过期时间。

3、构建多级缓存架构:nginx 缓存 + redis 缓存 + 其他缓存(ehcache 等)

4、使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,该方法不适用高并发情况。

1.4 分布式锁

分布式锁本质上要实现的目标就是在 Redis 里面占一个“坑位”,当别的进程也要来占 时,发现已经有进程占用了,就只好放弃或者稍后再试。 占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占,用 完了,再调用 del 指令释放。

1.4.1 setnxdel命令实现

127.0.0.1:6379> setnx lock true # 获取锁
(integer) 1
# ... do biz ...
# ... do biz ...
127.0.0.1:6379> del lock # 释放锁
(integer) 1
127.0.0.1:6379>

但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样 就会陷入死锁,锁永远得不到释放。

于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也 可以保证 5 秒之后锁会自动释放。

1.4.2 setnxexpiredel命令实现

127.0.0.1:6379> setnx lock true
(integer) 1
127.0.0.1:6379> expire lock 5
(integer) 1
# ... do biz ...
# ... do biz ...
127.0.0.1:6379> del lock
(integer) 0

但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,或者客户端由于某种原因掉线,就会导致 expire 得不到执行,也会造成死锁。

在这里插入图片描述

这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。也许会想到用 Redis 事务来解决。但是这里不行,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务的特点是一口气执行,没有 ifelse 分支逻辑,要么全部执行要么一个都不执行。

为了解决这个疑难,Redis 开源社区涌现了一堆分布式锁的 library,专门用来解决这个问题,实现方法极为复杂。

1.4.3 set 扩展命令参数实现

幸运的是在redis 2.6.12 及之后版本set命令进行了扩展,使得可以原子性的实现 setnx + expire 功能:

SET key value [NX | XX] [GET] [EX seconds | PX milliseconds |
  EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
127.0.0.1:6379> set lock true ex 5 nx
OK
127.0.0.1:6379> set lock true ex 5 nx
(nil)

经过上三步的推到,分布式锁依然存在一下问题:

1.4.4 超时问题

这种方式的Redis 的分布式锁不能解决超时问题,A线程超时时间设为10s(为了解决死锁问题), 但代码执行时间可能需要30s, 然后redis服务端10s后将锁删除, 此时, B线程恰好申请锁, redis服务端不存在该锁, 可以申请, 也执行了代码, 那么问题来了, A、B线程都同时获取到锁并执行业务逻辑, 这与分布式锁最基本的性质相违背: 在任意一个时刻, 只有一个客户端持有锁, 即独享,还会引发A线程把B线程的锁释放了,B把C释放了依次递推,造成分布式锁彻底失效

为了避免这个问题:

方案一:Redis 分布式锁不要用于较长时间的任务

方案二:异步定时进行lock锁的续期,实现比较复杂(例如:redisson中监控锁的看门狗)

1.4.5 可重入性

可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。比如 Java 语言里有个 ReentrantLock 就是可重入锁。

Redis 分布式锁如果要支持可重入,需要对客户端的 set 方法进行包装,使用类似线程的Threadlocal变量存储当前持有锁的计数。对于redis的重入锁业界还是有很多解决方案的,目前最流行的就是采用Redisson

不推荐使用可重入锁,它加重了客户端的复杂性,在编写业务方法时注意在逻辑结构上进行调整完全可以不使用可重入锁

1.4.6 集群环境下的分布式锁

比如加锁操作在 Sentinel 集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当 另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生。

在这里插入图片描述

不过这种不安全也仅仅是在主从发生 failover 的情况下才会产生,而且持续时间极短, 业务系统多数情况下可以容忍。

1.4.7 Redlock 算法

Redlock 是一种算法,Redlock 也就是 Redis Distributed Lock,它的流程比较复杂,解决集群环境下的分布式锁的问题,可用实现多节点 redis 的分布式锁。

RedLock 官方推荐,Redisson 完成了对 Redlock 算法封装。

此种方式具有以下特性:

  • 互斥访问:即永远只有一个 client 能拿到锁。
  • 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使锁定资源的服务崩溃或者分区,仍然能释放锁。
  • 容错性:只要大部分 Redis 节点存活(一半以上),就可以正常提供服务

加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试时钟漂移等很多细节问题,同时因为 Redlock 需要向多个节点进行读、写,意味着相比单实例 Redis 性能会下降一些。

1.5 布隆过滤器

1.5.1 什么是 BloomFilter

在这里插入图片描述

布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

当布隆过滤器表示某个值存在时,该值可能不存在;当它说一个值不存在时,它就一定不存在。

通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O ( n ) O(n) O(n) O ( l o g n ) O(logn) O(logn) O ( 1 ) O(1) O(1)

这个时候,布隆过滤器(Bloom Filter)就应运而生。

1.5.2 工作原理与设计思想

1.5.2.1 Hash 函数原理

了解布隆过滤器原理之前,先回顾下 Hash 函数原理

哈希函数的概念是:将任意大小的输入数据转换成特定大小的输出数据的函数,转换后的数据称为哈希值哈希编码,也叫散列值。下面是一幅示意图:

在这里插入图片描述

所有散列函数都有如下基本特性:

  • 单向散列函数:如果两个散列值不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。这个特性是散列函数具有确定性的结果,具有这种性质的散列函数称为单向散列函数。
  • 散列碰撞:如果两个散列值相同,两个原始输入值很大可能是相同的,但也可能不同

1.5.2.2 布隆过滤器数据结构

BloomFilter 是由一个固定大小的二进制向量或者位图(bitmap)和一系列映射函数组成的。在初始状态时,对于长度为 m 的位数组,它的所有位都被置为0,如下图所示:

在这里插入图片描述

当有变量被加入集合时,通过 K 个映射函数将这个变量映射成位图中的 K 个点,把它们置为 1(假定有两个变量都通过 3 个映射函数)。

变量:obj1、obj2;函数:Fun1、Fun2、Fun3

在这里插入图片描述

查询某个变量的时候我们只要看看这些点是不是都是 1 就可以大概率知道集合中有没有它了

  • 如果这些点有任何一个 0,则被查询变量一定不在
  • 如果都是 1,则被查询变量很可能存在

为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,会产生散列碰撞

1.5.2.3 布隆过滤器误判率和删除问题

布隆过滤器的误判是指多个输入经过K个哈希之后在相同的bit位,置为1,这样就无法判断究竟是哪个输入产生的,因此误判的根源在于相同的 bit 位被多次映射且置 1。

这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。如果我们直接删除这一位的话,会影响其他的元素,导致误判率增加。例如上图中 3 位置的判断两个值obj1、obj2都映射在一个位置了。

1.5.2.4 添加与查询元素步骤

添加元素

  1. 将要添加的元素给 k 个哈希函数进行计算
  2. 得到对应于位数组上的 k 个位置
  3. 将这k个位置设为 1

查询元素

  1. 将要查询的元素给k个哈希函数进行计算
  2. 得到对应于位数组上的k个位置
  3. 如果k个位置有一个为 0,则肯定不在集合中
  4. 如果k个位置全部为 1,则可能在集合中

1.5.3 布隆过滤器优缺点

1.5.3.1 优点

  • 相比于其它的数据结构,布隆过滤器在空间时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数 O(K),另外,散列函数相互之间没有关系,方便由硬件并行实现。
  • 布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
  • 布隆过滤器可以表示全集,其它任何数据结构都不能。

1.5.3.2 缺点

  • 布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

  • 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。

1.5.4 布隆过滤器使用场景

布隆过滤器的典型应用有:

  1. 黑名单:垃圾邮件过滤功能,从数十亿个垃圾邮件列表(类似地,垃圾邮件)中判断电子邮件是否为垃圾邮件。

  2. 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。

  3. 缓存穿透,将已经存在的缓存放在布隆中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉

    在这里插入图片描述

  4. 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。

  5. WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。

    • Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。
    • Google Chrome浏览器使用了布隆过滤器加速安全浏览服务

1.5.5 Redis中使用布隆过滤器

布隆过滤器(Bloom Filter)是Redis 4.0提供的新功能。它作为插件加载到Redis服务器中。

安装 RedisBloom

# 下载地址
https://github.com/RedisBloom/RedisBloom
# 解压文件
unzip RedisBloom-master.zip
# 进入目录
cd RedisBloom-master
# 执行编译命令,生成redisbloom.so 文件
make
# 拷贝至指定目录
cp redisbloom.so /path/to/redisbloom.so
# 在redis配置文件里加入以下配置
loadmodule /path/to/redisbloom.so
# 配置完成后重启redis服务
sudo redis-server restart
# 测试是否安装成功
127.0.0.1:6379> bf.add user 101
(integer) 1
127.0.0.1:6379> bf.exists user 101
(integer) 1
127.0.0.1:6379> bf.exists user 10
(integer) 0

常用命令

  • bf.add:添加元素到布隆过滤器。
  • bf.exists:判断某个元素是否在于布隆过滤器中。
  • bf.madd:同时添加多个元素到布隆过滤器。
  • bf.mexists:同时判断多个元素是否存在于布隆过滤器中。
  • bf.reserve:以自定义的方式设置布隆过滤器参数值,共有 3 个参数分别是 key、error_rate(错误率)、initial_size(初始大小)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一份标准的软件测试方案模板

第一章 概述 ​ 软件的错误是不可避免的,所以必须经过严格的测试。通过对本软件的测试,尽可能的发现软件中的错误,借以减少系统内部各模块的逻辑,功能上的缺陷和错误,保证每个单元能正确地实现其预期的功能。检测和排…

亚马逊云科技开启您的云财务管理之旅:云财务运营

亚马逊云科技“开启您的云财务管理之旅”系列内容提出了关于如何启动和实施一个成功的云财务管理CFM战略的建议。云财务管理CFM的三个原则:SEE-查看、SAVE-节省和PLAN-计划。接下来介绍的是第四个阶段:RUN-运营。 在这一阶段,可以了解云财务管…

vue 做一个文本展示 点击文本弹出element ui的时间选择器 但不会出现element ui时间组件的那个输入框

我们先来创建一个vue2项目 引入element ui 然后 找到一个组件 这样写 <template><div><el-date-pickerv-model"value"type"datetimerange"align"right"unlink-panelsrange-separator"至"start-placeholder"开始日…

C/C++的命名空间和调用函数的详细讲解

目录 空函数 调用函数 调用 执行流程 命名空间 在创建函数时&#xff0c;必须编写其定义。所有函数定义包括以下组成部分&#xff1a; 名称&#xff1a;每个函数都必须有一个名称。通常&#xff0c;适用于变量名称的规则同样也适用于函数名称。形参列表&#xff1a;调用函…

手机摄影笔记(二)

第5章 镜头语言 镜头语言分类&#xff08;8个&#xff09;&#xff1a; 推&#xff1a;从远到近 拉&#xff1a;从近到远 摇&#xff1a;机位固定&#xff0c;旋转手机拍全景或者跟着拍摄对象进行摇摄&#xff08;跟摇&#xff09;.通常用此方式来介绍环境时&#xff0c;表现的…

开放原子训练营(第三季)inBuilder低代码开发实验室---报销单录入系统

作为一名低代码初学者&#xff0c;我使用inBuilder系统设计了一款报销单录入系统&#xff0c;实现了报销单录入与显示报销单列表的功能&#xff08;如图1与图2所示&#xff09;&#xff0c;并获得了很多开发心得。从inBuilder系统的优点、缺点以及开发过程三方面出发&#xff0…

基于SpringBoot,vue的家政服务平台的设计与实现

背景 以往的家政服务管理平台的管理&#xff0c;一般都是纸质文件来管理家政服务信息&#xff0c;传统的管理方式已经无法满足现代人们的需求&#xff1b;使用家政服务管理平台, 首先可以大幅提高家政服务信息检索&#xff0c;只需输入家政服务相关信息就能在数秒内反馈想要的…

JavaScript学习(一)

一、JavaScript的背景及知识结构 1、三个问题 什么是JavaScript&#xff1f;JavaScript能干什么&#xff1f;JavaScript是由什么构成的&#xff1f;怎样学习JavaScript&#xff1f; 2、什么是JavaScript&#xff1f; ①JavaScript是一种轻量级的编程语言&#xff1b;借鉴了J…

【SSA-LSTM】基于麻雀算法优化LSTM 模型预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

C#_Struct与Class的差异

简述 struct属于值类型&#xff0c;class属于引用类型 存储地址 struct储存于栈&#xff0c;class储存于堆&#xff08;class于栈中储存引用&#xff09; 传参性质 struct属于值传递&#xff0c;在函数内对参数进行修改&#xff0c;不会修改struct class处于引用传递&…

行为型模式-状态模式

状态模式 概述 【例】通过按钮来控制一个电梯的状态&#xff0c;一个电梯有开门状态&#xff0c;关门状态&#xff0c;停止状态&#xff0c;运行状态。每一种状态改变&#xff0c;都有可能要根据其他状态来更新处理。例如&#xff0c;如果电梯门现在处于运行时状态&#xff0…

MySQL

关系型数据库 数据结构&#xff1a;二维表格 库 -> 表 -> 列&#xff08;字段&#xff09;&#xff1a;用来描述对象的一个属性 -> 行&#xff08;记录&#xff09;&#xff1a;用来描述一个对象的信息 市面上&#xff1a;MySQL 、Mariadb 、PostgreSQL 、 Oracle&a…

汽车电路图、原理框图、线束图、元器件布置图的识读技巧与要点

摘要&#xff1a; 想要读懂汽车电路图就必须把电的通路理清楚&#xff0c;即某条线是什么信号&#xff0c;该信号是输入信号、输出信号还是控制信号以及信号起什么作用&#xff0c;在什么条件下有信号&#xff0c;从哪里来&#xff0c;到哪里去。 一、汽车电路图的识读技巧 1.…

在 Python 中将 Tqdm 与 Asyncio 结合使用

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 困扰 在 Python 中使用并发编程来提高效率对于数据科学家来说并不罕见。在后台观察各种子进程或并发线程以保持我的计算或 IO 绑定任务的顺序总是令人满意的。 但是还有一点困扰我的是&#xff0c;当我在后台并发处理成百…

PBDB Data Service:Thumbnail images of lifeforms(生命形式的缩略图)

Thumbnail images of lifeforms&#xff08;生命形式的缩略图&#xff09; 描述用法参数方法响应值格式术语表 描述 此操作返回表示指定分类的图像&#xff0c;或关于图像的信息。如果后缀是 .png&#xff0c;则返回图像内容数据。否则&#xff0c;将以指定的格式返回一个描述…

9:00进去,9:05就出来了,这问的也太···

从外包出来&#xff0c;没想到死在另一家厂子了。 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内推…

【Halcon】新建程序 读取图片 路径设置

文章目录 1 新建程序2 读取一张图片3 图片路径4 图片格式读取报错5 快速添加 绝对路径1 新建程序 点击新程序图标,即可新建; 程序另存为,会弹出保存路径 2 读取一张图片 read_image(Image,fabrik)

软件测试工程师的核心竞争力究竟是什么?

对于测试员而言&#xff0c;了解自己岗位的核心竞争力是非常重要的。在职业初期&#xff0c;许多人认为掌握代码才是软件测试的核心竞争力&#xff0c;但是随着经验的增加&#xff0c;我们会发现真正的核心竞争力是由多个方面组成的。 首先&#xff0c;测试人员需要具备良好的测…

Windows环境安装Elasticsearch和Kibana

文章目录 1 Elasticsearch1.1 下载1.2 解压并添加环境变量1.3 访问1.4 cmd命令1.5 中文分词器1.5.1 下载1.5.2 安装1.5.2.1 命令安装1.5.2.2 手动安装1.5.2.3 验证分词 1.6 使用curl批量导入 2 安装 kibana2.1 下载kibana2.2 中文界面2.3 操作索引2.3.1 增加索引2.3.1.1 单条新…

Apache Doris学习记录

1. Doris基础学习 中文官网:https://doris.apache.org/zh-CN/docs/dev/summary/basic-summary/ 1.1 doris 简介 Apache Doris 是一个现代化的 MPP(Massively Parallel Processing&#xff0c;即大规模并行处理) 分析型数据库产品 亚秒级响应时间即可获得查询结果 可以支持 10PB…