Redis学习 - 了解Redis(三)

news2024/11/24 11:25:14

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.coloradmin.cn/o/1024444.html

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

相关文章

读书笔记-《ON JAVA 中文版》-摘要25[第二十二章 枚举]

文章目录 第二十二章 枚举1. 基本功能1.1 基本 enum 特性 2. 方法添加2.1 方法添加2.2 覆盖 enum 的方法 3 switch 语句中的 enum4. values 方法的神秘之处5. 实现而非继承6. 随机选择7. 使用接口组织枚举8. 使用 EnumSet 替代 Flags9. 使用 EnumMap10. 常量特定方法11. 本章小…

pcl--第六节 3D特征描述子

特征描述子 Feature Descriptor 是每个特征点独特的身份认证同一空间点在不同视角的特征点具有高度相似的描述子不同特征点的描述子差异性尽量大通常描述子是一个具有固定长度的向量 描述子可以分为以下几种类型&#xff1a;基于不变性的描述子、基于直方图的描述子、二进制描…

上万条童话故事儿童故事ACCESS\EXCEL数据库

虽然已经有《7千多儿童故事网ACCESS\EXCEL数据库》这种记录数的童话故事类数据&#xff0c;但是遇到了好采集的就总想采集下来&#xff0c;后续有时间或有需求可以再做合并等操作。 分类情况统计为&#xff1a; 儿童故事&#xff1a;儿童小故事&#xff08;1895&#xff09;、…

Cobra眼睛蛇-强大的Golang CLI框架,快速上手的脚手架搭建项目工具,详细安装和使用

Cobra眼睛蛇-强大的Golang CLI框架&#xff0c;快速上手的脚手架搭建项目工具&#xff0c;详细安装和使用。 阅读过k8s源码的同学&#xff0c;应该都知道k8s Scheduler、kubeadm、kubelet等核心组件的命令行交互全都是通过spf13写的Cobra库来实现。本文就来介绍下Cobra的相关概…

成都爱尔李晓峰医生解析“秋季干眼症”!该如何远离

秋高气爽?相较于夏天不然湿热难忍&#xff0c;气候逐渐干燥似乎“好过”了些。但干燥不见得都是好事&#xff0c;环境空气的干同时让人体呈现“燥”&#xff0c;如皮肤干燥、喉痒、口干、干咳、便秘等不适&#xff0c;就连眼睛也不能幸免。 由于眼部水分较其他季节蒸发快&…

时序预测 | MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测

时序预测 | MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测 目录 时序预测 | MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测&#…

STM32F407 串口使用DMA方式通信

DMA的原理&#xff0c;就是利用寄存器方式进行读写&#xff0c;这样的好处就是相对于中断触发&#xff08;往往一个字节字节的就中断一次&#xff09;&#xff0c;CPU中断次数大大降少&#xff0c;提高了效率&#xff0c;但也影响了实时性。总体来说&#xff0c;对于一般的应用…

系统安装(一)CentOS 7 本地安装

CentOS与Ubuntu并称为Linux最著名的两个发行版&#xff0c;但由于笔者主要从事深度学习图像算法工作&#xff0c;Ubuntu作为谷歌和多数依赖库的亲儿子占据着最高生态位。但最近接手的一个项目里&#xff0c;甲方指定需要在CentOS7上运行项目代码&#xff0c;笔者被迫小小cos了一…

VS Code时间轴插件:MarkWhen

文章目录 简介时间格式事件格式 简介 MarkWhen是一款文本转时间轴的工具&#xff0c;非常好用&#xff0c;也十分炫酷。可在VS Code中搜索插件MarkWhen&#xff0c;点击安装&#xff0c;然后新建一个.mw后缀的文件&#xff0c;就可以使用了&#xff0c;下面举一个简单的例子 …

嵌入式笔试面试刷题(day15)

文章目录 前言一、Linux中的主设备号和次设备号1.查看方法2.主设备号和次设备号的作用 二、软件IIC和硬件IIC的区别三、变量的声明和定义区别四、static在C和C中的区别五、串口总线空闲时候的电平状态总结 前言 本篇文章继续讲解嵌入式笔试面试刷题&#xff0c;希望大家坚持跟…

PyTorch深度学习实战(17)——多任务学习

PyTorch深度学习实战&#xff08;17&#xff09;——多任务学习 0. 前言1. 多任务学习1.1 多任务学习基本概念1.2 多任务学习优势 2. 模型与数据集分析2.1 模型分析2.2 数据集介绍 3. 实现年龄估计和性别分类小结系列链接 0. 前言 多任务学习( Multi-Task Learning, MTL )是一…

SpringCloud Alibaba 整合Sentinel的基本使用

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

Matlab图像处理-区域描述

一旦一幅图像的目标区域被确定&#xff0c;我们往往用一套描述子来表示其特性。选择区域描述子的动机不单纯为了减少在区域中原始数据的数量&#xff0c;而且也应有利于区别带有不同特性的区域。因此&#xff0c;当目标区域有大小、旋转、平移等方面的变化时&#xff0c;针对这…

ThreeJS-3D教学一基础场景创建

Three.js 是一个开源的 JS 3D 图形库&#xff0c;用于创建和展示高性能、交互式的 3D 图形场景。它建立在 WebGL 技术之上&#xff0c;并提供了丰富的功能和工具&#xff0c;使开发者可以轻松地构建令人惊叹的 3D 可视化效果。 Three.js 提供了一套完整的工具和 API&#xff0…

【深度学习】实验13 使用Dropout抑制过拟合

文章目录 使用Dropout抑制过拟合1. 环境准备2. 导入数据集3. 对所有数据的预测3.1 数据集3.2 构建神经网络 3.3 训练模型3.4 分析模型 4. 对未见过数据的预测4.1 划分数据集4.2 构建神经网络4.3 训练模型4.4 分析模型 5. 使用Dropout抑制过拟合5.1 构建神经网络5.2 训练模型5.3…

基于Qt4的拉格朗日插值实现及使用

目录 1 拉格朗日插值算法 2 实现思路 3 子程序编写 1 框架搭建 2 加载节点值 3 加载插值点 4 位置查找 5 二点线性插值 3 子程序使用 1 拉格朗日插值算法 拉格朗日插值是一种常用的散点插值算法,是是以法国十八世纪数学家约瑟夫拉格朗日命名的一种多项式插值方法。是…

python爬虫——爬取豆瓣top250电影数据(适合初学者)

前言&#xff1a; 爬取豆瓣top250其实是初学者用于练习和熟悉爬虫技能知识的简单实战项目&#xff0c;通过这个项目&#xff0c;可以让小白对爬虫有一个初步认识&#xff0c;因此&#xff0c;如果你已经接触过爬虫有些时间了&#xff0c;可以跳过该项目&#xff0c;选择更有挑…

Linux Shell 实现一键部署podman

podman 介绍 使用 Podman 管理容器、Pod 和映像。从本地环境中无缝使用容器和 Kubernetes&#xff0c;Podman 提供与 Docker 非常相似的功能&#xff0c;它不需要在你的系统上运行任何守护进程&#xff0c;并且它也可以在没有 root 权限的情况下运行。 Podman 可以管理和运行…

JavaWeb后端开发登录操作 登录功能 通用模板/SpringBoot整合

登录功能的思路 前端会传入两个参数:用户名和密码 在用户表中查询用户名,并校对相应的密码(涉及查询操作) SQL语句 select * from emp where username jingyong and password 123456; 如果有则成功,没有则登录失败.不可能为多个,因为添加了unique唯一约束,最终只会有一条 …

如何将转换器应用于时序模型

一、说明 在机器学习的广阔环境中&#xff0c;变压器作为建筑奇迹屹立不倒&#xff0c;以其复杂的设计和捕获复杂关系的能力重塑了我们处理和理解大量数据的方式。 自 2017 年创建第一台变压器以来&#xff0c;变压器类型呈爆炸式增长&#xff0c;包括强大的生成 AI 模型&#…