redis笔记

本文深入探讨了Redis的单线程模型及其优势,包括非阻塞I/O、多路复用机制。详细介绍了AOF和RDB持久化策略,以及主从同步的全量和增量复制。同时,分析了Redis性能影响因素,如集合操作、大Key删除、内存碎片等,并提出了解决方案。此外,还讨论了Redis作为消息队列的适用性和面临的挑战,以及如何处理缓存一致性问题,如缓存穿透、雪崩和击穿。
摘要由CSDN通过智能技术生成

redis是单线程的是指,redis的网络io和键值对读写是由一个线程来完成的。但是redis的其他功能如持久化、异步删除、集群数据同步等,其实是由额外的线程来完成的。
为什么采取单线程的设计思想呢?因为多线程大部分情况下并不能呈现左图中的线性关系,而是像右图那样,这是由于多进程之间需要共享资源,为了保证共享资源的正确性,就需要有额外的机制进行保证,而这个额外的机制,就会带来额外的开销。在这里插入图片描述
并发访问控制一直都是多线程开发的一个难点问题,如果没有经过精细的设计,比如说,如果采用一个粗力度的互斥锁就会导致即使增加线程,大部分线程也在等待获取访问共享资源的互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。

为什么单线程redis那么快?
一方面,redis大部分操作都是在内存中完成的,再加上它采用高效的数据结构例如哈希表和跳表,另一方面,它采用多路复用的网络io
在这里插入图片描述
基础io模式有两个潜在的阻塞点,第一个是accept()和recv(),当Redis监听到一个客户端有连接请求,但是一直未能成功建立起连接的话,就会阻塞在accept()函数这里,导致其他客户端无法和redis建立起连接。第二个就是通过recv()从客户端读取数据时,数据一直未到达redis就会一直阻塞在recv()这里

非阻塞模式
针对监听套接字,我们可以设置非阻塞模式;当redis调用accept()但是一直没有连接请求到达的时候,redis可以返回处理其他操作,而不用一直等待。redis线程虽然不用继续等待,但是总得有机制继续在监听套接字上等待后续连接请求,并在有请求的时候通知redis。
在这里插入图片描述
到此linux的io多路复用机制就要登场了
io的多路复用机制是指一个线程处理多个io流。简单来说,**在redis只运行单线程的情况下,该机制运行在内核中同时存在多个监听套接字和已连接套接字。**内核会一直监听这些套接字上的连接请求或者数据请求。下图中的fd就是我们所说的套接字
为了在请求到达的时候能通知到redis线程,select/epoll提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。
在这里插入图片描述
基于事件的回调机制是指select/epoll一旦检测到有请求到达时,就会触发相应的事件。这个事件就会被放入到事件队列中,redis单线程只需要处理事件队列中的事件就可以了。

redis数据持久化的两大机制

1.AOF
AOF是写后日志,也就是先执行命令把数据写入到内存中,然后再记录日志。在这里插入图片描述
AOF中记录的是redis收到的每一条命令,这些命令以文本的方式保存。如下所示,如果先记日志再执行命令的话,就可能出现错误的命令被记录到日志中。
并且命令执行后才写日志不会阻塞当前的写操作在这里插入图片描述
AOF存在两个风险点:
1.虽然AOF避免了阻塞当前命令,但是它却会导致下一个命令阻塞,因为AOF也是在主线程中执行的。
2.如果执行完命令但是日志没有写入就宕机的话,就会导致最后一条日志丢失

因为这两个风险点均跟写磁盘的时机有关。所以只要控制写磁盘的时机就可以解决以上问题。
对于以上问题AOF提供了三种解决方案:也就是AOF配置项的三个可选值:
Always:同步写回,每个写命令执行完,立马同步将日志写入到磁盘中。
EverySec:每秒写回,每个写命令执行完,只是先把日志写入到文件的内存缓冲区中,然后再每隔一秒将缓冲区的数据写入磁盘。
No:跟上一种类似,只是缓存区写入磁盘的时间由操作系统决定。
在这里插入图片描述
AOF文件过大带来的影响:
1.文件系统本身对文件大小的限制
2.如果文件过大就会导致再往里追加命令记录的话效率会变低
3.如果宕机了,用AOF恢复的话,就会导致恢复过程非常缓慢,影响redis的正常使用

日志过大的解决办法是AOF的重写机制:redis会重新创建一份AOF日志,将redis数据库中的每一条键值对对应一条命令写入到日志中,例如“testkey”: “testvalue”,重写机制会记录 set testkey testvalue 这条命令
虽然AOF重写后日志会缩小,但是要把整个数据库的最新数据的操作日志都写回磁盘仍然是一个耗时的过程。所以就会有一个问题:重写是否会阻塞主进程

AOF日志重写的过程:
每次执行重写时,主线程都会fork出一个后台子线程bgrewriteaof然后此时fork会把主线程的内存拷贝一份给子线程,然后子进程就可以在不影响主进程的情况下,逐一把拷贝的数据写成操作,写入到重写日志中
此时因为主线程没有阻塞,如果有新来的写操作,redis会把这个操作分别写入到正在使用的AOF日志的缓冲区和新的AOF重写日志的缓冲区中在这里插入图片描述
2.RDB
内存快照:类似于照片记录的方式把某一时刻的数据以文件的方式记录到磁盘
内存快照的方式存在两个问题:
1.对哪些数据执行快照,这涉及到快照的执行效率的问题
redis执行的是全量快照,就是把内存中的所有数据全部记录在磁盘中
redis提供两个命令来生成RDB文件,分别是save和bgsave:
save:在主线程中执行,会导致主线程阻塞
bgsave:创建一个子线程,专门用来写入rdb文件

2.做快照的时候,数据能否做增删改,这涉及到redis是否阻塞,能否同时正常处理请求
为了保证做快照的时候数据是“不动的”,redis就只能处理读操作不能处理写操作。为了解决这个问题redis就用到了操作系统的写时复制技术,在执行快照的时候,正常处理写操作。

简单来说,bgsave子线程由主线程fork生成的,可以共享主线程的所有内存数据。bgsave运行后开始读取主线程的内存数据,并把它们写入到RDB文件中。
也就是如果是读操作主线程和子线程互不影响,但如果是主线程修改一份数据,这块数据就会被复制一份生成这个数据的副本,然后主线程在这个副本上进行修改

但是1.频繁的执行快照虽然不会阻塞主线程,频繁的将全量数据写入磁盘也会给磁盘带来压力,多个快照竞争有限的磁盘带宽,前一个快照还没做完后一个就已经开始了
2.频繁的fork子线程也会阻塞主线程。
通过增量快照解决以上两个问题这就需要我们记住哪些数据被修改了。但是记住哪些数据被修改了有需要花费额外的内存
在这里插入图片描述
所以Redis提出了混合使用AOF日志和内存快照的方法,这种方法既可以享受rdb的快速恢复,又可以享受aof只记命令的简单操作
在这里插入图片描述

主从库如何实现数据一致的?

如果实例宕机了,那么它在恢复期间是无法服务新来的数据存取请求的。
但是redis是高可靠的,体现在两个方面:1.数据尽量少丢失 2.服务尽量少中断
AOF和RDB保证了前者,而通过增加副本的数量来保证后者
但是如何保证多个副本数据的一致性呢?
通过主从库之间采用读写分离来保证数据的一致性
在这里插入图片描述
主从之间第一次同步是如何进行的?
现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),在实例2上通过执行以下命令完成实例之间的同步

replicaof 172.16.19.3 6379

在这里插入图片描述
第一阶段:psync表示要进行数据同步,psync 命令包含了主库的runID和复制进度offset两个参数
runID是每一个redis都会生成的一个随机id,用来唯一标识这个实例,?代表不知道主库的id
offset是第几次复制,-1代表第一次
FULLRESYNC代表全量复制
第二阶段:主库将数据同步给从库,数据就是RDB文件。这是从库会先清空现有数据,然后加载RDB。
在主库发送给从库数据的过程中,主库不会被阻塞,但是这个过程中,写请求没有记录到RDB中,而是将数据写入到repl buffer中。
第三阶段:主库会把第二阶段执行过程中的写命令再发送给从库。

如果是一主多从的架构就会导致,主节点需要fork多个子进程来生成rdb文件,并且主节点网络传输也会给主节点造成网络压力,所以采用主-从-从的架构设计。
在这里插入图片描述
以上架构存在一个风险点就是网络断连或者阻塞。
如果网络断连或者阻塞就会导致主库无法向从库同步数据,就会导致客户端可能会从从库读到旧数据
redis2.8之后,主从库会采用增量复制的方法进行同步数据,增量复制只是将断网期间主库收到的命令发送给从库,通过维护以下的环形缓冲区,将从库读到的数据与主库写到的数据之间的数据发送给从库。但是有一个问题就是环形缓冲区如果主库写入的较快而从库读取的较慢就会导致主库会覆盖之前写入的数据,一般而言我们可以调整repl_backlog_size参数
在这里插入图片描述

哨兵机制

如果主库故障,会发生以下问题在这里插入图片描述
哨兵机制是实现主从库自动切换的关键机制
哨兵是一个运行在特殊模式下的redis进程,主从实例运行的同时它也在运行。哨兵主要有三个职责:监控、选主、通知
在这里插入图片描述

判断主库下线有两种情况:主观下线和客观下线
主观下线是指哨兵发现主库或者从库对于ping命令的响应超时
但是主观下线会有误判的情况,例如网络拥塞、网络压力过大、主库本身压力过大等情况
那如何来减少误判呢?一般我们对一些重要的事情进行判断都会找家人或者朋友一起商量。
所以哨兵机制也是类似的,它通常采用多个实例组成的集群模式进行部署,也就是哨兵集群。
客观下线是指
选择新主库的方法:
首先会根据网络状况进行筛选,网络状况是根据配置项的down-after-milliseconds * 10判断的,down-after-milliseconds代表超时的时间,10代表次数
然后会根据从库优先级、从库复制进度、从库id号进行打分
在这里插入图片描述

哨兵机制的组成和运行机制

1.基于pub/sub机制的哨兵集群组成
通过哨兵发送到指定频道,其他哨兵订阅该频道的消息
哨兵
哨兵是如何知道从库的ip地址和端口的呢?
首先由哨兵向主库发送INFO命令,然后主库会把Slave列表返回给哨兵,然后哨兵就可以跟从库建立连接了。
但是,哨兵不能只和主、从库连接。因为,主从库切换后,客户端也需要知道新主库的连接信息,才能向新主库发送请求操作。所以,哨兵还需要完成把新主库的信息告诉客户端这个任务。
在这里插入图片描述
在实际使用哨兵时,我们有时会遇到这样一个问题:如何在客户端通过监控了解到redis主从切换的过程。
在这里插入图片描述知道这些频道之后,就可以让客户端从哨兵这里订阅消息了

Redis切片集群

切片集群就是启动多个redis实例组成一个集群,然后按照一定规则把收到的数据分成多份,每个实例保存一份数据。
单实例保存25GB的数据,在fork子进程生成RDB的时候需要拷贝25GB的数据。
而切片之后只需要5GB
在这里插入图片描述
如何保存更多的数据资源呢?
通过纵向和横向扩展
在这里插入图片描述
但是切片集群需要解决两个问题:
1.数据切片后,多个实例之间如何分布?
Redis cluster通过哈希槽(Hash Slot)来处理数据和实例的对应关系。在redis Clsuter方案中,一个切片集群共有16384个哈希槽,通过cluster create 命令Redis会自动把hash槽平均分配在集群实例上,但是有一个问题就是不同的实例之间资源配置不一。所以可以通过cluster addslots来手动分配Hash槽在这里插入图片描述
客户端如何定位数据的呢?
redis每个实例都会将它的hash槽发送给其他实例,来完成hash槽分布信息的扩散。然后客户端收到hash信息后就会将hash槽信息缓存在本地。
但是hash槽并不是一成不变的,最常见的两个变化:
1.在集群中,实例需要新增或者删除,redis需要重新分配hash槽
2.为了负载均衡,redis需要将hash槽全部重新分配一遍
此时,实例之间还可以通过相互传递消息,获得最新的哈希槽分配信息,但是,客户端是无法主动感知这些变化的。这就会导致,它缓存的分配信息和最新的分配信息就不一致了,那该怎么办呢?
通过重定向机制,如果该实例不存在该hash槽,就会返回指定的hash槽位所在的实例,客户端重新更新缓存在这里插入图片描述
有一种情况就是如果某一槽位的数据正在迁移,例如slot2正在迁移,此时发送slot2原本的数据的请求,就会返回ASK <槽位id> <该槽位正在迁移到的实例>
ASK有两层含义:1.表示slot正在迁移 2.ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。
在这里插入图片描述

Redis适合做消息队列么?

消息队列在存取消息的时候,需要满足三个需求:
1.消息保序:在读写场景下,不管如何设计都需要严格保证消息的有序
2.处理重复消息:例如在出现网络阻塞的场景下,会出现数据重传的现象,此时消费者会收到多条重复消息,如果这条消息是修改操作,就会导致数据被多次修改。
3.保证消息可靠性:例如如果因为宕机或者故障导致消息没有处理完的情况。当消费者重启后,可以重新读取消息再次进行处理
解决以上三个问题的方案:
1.基于List的消息队列:但是以上方法有一个潜在的风险点,list并不会主动通知消费者,所以就需要消费者不停地调用RPOP命令来判断是否有新消息写入,这会造成额外的资源开销。
为了解决以上问题,redis提供了BRPOP命令。BRPOP命令也称为阻塞式读取。客户端在没有读取到队列数据时,会自动阻塞直到有新数据写入队列,再开始读取新数据。
如何处理重复消息呢?消息队列要给每一个消息提供全局唯一的ID号,另外消费者程序要把每一个处理过的消息的id号记录下来。
如何保证消息的可靠性呢?为了留存消息。list提供了BRPOPLPUSH命令,这个命令是让消费程序从一个list中读取消息,同时会把这个消息再插入到另一个list中留存,这样如果消费者读取了消息没有正常处理,等它重启之后,就会从备份list中重新读取消息并进行处理
在这里插入图片描述

影响redis性能的5大方面的潜在因素:

1.redis内部的阻塞式操作
redis的网络io和键值对的读写是由主线程来完成的,那么如果在主线程执行的操作消耗的时间过长就会导致主线程阻塞
和redis交互的对象,以及交互式发生的操作:
客户端:网络IO、客户端增删改查操作、数据库操作
网络io由于使用的是多路复用机制,所以不会是导致阻塞的因素
复杂O(N)的键值对的增删改查一定会是阻塞redis的操作:例如集合的全量查询和聚合操作
另外删除的操作也会阻塞主线程,因为操作系统释放掉的内存块会插入一个空闲的内存块链表,以便后续进行管理和再分配。所以如果一次释放大量的内存就造成空闲内存链表操作时间增加,造成redis阻塞
以下是不同元素数量的集合在删除操作的耗时:在这里插入图片描述
同样的清理数据库的操作也会导致redis阻塞,因为它涉及删除和释放所有的键值对。

磁盘:生成RDB快照、记录AOF日志、AOF日志重写
由于redis采用子线程生成RDB快照,以及执行AOF日志重写操作,所以不会导致主线程阻塞。
但是AOF日志会根据不同的写回策略进行落盘操作,所以也会导致redis阻塞
主从节点:主库生成,传输RDB文件,从库接收RDB文件,清空数据库,加载RDB文件

切片集群实例:向其他实例传输哈希槽信息,数据迁移

所以以下五个阻塞点只有1和5是关键路径,其他三种都可以通过异步子线程的方式进行优化:
1.集合全量查询和聚合操作
2.bigkey删除
3.清空数据库
4.AOF日志同步写
5.从库加载RDB文件

异步的子线程机制:
1.惰性删除:通过主线程将请求放入到队列中,然后队列返回一个已完成删除的信息,子线程从队列获取任务进行删除在这里插入图片描述
当有大量元素需要删除的时候,使用UNLINK命令
清空数据库操作:

FLUSHDB ASYNC
FLUSHALL AYSNC

2.cpu和NUMA架构的影响

目前的cpu架构:在这里插入图片描述
在这里插入图片描述
taskset命令可以将一个程序绑定到一个核上运行(其中-c代表核号)

taskset -c 0 ./redis-server

cpu的NUMA架构对redis的性能影响:
在CPU多核的场景下,用taskset命令把Redis实例和一个核绑定,可以减少Redis实例在不同核上被来回调度执行的开销,避免较高的尾延迟;在多CPU的NUMA架构下,如果你对网络中断程序做了绑核操作,建议你同时把Redis实例和网络中断程序绑在同一个CPU Socket的不同核上,这样可以避免Redis跨Socket访问内存中的网络数据的时间开销。
在这里插入图片描述
但是绑核存在的问题:由于redis除了主线程之外还存在大量的子线程,所以如果只绑定一个逻辑核的话就会导致子进程占用cpu导致主线程阻塞。
解决办法:
1.将redis绑定在一个物理核而不是一个逻辑核上,但是实际上资源不足的时候也会导致主线程阻塞
2.优化redis的源码

3.Redis的关键系统配置

4.Redis内存碎片

5.Redis缓冲区

如何应应对变慢的redis?

例如下图三个操作需要保证事务的原子性,所以如果Redis变慢就会导致MySQL变慢等连锁反应。
在这里插入图片描述
首先判断redis是否慢需要指定一个基线性能,而如何判断基线性能呢?
我们可以通过./redis-cli --intrinsic-latency 120以下命令来指定基线性能,120代表监控的时间。其中最大延迟定为基线

紧接着我们需要排查变慢的原因:
Redis自身特性的影响:
**慢查询命令:**可通过https://redis.io/commands/ 查看时间复杂度。 通过1.采用更高效的命令代替,比如如果你需要返回一个SET中的所有成员,可以通过SSCAN多次迭代返回而不是使用SMEMBERS一次返回。 2.当你需要执行排序、交集、并集等操作的时候,可以在客户端完成,而不是采用SORT、SUNION、SINTER这些命令。

过期key操作: Redis对key可以设置过期时间,默认情况下每100ms会删除一些过期的key,具体的算法如下:1.采用ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个数的key,并将其中过期的key全部删除。
2.如果超过25%的key过期了,就会一直删除直到过期key的比例下降到25%以下。
出现以上问题的重要来源,频繁使用带有相同时间参数的EXPIREAT命令设置过期key,解决办法:可以在时间参数上加上一定范围内的随机数。

文件系统和操作系统的影响
AOF的三种日志写回策略实际上依赖的操作系统的两个系统调用:write和sync
write:只把日志记录写入到内核缓冲区中就返回,而不需要等待日志实际写入到磁盘。
sync:需要等待日志写入到磁盘再返回。
在这里插入图片描述
everysec由于允许丢失1s的数据,所以Redis主线程不需要确保每个操作记录日志都写回磁盘。所以everysec的方式允许使用后台子线程来执行。
always由于不允许数据丢失,主线程请求需要知道响应结果,所以不能使用后台子线程来执行。

当AOF重写操作占用了大量的io的时候,会导致fsync后台线程阻塞(因为fsync会一直等待到磁盘写入),由于redis主线程会监控fsync后台子线程的执行进度,所以也会导致redis主线程阻塞。
在这里插入图片描述
如果我们对延迟非常敏感的话,但同时允许一定量的数据丢失,那么可以将no-appendfsync-on-rewrite 设置为yes。
该配置项配置为yes的时候代表AOF重写时,不进行sync操作,也就是说redis把写命令写入到内存后,就直接返回而不是调用后台线程进行sync操作。当然如果发生宕机就会导致数据丢失

2.操作系统的swap机制:swap触发后影响的是redis主io线程,这会极大的增加redis的耗时。
触发swap机制的原因是物理机内存不足,可能是redis自身占用过大内存,或者宿主机的其他应用占用过大内存。

$ redis-cli info | grep process_id
process_id: 5332
$ cd /proc/5332
$cat smaps | egrep '^(Swap|Size)'
Size: 584 kB
Swap: 0 kB
Size: 4 kB
Swap: 4 kB
Size: 4 kB
Swap: 0 kB
Size: 462044 kB
Swap: 462008 kB
Size: 21392 kB
Swap: 0 kB

3.内存大页机制:虽然分配相同内存时,内存大页会一次分配大量的内存页,可以减小多次分配的。但是redis数据需要持久化保存,此时redis仍然可以接受客户端的写操作,所以就需要写时复制,一旦数据有修改,redis并不会修改内存中的数据,而是先将数据拷贝一份再进行修改。如果开启内存大页机制就会导致写时复制的时候会复制大量内存。

4.其他:如果使用redis主从集群,把主库的数据量控制到2 ~4GB,以免主从复制的时候从库因为加载大的RDB文件而阻塞

为什么删除数据后,内存占用率仍然很高?

实际上这是因为当数据删除后,redis释放的内存空间会由内存分配器处理,并不会返回给操作系统。
redis的内存碎片是如何形成的?内因是内存分配策略 外因是redis的负载,包含大小不一的键值对以及键值对修改删除带来的内存空间的变化
通过INFO memory命令可以查看到redis内存使用的详细情况

INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86

mem_fragmentation_ratio代表当前redis内存的碎片率。
它是通过used_memory_rss/used_memory计算出来的,used_memory_rss代表操作系统实际分配给redis的物理内存,used_memory代表redis为了保存数据实际申请的内存空间。
当mem_fragmentation_ratio大于1.5的需要采取一些措施来降低内存碎片率了。
如下图所示,可以通过redis的自动碎片清理机制来降低内存碎片率。但是碎片清理机制是有代价的,碎片清理需要拷贝内存,由于redis是单线程的就会导致redis无法处理请求性能降低。并且数据拷贝是有先后顺序的。在这里插入图片描述
如何解决碎片清理机制带来的问题呢?通过控制碎片清理的开始和结束时机,以及占用的CPU比例,从而减少碎片清理对Redis本身请求处理的性能影响。

redis的内存缓冲区介绍

缓冲区的两个应用场景:1.暂存客户端和服务器端传输的数据 2.在主从节点间数据同步的时候,用来暂存主节点接收的写命令和数据
如何查看输入缓冲区的内存使用情况?

CLIENT LIST
id=5 addr=127.0.0.1:50487 fd=9 name= age=4 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client

age表示以s为单位的已连接时长
cmd表示客户端最新执行的命令
qbuf表示输入缓冲区已经使用的大小
qbuf-free表示输入缓冲区尚未使用的大小

发生输入缓存区溢出的情况:
1.写入bigkey,比如一下写入了百万级别的集合类型数据
2.服务器端处理请求的速度过慢

redis输出缓冲区包含两部分:
1.一个大小为16KB的固定缓冲空间。用于暂存OK响应和出错信息的
2.一个可以动态增加的缓冲空间,用来暂存大小可变的响应结果。

什么情况下会发生输出缓存区溢出呢?
1.服务器返回bigkey的大量结果
2.执行了MONITOR命令; MONITOR命令是用来检测reids执行的
3.缓冲区大小设置得不合理。

和redis交互的客户端类型:
1.常规和redis服务端进行读写的普通客户端
普通客户端一般做如下设置:client-output-buffer-limit normal 0 0 0
2.订阅了redis频道的订阅客户端
订阅客户端一般这样设置:client-output-buffer-limit pubsub 8mb 2mb 60
3.主节点上用来主从同步的从节点客户端

主从集群中的缓存区
复制缓冲区的溢出问题:在全量复制过程中,主节点在向从节点传输RDB文件的同时,会继续接收客户端发来的写命令,这些写命令会保存在复制缓冲区中,等RDB文件传输完成后再发送给从节点去执行。所以如果在全量复制的时候,从节点接收和加载RDB较慢,同时主节点接收到大量的写命令,写命令在复制缓冲区越积越多就会导致溢出。在这里插入图片描述
如何避免复制缓冲区发生溢出?
1.我们可以控制主节点保存的数据量大小,这样可以让全量同步执行得更快一些,避免复制缓冲区积累过多命令
2.合理的设置复制缓冲区大小

缓存一致性

一致性包括以下两点:
1.如果缓存中存在数据的话,数据库中的数据和缓存中的数据一致
2.如果缓存中不存在数据的话,数据库中的数据必须是最新数据

之前介绍过的读写场景会存在数据不一致的情况:
例如应用要将数据从10更新为3,但是更新数据库的时候失败了,此时我们读到的数据将还是旧值。在这里插入图片描述
在这里插入图片描述
如何解决数据不一致呢?
通过重试机制,把要删除的缓存值或者要更新的数据值放入到消息队列中,然后当应用如果没有成功地删除缓存或者更新数据库值的时候,可以从消息队列中重新读取这些值然后再进行删除或者更新。在这里插入图片描述
情况一:先删除缓存再更新数据库
在这里插入图片描述
解决方案:在线程A更新完数据库之后需要sleep一段时间,再进行一次缓存删除操作

情况二: 先更新数据库,再删除缓存值
在这里插入图片描述
缓存雪崩的解决办法:1.设置一定范围内的随机的过期时间 2.熔断和限流 3.采用集群架构设计
缓存击穿:热点数据在缓存中大量失效
缓存击穿的解决办法:1.对于热点数据不进行过期时间的设置
缓存穿透:数据既不在缓存中,也不在数据库中。一般发生在缓存和数据库中的数据被误删除和服务被恶意攻击(专门访问数据库中没有的数据)的情况下
缓存穿透的解决方案:1.缓存空值或者缺省值 2.使用布隆过滤器快速判断数据是否存在,避免从数据库判断,减小数据库压力。 3.在前端进行请求检查,例如在前端把恶意的请求进行过滤

缓存污染

缓存污染是指有些数据访问的次数非常少,甚至有的只访问一次,但是会一直被缓存着,白白占用内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值