Redis学习 - 了解Redis(三)

1. 什么是缓存击穿、缓存穿透、缓存雪崩?

1.1 缓存穿透问题

先来看一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。

缓存穿透:是指查询一个不存在的数据,由于 Redis 中没有该数据,因此会直接查询数据库,导致数据库压力增大,甚至可能引发恶意攻击。

缓存穿透一般都是这几种情况产生的:

  •     业务不合理的设计,比如大多数用户都没开守护,但是你的每个请求都去缓存,查询某个userid查询有没有守护。
  •     业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。
  •     黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。

如何避免缓存穿透呢? 一般有二种方法。

方法一:缓存空数据

 查询返回的数据为空,仍把这个空结果进行缓存;比如一个get请求:gugu/shop/getById/1,可以将{key:1,value:null}存入redis中。

如果查询数据库为空,我们可以给redis缓存设置个空值,或者默认值,当查询一个不存在的数据时,Redis 中有该键,但是该键对应的值是一个空对象,因此不会查询数据库。        注意:有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)。

优点:实现简单。
缺点:①如果有大量查询的数据都不存在,则redis中会缓存大量空数据,这会消耗内存(这里可以给缓存添加一个TTL,减少内存消耗);②如果原先查的数据不存在,但是后来数据库中又添加上了,可能存在数据不一致的问题。

方法二:布隆过滤器 

    使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。

优点:内存占用较少,没有多余key。
缺点:①实现复杂;②存在误判。

1.3 小结

缓存穿透的解决方案有哪些?
①缓存nul值
②布隆过滤
【额外补充几点】
③增强id的复杂度,避免被猜测id规律
④做好数据的基础格式校验
⑤加强用户权限校验
⑥做好热点参数的限流

1.2 缓存雪奔问题

缓存雪奔: 是指在同一时段大量的缓存key过期或者Redis服务宕机,导致大量请求都直接访问数据库,引起数据库压力过大甚至down机。

方法一:大量的缓存key失效

不同的Key的TTL添加随机值,比如将缓存失效时间分散开,可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

方法二:Redis服务宕机

①利用Redis集群提高服务的可用性,比如哨兵模式、集群模式;
②给缓存业务添加降级限流策略,比如可以在ngxin或spring cloud gateway中处理;(注:降级可做为系统的保底策略,适用于穿透、击穿、雪崩)
③给业务添加多级缓存,比如使用Guava或Caffeine作为一级缓存,redis作为二级缓存等;

 

1.3 缓存击穿问题

缓存击穿: 给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。
缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

方法一: 互斥锁

    使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。

 

 

方法二: 逻辑过期

①在设置key的时候,设置一个过期时间字段,一起存入缓存中,注意不给当前key设置TTL过期时间;
在这里插入图片描述
②当查询的时候,从redis取出数据后判断时间是否过期;
③如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,但这个数据不是最新的

“永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。


2.Redis缓存之双写一致性

redis做为缓存,mysql的数据如何与redis进行同步呢?(本质上问的就是双写一致性)


2.1 双写一致性定义

双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
在这里插入图片描述


读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间;
写操作:延迟双删


2.2 问题分析

这里就会产生一个问题,上图步骤①②可以调换顺序吗?即先删除缓存,还是先修改数据库?

解答:不管先删除缓存,还是先修改数据库,都会产生脏数据,所以需要删除两次缓存;由于数据库主从同步的问题,还需要延时删除
但是延迟双删这个延迟时间不好判断;而且在延时的过程中可能会出现脏数据,并不能保证强一致性;



2.3 解决方案

2.3.1 针对一致性要求高

使用分布式锁

  • 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作;
  • 排他锁:也叫独占锁writeLock,加锁之后,阻塞其他线程读写操作;

读数据的时候,添加共享锁,写数据的时候,添加排他锁

虽然使用读写锁,可以保证强一致性,但性能会降低。

2.3.2 针对允许延迟一致

基于MQ的异步通知:使用MQ中间件,更新数据之后,通知缓存删除;

 ②基于Canal的异步通知:利用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓存;

 备注:二进制日志(BINLOG)记录了所有的DDL(数据定义语言)语句和DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句;

3.说说Redis的常用应用场景

  • 缓存
  • 排行榜
  • 计数器应用
  • 共享Session
  • 分布式锁
  • 社交网络
  • 消息队列
  • 位操作

2.1 缓存

我们一提到redis,自然而然就想到缓存,国内外中大型的网站都离不开缓存。合理的利用缓存,比如缓存热点数据,不仅可以提升网站的访问速度,还可以降低数据库DB的压力。并且,Redis相比于memcached,还提供了丰富的数据结构,并且提供RDB和AOF等持久化机制,强的一批。

2.2 排行榜

当今互联网应用,有各种各样的排行榜,如电商网站的月度销量排行榜、社交APP的礼物排行榜、小程序的投票排行榜等等。Redis提供的zset数据类型能够实现这些复杂的排行榜。

比如,用户每天上传视频,获得点赞的排行榜可以这样设计:

    1.用户Jay上传一个视频,获得6个赞,可以酱紫:

zadd user:ranking:2021-03-03 Jay 3

    2.过了一段时间,再获得一个赞,可以这样:

zincrby user:ranking:2021-03-03 Jay 1

    3.如果某个用户John作弊,需要删除该用户:

zrem user:ranking:2021-03-03 John

    4.展示获取赞数最多的3个用户

zrevrangebyrank user:ranking:2021-03-03 0 2

2.3 计数器应用

各大网站、APP应用经常需要计数器的功能,如短视频的播放数、电商网站的浏览数。这些播放数、浏览数一般要求实时的,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择。

2.4 共享Session

如果一个分布式Web服务将用户的Session信息保存在各自服务器,用户刷新一次可能就需要重新登录了,这样显然有问题。实际上,可以使用Redis将用户的Session进行集中管理,每次用户更新或者查询登录信息都直接从Redis中集中获取。

2.5 分布式锁

几乎每个互联网公司中都使用了分布式部署,分布式服务下,就会遇到对同一个资源的并发访问的技术难题,如秒杀、下单减库存等场景。

    用synchronize或者reentrantlock本地锁肯定是不行的。
    如果是并发量不大话,使用数据库的悲观锁、乐观锁来实现没啥问题。
    但是在并发量高的场合中,利用数据库锁来控制资源的并发访问,会影响数据库的性能。
    实际上,可以用Redis的setnx来实现分布式的锁。

使用过Redis分布式锁嘛?有哪些注意点呢?

分布式锁,是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁,我们项目中经常使用Redis作为分布式锁。

选了Redis分布式锁的几种实现方法,大家来讨论下,看有没有啥问题哈。

  •     命令setnx + expire分开写
  •     setnx + value值是过期时间
  •     set的扩展命令(set ex px nx)
  •     set ex px nx + 校验唯一随机值,再删除
2.5.1 命令setnx + expire分开写

if(jedis.setnx(key,lock_value) == 1){ //加锁 expire(key,100); //设置过期时间 try { do something //业务请求 }catch(){ } finally { jedis.del(key); //释放锁 } }

如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash掉或者要重启维护了,那这个锁就“长生不老”了,别的线程永远获取不到锁啦,所以分布式锁不能这么实现。

2.5.2 setnx + value值是过期时间

long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间 String expiresStr = String.valueOf(expires); // 如果当前锁不存在,返回加锁成功 if (jedis.setnx(key, expiresStr) == 1) { return true; } // 如果锁已经存在,获取锁的过期时间 String currentValueStr = jedis.get(key); // 如果获取到的过期时间,小于系统当前时间,表示已经过期 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈) String oldValueStr = jedis.getSet(key_resource_id, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁 return true; } } //其他情况,均返回加锁失败 return false; }

笔者看过有开发小伙伴是这么实现分布式锁的,但是这种方案也有这些缺点

  •     过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步。
  •     没有保存持有者的唯一标识,可能被别的客户端释放/解锁。
  •     锁过期的时候,并发多个客户端同时请求过来,都执行了jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。

2.5.3:set的扩展命令(set ex px nx)(注意可能存在的问题)

if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务处理 }catch(){ } finally { jedis.del(key); //释放锁 } }

这个方案可能存在这样的问题:

    锁过期释放了,业务还没执行完。
    锁被别的线程误删。

2.5.4 set ex px nx + 校验唯一随机值,再删除

if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务处理 }catch(){ } finally { //判断是不是当前线程加的锁,是才释放 if (uni_request_id.equals(jedis.get(key))) { jedis.del(key); //释放锁 } } }

在这里,判断当前线程加的锁和释放锁是不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁

一般也是用lua脚本代替。lua脚本如下:

if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end;

这种方式比较不错了,一般情况下,已经可以使用这种实现方式。但是存在锁过期释放了,业务还没执行完的问题(实际上,估算个业务处理的时间,一般没啥问题了)。
 

2.6 社交网络

赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适保存 这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功能。

2.7 消息队列

消息队列是大型网站必用中间件,如ActiveMQRabbitMQKafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

2.8 位操作

用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多。这里要用到位操作——使用setbit、getbit、bitcount命令。原理是:redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统。
 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud Alibaba 整合Sentinel的基本使用

文章目录 一、什么是Sentinel二、Sentinel 的主要特性1. 流量控制&#xff1a;2. 熔断降级&#xff1a;3. 实时监控&#xff1a;4. 规则配置&#xff1a;5. 集成方便&#xff1a; 三、Sentinel 分为哪几部分:1. 核心库&#xff08;Java 客户端&#xff09;2. 控制台&#xff08…

2023 第十二届中国智能产业高峰论坛 - 文档大模型的未来展望

目录 前言文档图像分析识别与理解中的技术挑战 文档图像分析识别与理解的研究主题文档图像分析与预处理文档解析与识别版面分析与还原文档信息抽取与理解AI安全知识化&存储检索和管理 多模态大模型在文档图像处理中的应用多模态的GPT-4在文档图像上的表现多模态的Google Ba…

IntelliJ IDEA学习总结(3)—— IntelliJ IDEA 常用快捷键(带动图演示)

一、构建/编译 Ctrl + F9:构建项目 该快捷键,等同于菜单【Build】—>【Build Project】 执行该命令后,IntelliJ IDEA 会编译项目中所有类,并将编译结果输出到out目录中。IntelliJ IDEA 支持增量构建,会在上次构建的基础上,仅编译修改的类。 Ctrl + Shift + F9:重新编…

【C++心愿便利店】No.6---C++之拷贝构造函数

文章目录 一、拷贝构造函数的引入二、拷贝构造函数 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到我的乱七八糟小星球&#x1f31d; &#x1f4cb;专栏&#xff1a;C 心愿便利店 &#x1f511;本章内容&#xff1a;拷贝构造函数 记得 评…

Spring boot easyexcel 实现复合数据导出、按模块导出

场景&#xff1a; 导出数据为1对多的复合数据一个模块是一条数据&#xff0c;直接填充数据无法实现 如图&#xff1a; 红框内为一条数据(1对多)&#xff0c;下方箭头指向为第二条数据如果直接填充&#xff0c;只能填充第一条&#xff0c;第二条就没办法了。由于多行都包含许多&…

安卓玩机搞机----不用刷第三方官改固件即可享受“高级设置”的操作 ChiMi安装使用步骤

很多玩友特别喜欢第三方作者修改的带有高级设置的官改包。因为他可以随意修改系统里面的有关设置选项。包括但不限于修改状态栏 显示日期 秒等等的操作。 第三方带高级设置的官改 一般官改带高级设置的类似与 今天给大家分享下不用刷这些官改包即可享受高级设置的操作。 红米…

2023上海工博会,正运动展位现场直击(二)

9月21日&#xff0c;上海工博会已经成功开展了2天&#xff0c;热度仍旧不减&#xff0c;正运动技术展位6.1H-E261不仅吸引了大量工业自动化专业人士&#xff0c;而且也为他们呈现了一系列令人印象深刻的产品和运动控制解决方案。其中&#xff0c;高性能软硬件产品引发了广泛关注…

数据结构入门-14-排序

一、选择排序 1.1 选择排序思想 先把最小的元素拿出来 剩下的&#xff0c;再把最小的拿出来 剩下的&#xff0c;再把最小的拿出来 但是这样 空间复杂度是O(n) 优化一下&#xff0c;希望原地排序 1.1.2 选择原地排序 索引i指向0的位置 索引j指向i1的元素 j 后面的元素遍历&…

使用香橙派学习Linux udev的rules 并实现U盘的自动挂载

在之前编程首先语音刷抖音的博文里提到过udev&#xff0c;现在回顾一下&#xff1a; 什么是udev&#xff1f; udev是一个设备管理工具&#xff0c;udev以守护进程的形式运行&#xff0c;通过侦听内核发出来的uevent来管理/dev目录下的设备文件。udev在用户空间运行&#xff0c;…

macOS Sonoma 14 RC2(23A344)/Ventura13.6/Monterey 12.7 三版系统同时更新

macOS Sonoma 14 RC2&#xff08;23A344&#xff09;/macOS13.6/macOS 12.7 同时更新

冯诺伊曼体系结构和操作系统

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析3 目录 &#x1f449;&#x1f3fb;一、冯诺依曼体系结构概念常见的输入设备和输出设备内…

【数据结构】二叉树之堆的实现

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、二叉树的顺序结构 &#x1f4d2;1.1顺序存储 &#x1f4d2;1.2堆的性质…

MySQL查询表结构方法

MySQL查询数据库单个表结构代码 – 查询数据库表信息 SELECT​ COLUMN_NAME 列名,​ DATA_TYPE 字段类型,​ CHARACTER_MAXIMUM_LENGTH 长度,​ IS_NULLABLE 是否为空,​ IF(column_key PRI,Y,) 是否为主键,​ COLUMN_DEFAULT 默认值,​ COLUMN_COMMENT 备注FROM​ INFORMAT…

【数据结构】图的基本概念,图的存储结构(邻接矩阵;邻接表;十字链表;邻接多重表)

欢~迎~光~临~^_^ 目录 1、图的基本概念 2、图的存储结构 2.1邻接矩阵 2.2邻接表 2.3十字链表 2.4邻接多重表 2.5图的四种存储结构的对比 1、图的基本概念 图是由一组节点&#xff08;通常称为顶点&#xff09;和一组连接这些节点的边&#xff08;通常称为边&#xff0…

Linux中sudo命令的添加和操作

使用 sudo分配权限 &#xff08;1&#xff09;修改/etc/sudoers 文件分配文件 # chmod 740 /etc/sudoers # vi /etc/sudoers 找到这行&#xff1a;root ALL(ALL) ALL, 在这行下面添加 xxx ALL(ALL) ALL (这里的xxx就是你的普通用户&#xff0c;而ruice就是我的普通用户 ) 编…

外汇天眼:外汇交易市场与股票交易市场优势对比!

在纽约证券交易所上市的股票大约有2800多只。纳斯达克证券交易所还列出了另外3,300多家股票。您将交易哪一个&#xff1f;有时间留在这么多公司的头上吗&#xff1f;在外汇交易中&#xff0c;有数十种货币交易&#xff0c;但是大多数市场参与者交易了七种主要货币对。难道七个主…

微信开放平台第三方开发,实现代小程序备案申请

大家好&#xff0c;我是小悟 微信小程序备案整体流程总共分为五个环节&#xff1a;备案信息填写、平台初审、工信部短信核验、通管局审核和备案成功。 服务商可以代小程序发起备案申请。在申请小程序备案之前&#xff0c;需要确保小程序基本信息已填写完成、小程序至少存在一个…

如何利用播放器节省20%点播成本

点播成本节省的点其实涉及诸多部分&#xff0c;例如&#xff1a;CDN、转码、存储等&#xff0c;而利用播放器降本却是很多客户比较陌生的部分。火山引擎基于内部支撑抖音集团相关业务的实践&#xff0c;播放器恰恰是成本优化中最重要和最为依赖的部分。 火山引擎的视频团队做了…

华为云云耀云服务器L实例评测|Docker版的Minio安装 Springboot项目中的使用 结合vue进行图片的存取

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到过MySQL数据库被攻击的情况&#xff0c;Redis被攻击的情况&#xff0c;教训是密码不能太简单。在使用服务器时&#xff0c;学习到很多运维相关的知识。 本篇博客介绍如何在Linux中安装mi…

IP协议的相关特性

文章目录 一.IP协议二. IP地址不够用了?1. 动态分配IP(DHCP)2. NAT机制(网络地址转换)(理解网络结构的关键要点)3. IPv64. 为什么IPv6不如NAT受用? 二. IP组成三. 路由转发(了解) 一.IP协议 概念 IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地…