Redis

Redis是什么

一个开源基于内存存储、Key-Value结构的缓存数据库,并提供多种语言的API

为什么redis快

完全基于内存**、** 数据结构简单**,操作也简单、** 采用单线程设计,避免上下文切换和竞争条件、使用多路I/O复用模型,非阻塞IO、使用底层模型不同,Redis直接自己构建了VM机制,因为一般的系统调用系统函数,会浪费一定的时间去移动和请求

redis的应用场景有哪些

计数器、数据库缓存、会话缓存、全网页缓存(FPC)、消息队列(订阅/发布功能)、分布式锁(redis支持的setnx实现分布式锁、官方推荐的分布式锁「redisson

如何实现redisson分布式锁

首先需要引入redisson依赖配置redis的节点配置信息 ,然后使用**@Bean** 声明RedissonClient 的客户端,使用的时候就需要注入redisson的客户端。然后使用redissonClient.getLock(锁的唯一标识);获取到RLock锁对象。通过锁对象可以使用lock或者trylock进行加锁。业务执行完成之后调用unlock方法进行落锁

redission分布式锁的底层和实现原理是什么

使用redisson实现的分布式锁,底层是setnx和lua脚本(保证原子性)。

Redis实现分布式锁如何合理的控制锁的有效时长

加锁、设置过期时间等操作都是基于lua脚本完成。

redis的数据类型有哪些

基本数据类型

  1. String:对字符串或字符串部分进行操作;虽然是Stirng类型但是也支持数字、浮点。对数据可以进行自增减操作
  2. List:双向链表。从两端压入或弹出元素;对单个或多个进行修剪操作;保留一定范围的元素
  3. Set:添加、获取、移除单个元素,检查一个元素是否存在,计算交、并、差集计算,在集合里面随机获取元素
  4. Hash:添加、获取、移除、单个键值对;获取所有键值对;检查某个键是否存在
  5. ZSet:有序结合。添加、获取、删除元素;根据分值范围或成员获取元素;计算一个键的排名

特殊数据类型

  1. 地理位置(geospatial)
  2. HyperLogLog是用来做基数统计的算法
  3. 位存储(bitmaps)

redis的持久化模式有哪些

**RDB(redisdatabase缩写快照)**模式

原理:按照一定的时间将内存数据以快照的形式保存到硬盘中

优点:只有一个dump.rdb文件,方便持久化容灾性好性能最大化,子进程完成数据备份,不影响主进程;数据保存性能和数据恢复性能比较快特别是数据集大时候

缺点:数据安全性低,隔一段时间备份会**有数据丢失的问题,**如果我设置快照时间是10分钟,在上一个10分钟之后,第九分钟宕机了那么这九分钟的数据会丢失

**AOF(appendonlyfile)**模式

原理:将redis执行的命令放到一个日志文件中记录

优点:数据安全,可以做到记录备份每一条数据;rewrite模式会对过大的文件进行合并重写,删除其中的某些命令;比RDB方式更安全

缺点:AOF文件比RDB文件大,数据恢复启动时慢

如果同时使用两种方式备份时,优先使用AOF恢复数据

**HPM(HybridPersistenceMode)**混合模式

在持久化时:Redis会在保存AOF文件时,首先生成一个RDB快照并将其作为AOF文件的前缀。在AOF文件的前半部分包含了一个完整的数据快照(RDB快照)。在AOF文件的后半部分,记录了自生成RDB快照以来的所有写操作日志。

在恢复时:Redis首先加载AOF文件中的RDB快照部分,这个过程非常快,因为它只是加载一个二进制文件。然后,Redis会继续读取并重放AOF文件中记录的写操作日志,以恢复生成RDB快照之后的所有数据更改。

数据容灾后恢复速度快:因为首先加载的是RDB快照,整个数据集可以一次性快速恢复,然后再重放后续的写操作日志,相比完全重放整个AOF日志快得多。同样数据安全性也很高,因为AOF文件记录了每一个写操作,确保数据的完整性,减少数据丢失的可能性。

redis的跳表是什么

Redis中的跳跃表:https://blog.csdn.net/weixin_72525373/article/details/144307605

跳跃表是一种有序的数据结构,它通过在每个节点维持多个指向其他节点的指针,从而达到快速访问节点的目的。

Redis的跳跃表由zSkipListNode和skipList两个结构定义,其中zSkipListNode结构用于表示跳跃表节点,而zSkipList结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针

Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多 ,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合健的底层实现。

跳跃表在链表的基础上增加了多级索引以提升查找的效率,但其是一个空间换时间的方案,必然会带来一个问题------索引是占内存的。原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值值和几个指针,并不需要存储对象,因此当节点本身比较大或者元素数量比较多的时候,其优势必然会被放大,而缺点则可以忽略。

Redis的跳跃表由zskiplistNodeskiplist两个结构定义,其中zskiplistNode结构用于表示跳跃表节点,而zskiplist结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等。

skiplist结构如下:

复制代码
typedefstructzskiplist{
structzskiplistNode*header,*tail;
unsignedlonglength;
intlevel;
}zskiplist;
  • header:指向跳跃表的表头节点,通过这个指针程序定位表头节点的时间复杂度就为O(1)

  • tail:指向跳跃表的表尾节点,通过这个指针程序定位表尾节点的时间复杂度就为O(1)

  • level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内),通过这个属性可以再O(1)的时间复杂度内获取层高最好的节点的层数。

  • length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内),通过这个属性,程序可以再O(1)的时间复杂度内返回跳跃表的长度。

    typedefstructzskiplistNode{
    sdsele;//成员对象(robjobj;)指向一个sds
    doublescore;//分值用于实现zset
    structzskiplistNode
    backward;//后退指针指向前一个节点
    structzskiplistLevel{//层用一个结构体数组来表示各层中节点的关系
    structzskiplistNode* for ward;//前进指针
    unsignedlongspan;//跨度实际上是用来计算元素排名(rank)的,在查找某个节点的过程中,将沿途访过的所有层的跨度累积起来,得到的结果就是目标节点在跳跃表中的排位
    }level[];
    }zskiplistNode;

Redis跳跃表示例如下:

  • 成员对象(ele):各个节点中的o1、o2和o3是节点所保存的成员对象。在同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值却可以是相同的:分值相同的节点将按照成员对象在字典序中的大小来进行排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向)
  • 分值(score):各个节点中的1.0、2.0和3.0是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列
  • 后退(backward)指针:节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。与前进指针所不同的是每个节点只有一个后退指针,因此每次只能后退一个节点
  • 层(level):节点中用1、2、L3等字样标记节点的各个层,L1代表第一层,L代表第二层,以此类推。每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离(跨度越大、距离越远)。在上图中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。每次创建一个新跳跃表节点的时候,程序都根据幂次定律(powerlaw,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的"高度"。

redis的链表数据结构

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。

链表在Redis中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。

每个链表节点使用一个listNode结构表示(adlist.h/listNode):

复制代码
typedefstructlistNode{
structlistNode*prev;
structlistNode*next;
void*value;
}listNode;

我们可以通过直接操作list来操作链表会更加方便:

复制代码
typedefstructlist{
listNode*head;//表头节点
listNode*tail;//表尾节点
void*(*dup)(void*ptr);//节点值复制函数
void(*free)(void*ptr);//节点值释放函数
int(*match)(void*ptr,void*key);//节点值对比函数
unsignedlonglen;//链表长度
}list;

list组成的结构如下图:

特性:

  • 双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的时间复杂度都是O(N)
  • 无环:表头节点的prev指针和表尾节点的next都指向NULL,对立案表的访问时以NULL为截止
  • 表头和表尾:因为链表带有head指针和tail指针,程序获取链表头结点和尾节点的时间复杂度为O(1)
  • 长度计数器:链表中存有记录链表长度的属性len
  • 多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数。

redis的Set(集合)底层数据结构

set底层是由哈希表或整数集合来实现的,当元素是整数时,采用整数集合intset数据结构。当元素不是整数,或者元素个数大于512个时,则使用哈希表的数据结构HashTable

redis的Hash(哈希)底层数据结构

Hash底层是由哈希表和压缩列表来实现的

什么是ziplist压缩列表

压缩列表(ziplist)是redis当中列表和哈希键的底层实现方式之一,若哈希键或者列表当中元素个数较少并且均为小整数和长度较短的字符串,那么redis就会把ziplist作为其底层实现

zlbytes:固定占用4字节,记录整个压缩列表占用的字节总数

zltail:固定占用4字节,记录尾节点(entryN)到压缩列表起始位置的字节总数

zllen:固定占用2字节,记录存储的节点个数

entryX:内存大小不定,由存储的内容决定

zlend:固定占用1字节,且为固定值0xFF,用于标记压缩列表的结束

Redis的数据类型与底层数据结构如何对应的

redis单线程还是多线程的

Redis早先的版本里,只使用一个线程来通过事件循环来处理所有用户请求,就可以达到每秒数万QPS的处理能力。

Redis是从6.0开始支持多线程了。但是**redis对于指命令处理、逻辑处理仍然是单线程。**就是不管有多少条连接去操作redis的数据,redis对命令的处理都在一个线程完成。

什么是缓存穿透、如何解决

缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机

解决的话有两个常用的方案:

缓存空对象:是指在持久层没有命中的情况下,对key进行set(key,null),这个方案会有一些缺陷虽然null不占用空间,但是会造成大量的key存在,并且缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响,比如设置这个空的key之后,数据库有插入了这个key,所以可能会造成数据不一致,那么我们就需要使用额外的业务操作来去进行处理

布隆过滤器拦截:在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。

什么是缓存雪崩、如何解决

缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩,目前我们持久层的设计并不具备高并发的场景,所以大量请求进入数据库甚至会导致数据库的宕机

目前我们现在做的方式就是去设置随机过期时间,存储缓存的时候我们封装的redis当中会进行随机按照小时、天、星期、月的方式进行生成过期时间,从而解决大批量key同时过期的情况

什么事缓存击穿、如何解决

当前key是一个热点key(例如一个秒杀活动),并发量非常大。重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

解决方案的话

使用分布式互斥锁:只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout),但是这个方案会有弊端比如说重建索引的时间较长,导致锁超时或者造成请求超时的情况

数据永不过期:从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是"物理"不过期。这个方案的话会存在数据不一致、需要用业务逻辑额外维护热点key的情况,因为有些key他只是在某个时间段是热点key

所以综合来说还是需要结合我具体的业务场景来去进行具体的设计

Redis的简单动态字符串是什么

简单动态字符串(simpledynamicstring,SDS)

SDS的结构体(即不同header类型)如下:

复制代码
struct__attribute__((__packed__))hisdshdr5{
unsignedcharflags;/*3lsboftype, AND 5msbofstringlength*/
charbuf[];
};
struct__attribute__((__packed__))hisdshdr8{
uint8_tlen;/*used*/
uint8_talloc;/*excludingtheheader AND nullterminator*/
unsignedcharflags;/*3lsboftype,5unusedbits*/
charbuf[];
};
struct__attribute__((__packed__))hisdshdr16{
uint16_tlen;/*used*/
uint16_talloc;/*excludingtheheader AND nullterminator*/
unsignedcharflags;/*3lsboftype,5unusedbits*/
charbuf[];
};
struct__attribute__((__packed__))hisdshdr32{
uint32_tlen;/*used*/
uint32_talloc;/*excludingtheheader AND nullterminator*/
unsignedcharflags;/*3lsboftype,5unusedbits*/
charbuf[];
};
struct__attribute__((__packed__))hisdshdr64{
uint64_tlen;/*used*/
uint64_talloc;/*excludingtheheader AND nullterminator*/
unsignedcharflags;/*3lsboftype,5unusedbits*/
charbuf[];
};

SDS主要由以下4部分组成:

  • len:占4个字节,表示buf的已用长度(额外开销)
  • alloc:占个4字节,表示buf的实际分配长度,一般大于len(额外开销)
  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis会自动在数组最后加一个"\0",这就会额外占用1个字节的开销(保存实际数据)
  • flags:表示使用的是哪种header类型

当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。(字符串最大长度为512M)。

SDS存储图如下:

一个字符串类型的内存最大容量

512M

Redis的过期策略

过期策略:当key过期了之后如何对Key进行处理

定时过期策略:到了时间就立即移除(对内存友好,但会占用大量cpu)

惰性过期策略:当访问一个key的时候再判断是否过期(对内存不够友好)

定期过期策略:每过一段扫描一次数据,清除过期的key

++redis采用惰性过期策略和定期过期策略++

++redis通过++ ++expire++ ++和++ ++persist(移除可以的过期时间)++ ++设置++ ++过期时间++ ++和++ ++永久生效++

redis的淘汰策略是什么

淘汰策略:指缓存的内容不足,怎么处理需要新写入需要额外空间的数据

allkeys-lru:最近最少使用

allkeys-r AND om:任意随机淘汰一个

no-enviction:++禁止淘汰,新写入会报错,但保证数据不会丢失,++ ++系统默认的方式++

volatile-lru:最近最少使用的数据

volatile-ttl:最接近淘汰时间的数据

redis的线程模型是什么

Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(fileeventh AND ler)。

它的组成结构为4部分:多个套接字IO多路复用程序文件事件分派器事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型

文件事件处理器使用**I/O多路复用(multiplexing)**程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

被监听的套接字准备好执行应答(accept)读取(read)写入(write)关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件虽然文件事件处理器以单线程方式运行

但通过使用I/O多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。

redis集群的原理

Redis3.0加入了Redis的集群模式,实现了数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题

Redis集群采用去中心化的思想,没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node

Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点

Redis使用一致性hash算法来保证相同key的数据可以落到同一个数据节点上面,Redis集群中有16384个哈希槽(槽的范围是0-16383,哈希槽),将不同的哈希槽分布在不同的Redis节点上面进行管理

redis集群不保证数据的强一致性,在实际中集群在特定的条件下可能会丢失写操作

redis的槽位置是什么

Redis集群有16384个哈希槽 ,每个key通过CRC16校验 后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽

redis的集群模式

一主一从,一主多从、哨兵模式(sentinel)(主从复制的一种实现

一主多从架构是什么

主要用来支撑读高并发,一主多从,一个节点写入,多个阶段读取

复制机制

一个master节点是可以配置多个slave节点的。slave节点也可以连接其他的slave节点,slave节点做复制的时候,不会堵塞master节点的正常工作。slave节点在做复制的时候 ,也不会堵塞对自己的查询操作,它会用旧的数据集来提供服务 ;但是复制完成的时候, 需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了

建议必须开启master节点的持久化:如果slave持久化,可能在master宕机重启的时候数据是空的,然后可能一经过复制,slave节点的数据也丢了。

核心原理

  1. 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
  2. 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
  3. 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
  4. 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
  5. 之后,主Redis每当接收到``写``命令时就会将命令发送从Redis,从而保证数据的一致

redis的哨兵模式(sentinel)

主从复制的一种实现

Redis哨兵模式以三大核心能力支撑高可用架构:通过持续心跳检测与状态分析,实时监控主从节点的进程健康状态;当识别到节点异常(如主节点不可用)时,自动触发多级响应机制,包括向管理员发送告警消息通知,以及启动故障转移流程,将符合条件的从节点提升为新主节点,确保服务持续可用。

其高可用能力依赖于哨兵节点自身设计的分布式特性。哨兵以集群形式部署,各节点通过共识机制协同工作。判断主节点宕机需基于集群内多数哨兵的共同裁决,以此规避单点误判风险,该过程涉及分布式选举算法。同时,集群具备容错能力,允许部分哨兵节点失效而不影响整体功能,但需遵循至少部署3个实例的原则,以形成多数投票的决策基础。

实际部署中需重点关注两方面:其一,哨兵集群的健壮性要求至少3个独立实例,避免因节点数量不足导致脑裂或选举僵局;其二,受限于Redis主从异步复制机制,若主节点在数据未完全同步至从节点时突发宕机,故障转移后将丢失未同步数据。因此,该方案优先保障服务可用性而非数据强一致性,需结合业务场景权衡取舍。

整个体系由两类角色构成:一类是作为决策管控层的哨兵节点,其不存储业务数据,专职执行监控、告警、故障转移等运维操作;另一类是承担数据读写的主从节点,构成实际的数据存储层。哨兵通过主动探测与集群协商机制,实现从异常感知到故障恢复的闭环自动化运维,形成Redis高可用的核心支撑架构。

官方集群方式-分布式集群

RedisCluster采用去中心化设计,通过数据分片与智能路由机制实现分布式存储。其核心是将数据划分为16384个固定槽位(Slot),由集群内各主节点分别承担部分槽区间,形成数据均匀分布。客户端请求可发送至任意节点,若目标Key所属槽位不在当前节点,服务端将返回重定向指令引导客户端直连正确节点,此过程依赖客户端与服务器的协同路由能力。

集群内每个数据分片以主从结构冗余存储,数据写入主节点后异步同步至从节点,支持配置为阻塞同步以提升一致性。节点间通过ClusterBus专用端口进行通信,采用二进制Gossip协议交换集群元数据(如节点状态、槽位映射),实现高效低耗的故障检测、配置同步及故障转移授权。故障恢复机制融合了类似哨兵模式的自动化能力,无需独立部署监控组件。

关键设计要点

  1. 双端口架构:每个节点需开放两个端口,分别处理客户端请求与节点间ClusterBus通信;
  2. 动态扩展:扩容时需通过槽位迁移重新分配数据,需人工介入迁移范围与进度控制;
  3. 数据异步性:主从节点间不强制数据强一致,副本间可能存在短暂状态差异;
  4. 协议约束:仅支持0号数据库,且批量操作受限于Key的槽位分布,需业务层规避跨槽事务。

优势在于无单点瓶颈,支持横向扩展,客户端直连降低延迟,同时内置故障转移保障高可用;局限性体现在运维复杂度高(如扩缩容需手动迁移数据)、功能裁剪(如多库与批量操作限制),且分布式逻辑与存储层深度耦合,难以独立升级优化。该方案适用于对线性扩展需求强烈、可接受最终一致性的海量数据场景

redis如何实现布隆过滤器

首先布隆过滤器(BloomFilter)我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。

相比于我们平时常用的的List、Map、Set等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。

理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。但是通过布隆过滤器可以过去大部分的无效请求

我们可以使用redissonClient.getBloomFilter来取创建布隆过滤器使用tryInit初始化过滤器,主要是告知过滤器大约有多少数据。然后调用add方法添加布隆过滤数据,使用contains判定是否存在值

如何保证redis和MySQL 的双写一致性、缓存一致性

  1. 延时双删策略

延时双删策略主要用于解决在高并发场景下,由于网络延迟、并发控制等原因造成的数据库与缓存数据不一致的问题。当更新数据库时,首先删除对应的缓存项,以确保后续的读请求会从数据库加载最新数据。

但是由于网络延迟或其他不确定性因素,删除缓存与数据库更新之间可能存在时间窗口,导致在这段时间内的读请求从数据库读取数据后写回缓存,新写入的缓存数据可能还未反映出数据库的最新变更。

所以为了解决这个问题,延时双删策略在第一次删除缓存后,设定一段短暂的延迟时间,如几百毫秒,然后在这段延迟时间结束后再次尝试删除缓存。这样做的目的是确保在数据库更新传播到所有节点,并且在缓存中的旧数据彻底过期失效之前,第二次删除操作可以消除缓存中可能存在的旧数据,从而提高数据一致性。

  1. 删除缓存重试机制

删除缓存重试机制是在删除缓存操作失败时,设定一个重试策略,确保缓存最终能被正确删除,以维持与数据库的一致性。

在执行数据库更新操作后,尝试删除关联的缓存项。如果首次删除缓存失败(例如网络波动、缓存服务暂时不可用等情况),系统进入重试逻辑,按照预先设定的策略(如指数退避、固定间隔重试等)进行多次尝试。直到缓存删除成功,或者达到最大重试次数为止。通过这种方式,即使在异常情况下也能尽量保证缓存与数据库的一致性。

  1. 监听并读取biglog异步删除缓存

在数据库发生写操作时,将变更记录在binlog或类似的事务日志中,然后使用一个专门的异步服务或者监听器订阅binlog的变化(比如Canal),一旦检测到有数据更新,便根据binlog中的操作信息定位到受影响的缓存项。讲这些需要更新缓存的数据发送到消息队列,消费者处理消息队列中的事件,异步地删除或更新缓存中的对应数据,确保缓存与数据库保持一致。

延迟双删,删的是什么,为什么要执行两次

1.‌第一次删除:防止"先更新数据库后删缓存"的脏读

若仅采用"先更新数据库再删缓存"策略,在数据库更新完成但缓存未删除的间隙,其他请求可能读取到缓存中的旧数据,导致短暂不一致。

第一次删除在更新数据库前执行,强制后续请求直接访问数据库获取最新数据,避免旧缓存干扰。

2.‌第二次删除(延迟执行):处理并发写入的残留脏数据

延迟目的‌:确保数据库更新操作已完成(通常设置500毫秒等待),避免因系统延迟导致第二次删除过早执行。