Redis实际应用中的解决方案

Redis缓存使用问题

1数据一致性

分析一下几种方案:

1:先更新缓存,再更新数据库

2:先更新数据库,在更新缓存

3:先删除缓存,后更新数据库

4:想更新数据库,后删除缓存

1.1 新增数据

如果是新增数据,数据会直接写入数据库中,不用对缓存做任何操作,此时,缓存中本身没有新增的数据,而数据库的值是最新的,此时,数据库和缓存的数据是一致的。

1.2 更新数据

1.2.1 先更新缓存,再更新数据库

这个方案一般不考虑。原因是更新缓存成功,更新数据库出现异常,导致缓存数据与数据库中数据完全不一致,而且我们很难察觉,因为缓存中的数据一直都存在。

1.2.2 先更新数据库,在更新缓存

这个方案一般不考虑。原因是更新数据库成功,更新缓存出现异常,导致缓存数据与数据库中数据完全不一致。同时还有一下几个问题:

1)并发问题

同时请求A和B进行更新操作,那么会出现

  1. 线程A更新数据库
  2. 线程B更新数据库
  3. 线程B更新缓存
  4. 线程A更新缓存

这就出现了问题,线程A应该比线程B更新缓存早才对,但是因为网络问题,B却比A更新了缓存,这就导致了脏数据,因此不考虑

2)业务场景问题

如果是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根没有督导,缓存就会被频繁的更新,浪费性能。

1.3 删除缓存类

1.3.3 先删除缓存,后更新DB

该方案也会出问题,具体出现的原因如下。

1、此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

2、请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作;

3、此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中;

4、但是此时请求 A 并没有更新成功,或者事务还未提交,请求B去数据库查询得到旧值;

5、那么这时候就会产生数据库和 Redis 数据不一致的问题。

如何解决呢?其实最简单的解决办法就是延时双删的策略。就是

(1)先淘汰缓存

(2)再写数据库

(3)休眠1秒,再次淘汰缓存

redis.delete(key)
db.update(key)
Thread.sleep(N)
redis.delete(key)

这么做可以将N秒内所造成的缓存脏数据再次删除。

那么该休眠几秒如何确定?

针对上面情形,应该评估自己的项目读数据业务逻辑的耗时,然后写数据的休眠时间则在读数据业务逻辑耗时基础上加几百毫秒即可。

但是上述的保证事务提交完以后再进行删除缓存还有一个问题,就是如果你使用的是 Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。

此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

请求 A 更新操作,删除了

Redis,

请求主库进行更新操作,主库与从库进行同步数据的操作,

请 B 查询操作,发现 Redis中没有数据,

去从库中拿去数据,此时同步数据还未完成,拿到的数据是旧数据。

此时的解决办法有两个:

1、还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。

2、就是如果是对 Redis

进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。

继续深入,采用这种同步淘汰策略,吞吐量降低怎么办?

那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再

返回。这么做,加大吞吐量。

继续深入,第二次删除,如果删除失败怎么办?

所以,我们引出了,下面的第四种策略,先更新数据库,再删缓存。

4、先更新DB,后删除缓存

这种方式,被称为Cache Aside Pattern,读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据

后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。

1.4 方案如何选择的问题

一般线上,更多的偏向使用删除缓存类操作。

2 缓存穿透和击穿

2.1 缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,于是这个请求就可以随意访问数据库。缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。

造成缓存穿透的基本原因有两个:

第一:自身的业务代码和数据出现问题。比如,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1

的数据或 id 为特别大不存在的数据。如果不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去

请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。

第二:一些恶意攻击,爬虫等造成大量空命中。

下面是解决方案:

1:缓存空对象

当存储层不命中时,到数据库查发现也没有命中,那么仍然将空对象保留到缓存层中,之后访问该数据将会从缓存层中获取,这样保护了后端数据源。

但是同样叶会出现问题:

第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较

有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。

第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为

5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利

用消前面所说的数据一致性方案处理。

2:布隆过滤器拦截

在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。例如:一个推荐系统有4

亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用

户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过

滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传# Redis缓存使用问题

1数据一致性

分析一下几种方案:

1:先更新缓存,再更新数据库

2:先更新数据库,在更新缓存

3:先删除缓存,后更新数据库

4:想更新数据库,后删除缓存

1.1 新增数据

如果是新增数据,数据会直接写入数据库中,不用对缓存做任何操作,此时,缓存中本身没有新增的数据,而数据库的值是最新的,此时,数据库和缓存的数据是一致的。

1.2 更新数据

1.2.1 先更新缓存,再更新数据库

这个方案一般不考虑。原因是更新缓存成功,更新数据库出现异常,导致缓存数据与数据库中数据完全不一致,而且我们很难察觉,因为缓存中的数据一直都存在。

1.2.2 先更新数据库,在更新缓存

这个方案一般不考虑。原因是更新数据库成功,更新缓存出现异常,导致缓存数据与数据库中数据完全不一致。同时还有一下几个问题:

1)并发问题

同时请求A和B进行更新操作,那么会出现

  1. 线程A更新数据库
  2. 线程B更新数据库
  3. 线程B更新缓存
  4. 线程A更新缓存

这就出现了问题,线程A应该比线程B更新缓存早才对,但是因为网络问题,B却比A更新了缓存,这就导致了脏数据,因此不考虑

2)业务场景问题

如果是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根没有督导,缓存就会被频繁的更新,浪费性能。

1.3 删除缓存类

1.3.3 先删除缓存,后更新DB

该方案也会出问题,具体出现的原因如下。

1、此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

2、请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作;

3、此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中;

4、但是此时请求 A 并没有更新成功,或者事务还未提交,请求B去数据库查询得到旧值;

5、那么这时候就会产生数据库和 Redis 数据不一致的问题。

如何解决呢?其实最简单的解决办法就是延时双删的策略。就是

(1)先淘汰缓存

(2)再写数据库

(3)休眠1秒,再次淘汰缓存

redis.delete(key)
db.update(key)
Thread.sleep(N)
redis.delete(key)

这么做可以将N秒内所造成的缓存脏数据再次删除。

那么该休眠几秒如何确定?

针对上面情形,应该评估自己的项目读数据业务逻辑的耗时,然后写数据的休眠时间则在读数据业务逻辑耗时基础上加几百毫秒即可。

但是上述的保证事务提交完以后再进行删除缓存还有一个问题,就是如果你使用的是 Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。

此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

请求 A 更新操作,删除了

Redis,

请求主库进行更新操作,主库与从库进行同步数据的操作,

请 B 查询操作,发现 Redis中没有数据,

去从库中拿去数据,此时同步数据还未完成,拿到的数据是旧数据。

此时的解决办法有两个:

1、还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。

2、就是如果是对 Redis

进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。

继续深入,采用这种同步淘汰策略,吞吐量降低怎么办?

那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再

返回。这么做,加大吞吐量。

继续深入,第二次删除,如果删除失败怎么办?

所以,我们引出了,下面的第四种策略,先更新数据库,再删缓存。

4、先更新DB,后删除缓存

这种方式,被称为Cache Aside Pattern,读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据

后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。

1.4 方案如何选择的问题

一般线上,更多的偏向使用删除缓存类操作。

2 缓存穿透和击穿

2.1 缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,于是这个请求就可以随意访问数据库。缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。

造成缓存穿透的基本原因有两个:

第一:自身的业务代码和数据出现问题。比如,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1

的数据或 id 为特别大不存在的数据。如果不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去

请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。

第二:一些恶意攻击,爬虫等造成大量空命中。

下面是解决方案:

1:缓存空对象

当存储层不命中时,到数据库查发现也没有命中,那么仍然将空对象保留到缓存层中,之后访问该数据将会从缓存层中获取,这样保护了后端数据源。

但是同样叶会出现问题:

第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较

有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。

第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为

5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利

用消前面所说的数据一致性方案处理。

2:布隆过滤器拦截

在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。例如:一个推荐系统有4

亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用

户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过

滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层。

2.2 缓存击穿

缓存击穿是指一个keyg非常热点,再不停的扛着大并发,大并发几种对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就被穿破缓存,直接请求数据库。

缓存击穿:设置热点数据永不过期或者加互斥锁就ok了。

使用互斥锁(mutex key)

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是

立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache

的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个

get缓存的方法。

在这里插入图片描述

永远不过期

这里的“永远不过期”包含两层意思:

(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现

要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能

访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

4热点Key

在Redis中,访问频率高的key称为热点key。

产生原因和危害

原因

热点问题产生的原因大致有以下两种:

用户消费的数据远大于生产的数据(热卖商品、热点新闻、热点评论、明星直播)。

在日常工作生活中一些突发的事件,例如:双十一期间某些热门商品的降价促销,当这其中的某一件商品被

数万次点击浏览或者购买时,会形成一个较大的需求量,这种情况下就会造成热点问题。同理,被大量刊

发、浏览的热点新闻、热点评论、明星直播等,这些典型的读多写少的场景也会产生热点问题。

请求分片集中,超过单Server的性能极限。在服务端读数据进行访问时,往往会对数据进行分片切分,此过

程中会在某一主机Server上对相应的Key进行访问,当访问超过Server极限时,就会导致热点Key问题的产

生。

危害

1、流量集中,达到物理网卡上限。

2、请求过多,缓存分片服务被打垮。

3、DB击穿,引起业务雪崩。发现热点key

4.1预估发现

针对业务提前预估出访问频繁的热点key,例如秒杀商品业务中,秒杀的商品都是热点key。

当然并非所有的业务都容易预估出热点key,可能出现漏掉或者预估错误的情况。

客户端发现

客户端其实是距离key"最近"的地方,因为Redis命令就是从客户端发出的,以Jedis为例,可以在核心命令入口,使用这个Google Guava中的AtomicLongMap进行记录,如下所示。

使用客户端进行热点key的统计非常容易实现,但是同时问题也非常多:

(1) 无法预知key的个数,存在内存泄露的危险。

(2) 对于客户端代码有侵入,各个语言的客户端都需要维护此逻辑,维护成本较高。

(3) 规模化汇总实现比较复杂。

Redis发现

monitor命令

monitor命令可以监控到Redis执行的所有命令,利用monitor的结果就可以统计出一段时间内的热点key排

行榜,命令排行榜,客户端分布等数据

此种方法会有两个问题:

1、monitor命令在高并发条件下,内存暴增同时会影响Redis的性能,所以此种方法适合在短时间内使用。

2、只能统计一个Redis节点的热点key,对于Redis集群需要进行汇总统计。

4.2 解决热点key

使用二级缓存:可以使用 guava-cache或hcache,发现热点key之后,将这些热点key加载到JVM中作为本地缓存。访问这些key时直接从本地缓存获取即可,不会直接访问到redis层了,有效的保护了缓存服务器。

key分散将热点key分散为多个子key,然后存储到缓存集群的不同机器上,这些子key对应的value都和热点key是一样的。当通过热点key去查询数据时,通过某种hash算法随机选择一个子key,然后再去访问缓存机器,将热点分散到了多个子key上

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

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

相关文章

Docker在服务器linux下配置及打包【bash指令】

1.管理员将用户设置为docker用户 (注意:这里需要服务器管理员进行设置此句) sudo gpasswd -a ${user} docker2.列出docker镜像 docker images 3.列出正在运行的docker容器 docker ps 4.0.创建一个容器 docker pull python3.10 docker p…

STM32 同时使用 FMC 和 FSMC 问题

在一个项目中有同时和 FPGA 和 SDRAM 通讯的需求,分别使用 FSMC 和 FMC 来控制。 一般来说 SDRAM 64ms需要刷新所有行。用到的 SDRAM 为 8192 行,时钟用的 100M,故刷新寄存器计数值为 1560,正常也就这么写了。 但是在使用中发现…

前端 Android App 上架详细流程 (Android App)

1、准备上架所需要的材料 先在需要上架的官方网站注册账号。提前把手机号,名字,身份证等等材料准备好,完成开发者实名认证;软著是必要的,提前准备好,软著申请时间比较长大概需要1-2周时间才能下来&#xf…

需求文档怎么写?

1. 导言 我也来个导言: 写这篇博客的目的就是来解答一下下面几个问题: 需求文档怎么写?需求文档都应该包含哪些内容?怎样才算一个合格的需求文档? 产品需求文档(Product requriement document)&#xff…

全网首发亲测有用:python免费将chatgpt机器人接入个人微信(同时支持钉钉、QQ 以及别的语言模型如文心一言等)

一、获得免费chatgptAPI https://github.com/chatanywhere/GPT_API_free?tab=readme-ov-file 点击这个就可以获取一个免费的CHATGPT API key 或者觉得不够用的话,也可以付费购买在里面,看着价格很便宜,个人没有买过 tips: 转发API无法直接向官方接口api.openai.com发起…

Clickhouse 字符串函数 - 2

reverse​ 反转字符串。 reverseUTF8​ 以Unicode字符为单位反转UTF-8编码的字符串。如果字符串不是UTF-8编码,则可能获取到一个非预期的结果(不会抛出异常)。 format(pattern, s0, s1, …)​ 使用常量字符串pattern格式化其他参数。pat…

笔试强训Day20 动态规划 模拟

经此一役小红所向无敌 题目链接&#xff1a;A-经此一役小红所向无敌_牛客小白月赛37 (nowcoder.com) 思路&#xff1a; 水题 直接跟思路即可。 AC code&#xff1a; #include<iostream> using namespace std; typedef long long LL; LL a1,a2,b1,b2,t1,t2,sum; int m…

Spring中的DigestUtils:数据摘要的艺术与实用

1. 概述 DigestUtils 是 Spring Framework 提供的一个实用工具类&#xff0c;用于生成数据的摘要&#xff08;也称为哈希或散列&#xff09;。它封装了常见的哈希算法&#xff0c;如 MD5、SHA-1、SHA-256 等&#xff0c;使得开发者能够方便地对字符串、字节数组或其他数据源进…

专题五_位运算(3)

目录 137. 只出现一次的数字 II 解析 题解 面试题 17.19. 消失的两个数字 解析 题解 137. 只出现一次的数字 II 137. 只出现一次的数字 II - 力扣&#xff08;LeetCode&#xff09; 解析 注意这里指的是比特位上的01来进行统计的 题解 class Solution { public:int sin…

机器学习常见概念

1. 机器学习 定义&#xff1a; 机器学习是一种人工智能的分支&#xff0c;让计算机通过数据学习规律和模式&#xff0c;从而做出预测或做出决策&#xff0c;而无需明确编程指令。 应用场景&#xff1a; 机器学习广泛应用于各种领域&#xff0c;比如推荐系统、医疗诊断、金融风…

深入理解分布式事务⑧ ---->MySQL 事务的实现原理 之 MySQL 事务流程(MySQL 事务执行流程 和 恢复流程)详解

目录 MySQL 事务的实现原理 之 MySQL 事务流程&#xff08;MySQL 事务执行流程 和 恢复流程&#xff09;详解MySQL 事务流程1、MySQL 事务执行流程1-1&#xff1a;MySQL 事务执行流程如图&#xff1a; 2、MySQL 事务恢复流程2-1&#xff1a;事务恢复流程如下图&#xff1a; MyS…

基于V4L2框架的摄像头从上层到底层开发

文章目录 一、V4L2应用开发1、识别摄像头2、查看摄像头设备的能力3、查看支持视频格式4、设置视频格式5、申请帧缓冲6、启动采集7、出队取一帧图像8、入队归还帧缓冲9、停止视频采集10、退出释放资源 二、V4L2框架源码分析1、struct video_device2、struct v4l2_device *v4l2_d…

HAL库 嵌入式

HAL库 “HAL库”&#xff08;Hardware Abstraction Layer Library&#xff0c;硬件抽象层库&#xff09;通常是指在嵌入式系统开发中用来提供硬件操作抽象的软件库&#xff0c;使得应用程序可以在不直接操作硬件的情况下与硬件通信。这种库通常是由硬件制造商提供&#xff0c;用…

python json字符串怎么用format方法填充参数值报KeyError

python json字符串怎么用format方法填充参数值报KeyError 需求问题分析解决方案 需求 因为python中的字典和json中的一些变量有差异&#xff0c;比如&#xff1a;json中有null、true&#xff0c;在python中就不会被识别&#xff0c;只能转换成字符串&#xff0c;在通过loads()…

Java对象的比较(详解三种比较方式)

Java对象的比较 一、基本类型的比较二、引用类型的比较三、三种自定义比较的方式1、重写equals()方法2、基于Comparble接口比较3、基于Comparator比较器进行比较 一、基本类型的比较 对于Java中的基本类型而言&#xff0c;Java可以对其直接比较。整型浮点型就是直接比较其大小…

C#核心之面向对象-继承

面向对象-继承 文章目录 1、继承的基本规则1、基本概念2、基本语法3、示例4、访问修饰符的影响5、子类和父类的同名成员 2、里氏替换原则1、基本概念2、is和as3、基本实现 3、继承中的构造函数1、基本概念2、父类的无参构造函数3、通过base调用指定父类构造 4、万物之父和装箱拆…

关于ESP32下载的几个小问题

文章目录 一、没有收到串口数据二、vscode使用jtag烧录失败 在使用esp32的时候&#xff0c;下载遇到了这么几个小问题&#xff0c;写一下解决方法。 一、没有收到串口数据 报错如下&#xff1a; 这是在使用arduino下载的时候出现的错误&#xff1a;A fatal error occurred: …

Shell命令和基础学习

Shell的作用&#xff1a; 解释执行用户输入的命令或程序等用户输入一条命令&#xff0c;shell就解释一条键盘输入命令&#xff0c;Linux就给出响应的方式&#xff0c;称为交互式 外层应用程序 -> shell解释器 -> 操作系统核心 -> 机器硬件 shell脚本&#xff1a; wi…

c++多线程2小时速成

简介 c多线程基础需要掌握这三个标准库的使用&#xff1a;std::thread,std::mutex, andstd::async。 1. Hello, world #include <iostream> #include <thread>void hello() { std::cout << "Hello Concurrent World!\n"; }int main() {std::th…

Web Component fancy-components

css-doodle 组件库 fancy-components 组件库使用 yarn add fancy-components使用&#xff1a; import { FcBubbles } from fancy-components new FcBubbles() //要用哪个就new哪个 new 这里可能会报错eslink,eslintrc.js中处理报错 module.exports {rules: {no-new: off} …