redis面试题

redis支持的数据类型

String字符串

格式set key value

String类型是二进制安全的,意识redis的string可以包含任何数据 .比如jpg图片或者序列化的对象.

String类型是redis最基本的数据类型,一个键最大能存储512M

Hash

格式hmset name key 1 value key 2 value 2

redis hash是一个键值(key>value)对集合

redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象

List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序.你可以添加一个元素到列表的头部(左边)或者尾部

格式Ipush name value

在key 对应的list的头部添加字符串

rpush name value

在key对应的list尾部添加字符串元素

Irem name index

key对应list中删除count个1和value相同的元素

格式llen name

Set(集合)

格式sadd name value

Redis的set是String的无序集合

集合是通过哈希表实现的,所以添加删除,查找,复杂度都是O(1)

Zset(sorted set 有序集合)

格式zadd name score value

Redis zset和set一样也是string类型元素的集合,且不允许重复的成员.

不同的是每个元素都会关联一个doubble类型的分数.reeids正是通过分数来为集合中的成员从小到大的排序.

zset的成员是唯一的,但分数却可以重复

什么是redis持久化,有哪几种方式

持久化就是把内存的数据写到硬盘中去,防止服务器宕机了内存数据丢失.

redis提供了俩种持久化方式 RDB和AOF

RDB

rdb是redis Database的缩写

rdb就算是吧数据已定时快照的形式保存在磁盘上,这是默认的持久化方式.

对于RDB来说提供了三种机制save,bgsave ,自动化.我们分别来看一下

  • save:该命令会阻塞当前redis服务器,执行save命令期间,redis不能处理其他命令,直到rdb过程完成位置
  • bgsave:执行该命令时,redis会再后台异步进行快照操作,快照同事还可以响应客户端请求,具体操作是redis进程执行fork操作创建子进程,RFDB持久化由子进程负责,完成后自动结束。阻塞 一般只发生在fork阶段,一般时间很短。基本上redis内部所有RDB操作都是采用bgsave命令

自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置,我们可以去设置:
​
①save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
​
默认如下配置:
​
#表示900 秒内如果至少有 1 个 key 的值变化,则保存save 900 1#表示300 秒内如果至少有 10 个 key 的值变化,则保存save 300 10#表示60 秒内如果至少有 10000 个 key 的值变化,则保存save 60 10000
​
不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。
​
②stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了
​
③rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。
​
④rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
​
⑤dbfilename :设置快照的文件名,默认是 dump.rdb
​
⑥dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。
​
我们可以修改这些配置来实现我们想要的效果。因为第三种方式是配置的,所以我们对前两种进行一个对比:

优势:RDB文件紧凑,全量备份,非常适合用于备份和容灾回复。生成RDB文件的时候,redis主进程会fork一个子进程来处理保存所有工作,主进程1不需要进行任何磁盘IO操作 ,RDB在恢复大数据集时比AOF的恢复速度要快

劣势 :RDB快照是一次全量备份,存储的是内存数据的二进制序列号形式,存储上会非常紧凑。当进行快照,开启的专门负责持久化的子进程会拥有父进程的数据,父进程修改的数据,子进程无法同步,所以在快照持续化期间修改的数据不会被保存,可能丢失数据。

AOF:

Aof是Append-only file缩写,是全量备份

AOF也有三种触发机制

  • 修改同步always:同步持久化每次发生数据变更会被立即记录到磁盘 性能比较差但数据完整性较好
  • 每秒同步everysec:异步操作,每秒记录 最多丢失一秒的数据
  • 不同步no:从不同步

优点

更好的保护数据不丢失,一般aof会隔一秒,通过一个后台线程执行一次fsync操作,最多丢失一秒的数据。AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损

aof日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性误删除的紧急恢复

缺点

对于同一份数据来说,aof日志文件通常比RDB数据快照文件更大

aof开启后,支持的写QPS会比RDB支持的QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次,性能也还是很高的

| 命令 | RDB | AOF |
| - | - | - |
| 启动优先级 | 低 | 高 |
| 体积 | 小 | 大 |
| 恢复速度 | 快 | 慢 |
| 数据安全性 | 丢数据 | 根据策略决定 |
| 轻重 | 重 | 轻 |

简单介绍RESP 协议

redis客户端和服务端之间在TCP协议上采用一种独立名为RESP协议作为通讯的标准方式

在以下几点做出了折中

  • 简单的实现
  • 快速的被计算机解析
  • 简单得可以被人工解析

新的统一协议已在Redis 1.2中引入,但是在Redis 2.0中,这就成为了与Redis服务器通讯的标准方式。在这个统一协议里,发送给Redis服务端的所有参数都是二进制安全的。Redis用不同的回复类型回复命令。它可以从服务器发送的第一个字节开始校验回复类型:

  • 单行回复(单行字符串回复),回复的第一个字节将是“+”
  • 错误消息(单行字符串回复的另外展示形式),回复的第一个字节将是“-”
  • 整型回复(正整形数字回复),回复的第一个字节将是“:”
  • 批量回复(多行字符串回复),回复的第一个字节将是“$”
  • 多个批量回复(数组回复),回复的第一个字节将是“*”

redis如何实现分布式锁

分布式锁其实可以理解为:控制分布式系统有序的对共享资源进行操作 ,通过互斥来保持一致性.举个不太恰当的例子:假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进去看书的人,分布式锁就是保证这个房子一个门,并且只有一个人可以通过,而且们里面有一把钥匙.然后许多人要去看书,可以,排队,第一个人拿着钥匙把门打开进屋看书并且把门锁上,然后第二个人没有钥匙,那就等着,等第一个出来,然后你在拿着钥匙进去,然后就是以此类推

实现原理

互斥性

保证同一时间只有一个客户端可以拿到锁,也就是可以对共享资源进行操作

安全性

只有锁的服务才能有解锁的权限,也就是不能让a加的锁,bcd都可以解锁,如果都能解除那分布式锁就没有意义了

可能出现的情况就是a去查询发现持有锁,就在准备解锁,这时候忽然a持有的锁过期了,然后b去获得锁,因为a锁过期,b拿到锁,这时候a继续执行第二步进行解锁如果不加校验,就将b持有的锁就给删除了

避免出现死锁

出现死锁就会导致后续的服务都拿不到锁,不能再对资源进行任何操作了

保证加锁解锁的原子性

这个其实属于实现分布式锁的问题,假如a用reids实现分布式锁

加锁操作分为俩部 设置key set(key,value)2,给key设置过期时间

假设现在a刚实现set后,程序崩了就导致了没给key设置过期时间就导致key一直存在就发生了死锁

redis实现分布式锁

使用redis命令 set key value NX EX max-lock-time

使用redis命令eval实现解锁

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

如果当前的锁是当前线程持有的锁,就解锁,不然就不操作

NX:只有这个key不存在的时候才会进行操作,if not exits

EX:设置key的过期时间为秒,具体时间由第五个参数决定

上面只是讲了加锁与解锁的操作,事项一下如果在业中去拿锁没有拿到,是应该阻塞一直等待还是直接返回这个问题其实可以写一个重试机制,根据重试次数和重试时间去做一个循环去拿锁,当然这个重试的次数和时间设多少合适,是要根据业务去衡量的

redis如何做异步队列,有什么优缺点

基本u俩种解决方案

自带的PUB,SUB机制

即发布订阅模式,这种模式生产者和消费者是1-M的关系,即一条消息会被多个消费者消费,当一个消费者时既可以看做一个1-1的消息队列,但这种方式并不适合题队列常见.首先,数据可靠性无法保证,redis宕机部分数据甚至网络不稳定都可能带来数据丢失,应该是无法忍受的,其次扩展不灵活,没法通过多加consumer来加快消费端进度,如果前端写入太多,同步会比较慢,数据不同步的状态越久,分享越大.这种方案适合对数据可靠性要求不高的场景

reids的PUS/HPOP机制

利用list结构.比较好的使用模式是,生产者lpush消息,消费者brpop,并设定超时时间,可以减少redis的压力.这种方案相对于第一种方案数据可靠性提高了,只有在redis宕机切数据没有持久化的情况下丢失数据,可以通过AOF和缩短持久化间隔来保证高可靠性,而且也可以通过多个client来提高消费速度.但相对于专业的消息队列来说,该方案消没有状态,且没有ACK机制,消息取出后消费者失败依赖于client记录日志,或者 重新push到队列里面

果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持消息确定-ACK。

但实在太多人使用redis去做去消息队列,redis的作者看不下去,另外基于redis的核心代码,另外实现了一个消息队列disque: antirez/disque

部署、协议等方面都跟redis非常类似,并且支持集群,延迟消息等等。

什么是缓存穿透,如何避免

一般缓存系统,都是按照key去缓存查询,如果不存在对应的value,就去后台数据库查找,一些恶意的请求,会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力.这就叫做缓存穿透.

如何避免?

对查询结果为空的情况也进行缓存,缓存时间设置段一点,或者对该key对应的数据insert了之后清理缓存

对不一定存在的key进行过滤.可以把所有的可能存在key放到一个bitmap中,查询时通过bitmap过滤

什么是缓存雪崩?何如避免?

当服务器重启或者大量缓存在一个时间段失效的时候,会给后端系统带来很大压力.导致系统崩溃

如何避免?

  1. 在缓存失效后,通过加锁或者队列来控制数据库写入缓存的线程数量.比如对某个key只允许一个线程查询数据和写缓存
  2. 做耳机缓存,A1位原始缓存,A2位拷贝缓存,A1失效的时候,可以访问A2,A1缓存失效时间为短期,A2设置长期
  3. 不同的key设置不同的过期时间,让缓存失效的时间尽量均匀

缓存预热

缓存预热就是系统上线后, 将相关的缓存数据直接加载到缓存系统.这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户查询事先被预热的数据

缓存降级

当访问量剧增,服务出现问题或非核心服务影响到核心流程性能时,任然需要保证服务还是可用的,即使是有损服务.系统可以根据一些关键数据自动降级,也可以配置开关实现人工降级.

缓存的目的是最终保证核心服务可用,即使是有损的.而且有些服务是无法降级的(如加入购物车,结算)

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  1. 一般:比如有服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级
  2. 警告:有些服务在一段时间内成功率有波动(比如在95-100之间)可用自动降级或人工降级,并发送警告
  3. 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统承受的最大阈值,此时可以根据情况自动降级或者人工降级
  4. 验证错误:比如因为特殊情况数据错误了,此时需要紧急人工降级

降级的目的,是为了防止redis服务故障,导致数据库一起发生雪崩问题.因此对于不重要的缓存数据,可以采取降级策略,例如一个比较常见的做法就是,redis出现问题,不去数据库 查询,而是直接返回默认值给用户

redis有哪些架构模式

单副本

优点:

  • 架构简单,部署方便。
  • 高性价比:缓存使用时无需备用节点(单实例可用性可以用 supervisor 或 crontab 保证),当然为了满足业务的高可用性,也可以牺牲一个备用节点,但同时刻只有一个实例对外提供服务。
  • 高性能。

缺点:

  • 不保证数据的可靠性。
  • 在缓存使用,进程重启后,数据丢失,即使有备用的节点解决高可用性,但是仍然不能解决缓存预热问题,因此不适用于数据可靠性要求高的业务。
  • 高性能受限于单核 CPU 的处理能力(Redis 是单线程机制),CPU 为主要瓶颈,所以适合操作命令简单,排序、计算较少的场景。也可以考虑用 Memcached 替代。

Redis 单副本,采用单个 Redis 节点部署架构,没有备用节点实时同步数据,不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景。

多副本(主从)

redis多副本,采用主从部署结构,相较于但副本而言最大的特点就是主从实例数据实时同步,并且提供持久化和备份策略。

主从实例部署在不同的物理服务器上,根据公司基础环境配置,可以实现同时对外提供服务和读写分离策略

优点

  • 高可靠性:一方面采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据库误操作和数据异常丢失的问题
  • 读写分离策略:从节点可以扩展主库点的读能力,有效应对大并发量的读操作。

缺点

  • 故障恢复复杂,如果没有redis HA系统,当主节点初夏你故障时,需要手动将一个从节点晋升位主节点,同事需要通知业务方变更配置,并且需要让其他从库去复制新主库节点,整个过程需要人为干预,比较繁琐
  • 主库的写能力受到单机限制,可以考虑分片。
  • 主库的存储能力受到单机的限制可以考虑pika
  • 原生复制的弊端是在早期版本会比较突出,redis复制中断后,slave会发起psync,此时如果同步不成功,则会进行全量同步,主库执行全量备份可能会造成毫秒级或秒级别的卡顿。
    又由于 COW 机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘 IO 和 CPU(压缩)资源消耗;发送数 GB 大小的备份文件导致服务器出口带宽暴增,阻塞请求,建议升级到最新版本。

redis Sentinel(哨兵)

Redis Setinel是社区版本推出的原生高可用方案,其部署架构主要包括两部分:redis Sentinel集群和redis数据集群

其中redis Sentinel集群是由若干个节点组成的分布式集群,可以实现故障发现,故障自动转移,配置中心和客户端通知.reids Setinel的节点要满足 2N+1的奇数个

redis sentinel有以下三个特效

  1. 监控Sentinel会不断监察你的主服务器和从服务器运作是否正常
  2. 提醒当被监控的某个redis服务出现问题时,sentinel可以通过API向管理员或者其他应用程序发送通知
  3. 自动故障迁移:当一个主服务器不能正常工作的时候,Sentinel会开始一次自动故障迁移操作

优点

  • redis Sentinel集群部署简单
  • 能够解决redis主从模式下的高可用切换问题
  • 很方便实现redis数据节点的扩展,轻松突破redis自身单线程瓶颈,可极大满足redis大容量或高性能需求
  • 可以实现一套Sentinel 监控一组redis数据节点或者多组数据节点

缺点

  • 部署相对Redis主从模式复杂,原理更繁琐
  • 资源浪费,redis数据节点中slave节点作为备份节点不提供服务
  • redis Sentinel主要是针对redis数据节点中的主节点高可用更huan,对redis的数据节点作失败判定分为主管下线和客观下线俩种,对节点做主观下线操作,并不执行故障
  • 没有解决 master 写的压力,切换需要时间,会丢失数据

集群

redis Cluster是社区版推出的redis分布式集群解决方案,主要解决redis分布式方面的需求,比如当遇到单机内存,并发和流量瓶颈的时候,redis Cluster能很好的起到均衡负载的目的

redis Cluster集群节点最小配置6个节点以上(三主三从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用.

redis cluster采用虚拟槽分区,很久哈希函数映射到0-16383个槽内,每个节点辅助维护一部分槽,以及槽锁映射的键值的数据

优点

  • 无中心架构
  • 按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布
  • 可扩展性:可扩展到1000多个节点,节点可动态添加删除
  • 高可用性:部分节点不可用时,集群仍可用,通过增加slave做standb数据副本,实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色提升
  • 降低运维成本,提高系统的扩展性可用性

缺点

  • 节点会因为某些原因发生阻塞
  • 数据通过异步复制,不报账数据的强一致性
  • 多个业务使用一套集群的时候,五分根据统计分区冷热数据,资源隔离性差,容易出现相互影响的情况.

哈希槽

proxy集群

Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。

特点:1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins

  1. 支持失败节点自动删除
  2. 后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致

优点:

  • 高可靠性、高可用性;
  • 自主可控性高;
  • 贴合业务实际需求,可缩性好,兼容性好。

缺点:

  • 实现复杂,开发成本高;
  • 需要建立配套的周边设施,如监控,域名服务,存储元数据信息的数据库等;
  • 维护成本高。

redis是单线程还是多线程

这个问题要从多个方面回答

redis基于reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器,它的组成结构为4部分:多个套接字,IO多录复用程序,文件事件派发器,事件处理器.因为文件事件分排气队列的消费是单线程的,所以redis才叫做单线程模型

reids不仅是单线程

redis单线程模型,指的是redis命令的核心模块是单线程的,而不是整个redis实例就是一个线程,redis其他模块还有各自的线程的

Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。

一般来说redis瓶颈不在CPU,而在内存和网络,如果要使用CPU多核,可以搭建多个Redis实例来解决

其实Redis4.0开始有多线程的概念了,比如Redis通过多线程方式在后台删除对象,以及通过Redis模块实现的阻塞等

但在redis6.0版本中引入了网络处理多线程功能(默认不开启),每次有请求会开一个io子线程

io多路复用

基于Reactor设计模式实现的Io多路复用

  1. IO多路复用程序监听多个套接字(Socket)
  2. 其次,当套接字进行读写操作的时候,IO多路复用程序分发到各个不同时间处理器上
  3. 然后事件处理器处理后返回给IO多路复用线程
  4. 最后IO多路复用程序返回结果给套接字

6.0之前

6.0之后

R4hYx1.md.png

过期键的删除策略

redis是kv数据库,redis的国企策略就是指当redis中缓存的key过期了redis如何处理

过期策略通常有以下三种

  1. 定时过期:每个设置过期时间的key都需要创建一个定时器,到国企时间就会立即清除.该策略可以立即清除过期的数据,对内存友好;但会占用大量的CPU资源去处理过期的数据
  2. 惰性过期:当访问一个key的时候,才会判断该key是否已经过期,过期就清除,该策略可以最大地节省CPU资源,却对内存不友好.极端情况可能出现key没有被再次访问从而不清除,大量占用内存
  3. 定期扫描:每隔一段时间,会扫描一定数量对数据库的expores字典中的key,并清除其中过期的key.该策略是一个这种方案.

redis中同时使用了惰性过期和定期过期这俩种过期策略

如何保证redis中的数据都是热点数据

redis内存书记集大小上升到一定大小的时候,就会施行数据淘汰策略

你知道有哪些Redis分区实现方案?

  • 客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
  • 代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
  • 查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

如何保证缓存与数据库双写时的数据一致性

只要用缓存,就看会设计到缓存与数据存储双鞋,只要是双写,就一定会有数据一致性问题,那么如何解决一致性问题

一致性就是保持数据的一致,在分布式系统中,可以理解为多个节点中数据值是一致的。

  • 强一致性:这种一致性最符合用户直觉,它要求系统写入什么,读出来也会是什么,用户体验很好,但实现起来往往对系统的性能影响很大
  • 弱一致性:这种一致性级别约束了在系统写入成功后,不承诺立即可以读到写入的这个值,也不承诺多久之后数据能达到一致,但会尽可能的保证到某个时间级别(比如秒级),数据能达到
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能达到的一个数据一致的状态。

三个经典的缓存模式

  • Cache-Aside Pattern
  • Read-Through/Write through
  • Write behind

Cache-Aside Pattern 旁路缓存模式

旁路缓存模式 ,它的提出是为了尽可能地好解决缓存与数据库不一致的问题

读的时候,先读缓存,如果缓存命中直接返回数据,缓存如果没有命中,就去读数据库,从数据库取出数据,放入缓存然后响应

更新的时候,先更新数据库,然后再删除缓存。

Read-Through/Write-Through 读写穿透

服务器把缓存作为主要数据存储.应用跟缓存交互,都是通过抽象层完成的.

Read-Through:从缓存读取数据,读到直接返回如果读不到就从数据库加载,写入缓存后,再返回响应

Write-Through:当发生写请求时,由抽象层完成数据的更新,

Write Behind(异步缓存写入)

跟Read-Through/Write-Through有相似的地方,都是由cache provider来负责缓存和数据库的读写.它又有个很大的不同read/write Through是同步更新缓存和数据的,Write Behind则是只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库.

这种方式下,缓存和数据库的一致性 不强,对一致性要求高的系统

操作的时候,删除缓存,还是更新缓存

一般业务场景,我们使用的就是cache-aside模式

有人会疑惑,cache-aside在写入请求的时候,为什么是删除缓存而不是更新缓存呢

线程A发起一个写操作,第一步先更新数据库

线程B在发起一个操作,第二部更新了数据库

由于网络原因,线程B先更新了缓存

然后线程A更新缓存

这时候,缓存保存的是A的数据(老数据),数据库保存的是B数据(新数据),数据不一致了,脏数据出现了.如果是删除缓存取代更新缓存则不会出现脏数据的问题.更新缓存对于删除缓存还有俩点优势

如果写入的缓存值,是经过复杂计算猜得到的话.更新缓存频率高的话,就浪费性能,在数据库场景多,读数据少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能, (实际上,写多的场景,用缓存也不是很划算)

双写的情况下,线操作数据库还是先操作缓存

Cache-Aside缓存模式中,在写入请求的时候,为什么是先操作数据库,而不是先操作缓存呢

  1. 线程A发起一个写操作,第一步del cache
  2. 此时线程B发起一个读操作,cashe miss
  3. 线程B继续读DB,读出来一个老数据
  4. 然后线程B把老数据设置入cache
  5. 线程A写入DB最新的数据

延迟双删策略

先删除缓存,再更新数据库,休眠一会再次删除缓存

这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。

延时是确保 修改数据库 -> 清空缓存前,其他事务的更改缓存操作已经执行完。

删除缓存重试机制

不管是延迟双删还是Cache-Aside的先操作数据再删除缓存,如果第二部的删除缓存失败,会导致脏数据,所以可以引入删除缓存重试机制

请求更新数据库成功,缓存删除失败,把删除失败的key放到消息队列,消费消息队列的消息,获取要删除的key

读取binlog异步删除缓存

读取biglog异步删除缓存

重试删除缓存机制还可以,就是会造成好多业务代码的入侵.其实还可以通过数据库的binlog来异步淘汰key

以mysql为例 可以使用阿里的canal 讲binlog日志发送到MQ队列里面,然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性.

缓存某些原因,删除失败可以把删除

Last modification:November 17, 2023
如果觉得我的文章对你有用,请随意赞赏