redis面试重点
认识 Redis
什么是 Redis?
Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快。
Redis 提供了多种数据类型来支持不同的业务场景,Redis 提供了多种数据类型来支持不同的业务场景,String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合),并且对数据类型的操作都是原子性的,因为执行命令由单线程负责的,不存在并发竞争的问题
Redis 还支持事务 、持久化、Lua 脚本、多种集群方案
Redis对于⼤ᰁ的请求,是怎样处理的
- Redis是⼀个单线程程序,也就说同⼀时刻它只能处理⼀个客户端请求;
 - Redis是通过IO多路复⽤(select,epoll,kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求
 
为什么用 Redis 作为 MySQL 的缓存?
Redis 具备高性能

访问 MySQL从硬盘上读取的太慢,该用户访问的数据缓存在 Redis,操作 Redis 缓存就是直接操作内存
Redis 具备高并发
Redis 的 QPS(Query Per Second,每秒钟处理完请求的次数)轻松破 10w,把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
Redis 和 Memcached 有什么区别?
- 
都是基于内存的数据库,一般都用来当做缓存使用
 - 
Memcached 只支持最简单的 key-value 数据类型
 - 
Memcached 没有持久化功能
 - 
Memcached 没有原生的集群模式
 - 
Memcached是多线程
 - 
Value 值⼤⼩不同:Redis 最⼤可以达到 512MB;Memcached 只有 1MB
 
Redis 数据结构
Redis 数据类型以及使用场景分别是什么?
String
- 
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
 - 
常规key-value缓存应⽤; 常规计数:微博数,粉丝数等
 
Hash
- 
Hash 是⼀个 string 类型的 field 和 value 的映射表,hash 特别适合⽤于存储对象,
 - 
后续操作的时候,你可以直接仅 仅修改这个对象中的某个字段的值。 ⽐如我们可以Hash数据结构来存储⽤户信息,商品信息等‘
 
List
- 
list 就是链表,Redis list 的应⽤场景⾮常多,也是Redis最᯿要的数据结构之⼀,⽐如微博的
 - 
关注列表,粉丝列表, 消息列表等功能都可以⽤Redis的 list 结构来实现
 
Set
- 
set 对外提供的功能与list类似是⼀个列表的功能,特殊之处在于 set 是可以⾃动排᯿ 的。
 - 
基于 set轻易实现交集、并集、差集的操作,共同关注、共同粉丝、共同喜好等
 
Sorted Set
- 
和set相⽐,sorted set增加了⼀个权᯿参数score,使得集合中的元素能够按score进⾏有序排列。
 - 
实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜
 
五种常见的 Redis 数据类型是怎么实现?
String
- 
SDS(简单动态字符串),不同于c,SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,API 都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组
 - 
SDS 不仅可以保存文本数据,还可以保存二进制数据,保存图片、音频、视频、压缩文件这样的二进制数据
 - 
SDS 获取字符串长度的时间复杂度是 O(1),用 len 属性记录了字符串长度, C 语言的字符串并不记录自身长度
 
List
- 列表的元素个数小于 512 个压缩列表,所有值小于 64 字节,不满足上面的条件,Redis 会使用双向链表
 
Hash
列表的元素个数小于 512 个压缩列表,所有值小于 64 字节,如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的底层数据结构。
Set集合
元素个数小于 512整数集合,不满足上面条件,则 Redis 使用哈希表
ZSet有序集合
小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表,不满足上面的条件,Redis 会使用跳表
Redis 线程模型
Redis 是单线程吗?
Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的
Redis 程序并不是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)
Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。
Redis 单线程模式是怎样的?
单线程⽤⽂件事件处理器,采⽤ IO 多路复⽤机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进⾏处理

- 
多个 socket
 - 
IO多路复⽤程序
 - 
⽂件事件分派器
 - 
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器
 
使⽤ I/O 多路复⽤程序来同时监听多个套接字, 并根据套接字⽬前执⾏的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执⾏连接应答(accept)、读取(read)、写⼊(write)、关闭(close)等操作时, 与操作相对应的⽂件事件就会产⽣, 这时⽂件事件处理器就会调⽤套接字之前关联好的事件处理器来处理这些事件。
多个 socket 可能会并发产⽣不同的操作,每个操作对应不同的⽂件事件,但是 IO 多路复⽤程序会监听多个 socket,会将 socket 产⽣的事件放⼊队列中排队,事件分派器每次从队列中取出⼀个事件,把该事件交给对应的事件处理器进⾏处理。
⼀句话总结就是:“I/O 多路复⽤程序负责监听多个套接字, 并向⽂件事件分派器传送那些产⽣了事件的套接字。”
Redis 为什么是单线程的⽽不采⽤多线程⽅案?
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的⼤⼩或者⽹络带宽。既然单线程容易实现,⽽且CPU不会成为瓶颈,那就顺理成章地采⽤单线程的⽅案了(毕竟采⽤多线程会有很多麻烦!)
单线程的Redis为什么这么快
- 
1、Redis的全部操作都是纯内存的操作;
 - 
2、Redis采⽤单线程,有效避免了频繁的上下⽂切换;
 - 
3,采⽤了⾮阻塞I/O多路复⽤机制
 
缓存中常说的热点数据和冷数据是什么?
热数据就是访问次数多的数据,冷数据就是访问很少或者从不访问的数据。热点数据,缓存才有价值
Redis 持久化
Redis 如何实现数据不丢失?
Redis 的读写操作都是在内存中,所以 Redis 性能才会高,但是当 Redis 重启后,内存中的数据就会丢失,那为了保证内存中的数据不会丢失,Redis 实现了数据持久化的机制,这个机制会把数据存储到磁盘,这样在 Redis 重启就能够从磁盘中恢复原有的数据。
实现:单ᇿ创建fork()⼀个⼦进程,将当前⽗进程的数据库数据复制到⼦进程的内存中,然后由⼦进程写⼊到临时⽂件中,持久化的过程结束了,再⽤这个临时⽂件替换上次的快照⽂件,然后⼦进程退出,内存释放
- 
AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
 - 
RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
 - 
混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;
 
RDB与AOF的选择之惑

对数据非常敏感,建议使用默认的AOF持久化方案
- 
AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出 现问题时,最多丢失0-1秒内的数据。
 - 
注意:由于AOF文件存储体积较大,且恢复速度较慢
 
数据呈现阶段有效性,建议使用RDB持久化方案
- 
数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段 点数据恢复通常采用RDB方案
 - 
注意:利用RDB实现紧凑的数据持久化会使Redis降的很低,慎重总结:
 
双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量
AOF 日志是如何实现的?
Redis 在执行完一条写操作命令后,就会把该命令以追加的方式写入到一个文件里,然后Redis 重启时,会读取该文件记录的命令,然后逐一执行命令的方式来进行数据恢复。

为什么先执行命令,再把数据写入日志呢?
- 
避免额外的检查开销:因为如果先将写操作命令记录到 AOF 日志里,再执行该命令的话,如果当前的命令语法有问题,那么如果不进行命令语法检查,该错误的命令记录到 AOF 日志里后,Redis 在使用日志恢复数据时,就可能会出错。
 - 
不会阻塞当前写操作命令的执行:因为当写操作命令执行成功后,才会将命令记录到 AOF 日志。
 
AOF 写回策略有几种?

AOF 日志过大,会触发什么机制?
提供了 AOF 重写机制,当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。
读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件,[历史」命令,没有作用了。这样一来,一个键值对在重写日志中只用一条命令就行了。
重写 AOF 日志的过程是怎样的?
- AOF᯿写可以产⽣⼀个新的AOF⽂件,这个新的AOF⽂件和原有的AOF⽂件所保存的数据库状
 - 态⼀样,但体积更⼩。
 - AOF᯿写是⼀个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序⽆须对现
 - 有AOF⽂件进⾏任伺读 ⼊、分析或者写⼊操作。
 - 在执⾏ BGREWRITEAOF 命令时,Redis 服务器会维护⼀个 AOF 重写缓冲区,该缓冲区会在
 - ⼦进程创建新AOF⽂件期间,记录服务器执⾏的所有写命令。当⼦进程完成创建新AOF⽂件的⼯作之后,服务器会将重写缓冲区中的所有内容 追加到新AOF⽂件的末尾,使得新旧两个AOF⽂件所保存的数据库状态⼀致。最后,服务器⽤新的AOF⽂件替换旧的 AOF⽂件,以此来完成AOF⽂件᯿写操作。
 
RDB 快照是如何实现的呢?
RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据,edis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。
RDB 做快照时会阻塞线程吗?
- 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程;
 - 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞;
 
Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。所以执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多
RDB 在执行快照的时候,数据能修改吗?

执行 bgsave 过程中,Redis 依然可以继续处理操作命令的,也就是数据是能被修改的,关键的技术就在于写时复制技术(Copy-On-Write, COW)。
如果主线程执行写操作,则被修改的数据会复制一份副本,然后 bgsave 子进程会把该副本数据写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据。
为什么会有混合持久化?
Redis 集群
Redis 如何实现服务高可用?
要想设计一个高可用的 Redis 服务,一定要从 Redis 的多服务节点来考虑,比如 Redis 的主从复制、哨兵模式、切片集群。
主从复制

- 
主从复制是 Redis 高可用服务的最基础的保证,实现方案就是将从前的一台 Redis 服务器,同步数据到多台从 Redis 服务器上,即一主多从的模式,且主从服务器之间采用的是「读写分离」的方式。
 - 
主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。
 
注意,主从服务器之间的命令复制是异步进行的。
具体来说,在主从服务器命令传播阶段,主服务器收到新的写命令后,会发送给从服务器。但是,主服务器并不会等到从服务器实际执行完命令后,再把结果返回给客户端,而是主服务器自己在本地执行完命令后,就会向客户端返回结果了。如果从服务器还没有执行主服务器同步过来的命令,主从服务器间的数据就不一致了。
所以,无法实现强一致性保证(主从数据时时刻刻保持一致),数据不一致是难以避免的。
哨兵模式

在使用 Redis 主从服务的时候,会有一个问题,就是当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复。
为了解决这个问题,Redis 增加了哨兵模式(Redis Sentinel),因为哨兵模式做到了可以监控主从服务器,并且提供主从节点故障转移的功能。
切片集群模式
当 Redis 缓存数据量大到一台服务器无法缓存时,就需要使用 Redis 切片集群(Redis Cluster )方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis 服务的读写性能。
Redis Cluster 方案采用哈希槽(Hash Slot),来处理数据和节点之间的映射关系。在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中,具体执行过程分为两大步:
- 
根据键值对的 key,按照 CRC16 算法 (opens new window)计算一个 16 bit 的值。
 - 
再用 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
 
接下来的问题就是,这些哈希槽怎么被映射到具体的 Redis 节点上的呢?有两种方案:
- 
平均分配: 在使用 cluster create 命令创建 Redis 集群时,Redis 会自动把所有哈希槽平均分布到集群节点上。比如集群中有 9 个节点,则每个节点上槽的个数为 16384/9 个。
 - 
手动分配: 可以使用 cluster meet 命令手动建立节点间的连接,组成集群,再使用 cluster addslots 命令,指定每个节点上的哈希槽个数。
 
为了方便你的理解,我通过一张图来解释数据、哈希槽,以及节点三者的映射分布关系。

集群脑裂导致数据丢失怎么办?
什么是脑裂?
Redis 过期删除与内存淘汰
Redis 使用的过期删除策略是什么?
Redis中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置⼀个过期时间。
作为⼀个缓存数据库, 这是⾮常实⽤的,⽐如⼀些 token 或者登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理⽅式,⼀般都是⾃⼰判断过期,这样⽆疑会严᯿影响项⽬性能。
我们 set key 的时候,都可以给⼀个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间,主要可采⽤定期删除和惰性删除两种⽅案
每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到一个过期字典(expires dict)中,也就是说「过期字典」保存了数据库中所有 key 的过期时间。
当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:
- 
如果不在,则正常读取键值;
 - 
如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。
 
什么是惰性删除策略?
什么是惰性删除策略?

不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。
惰性删除策略的优点:
- 因为每次访问时,才会检查 key 是否过期,所以此策略只会使用很少的系统资源,因此,惰性删除策略对 CPU 时间最友好。
 
惰性删除策略的缺点:
- 如果一个 key 已经过期,而这个 key 又仍然保留在数据库中,那么只要这个过期 key 一直没有被访问,它所占用的内存就不会释放,造成了一定的内存空间浪费。所以,惰性删除策略对内存不友好。
 
什么是定期删除策略?
定期删除策略的做法是,每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。

如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查,则继续重复步骤 1
定期删除策略的优点:
通过限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用。
定期删除策略的缺点:
难以确定删除操作执行的时长和频率。如果执行的太频繁,就会对 CPU 不友好;如果执行的太少,那又和惰性删除一样了,过期 key 占用的内存不会及时得到释放。
可以看到,惰性删除策略和定期删除策略都有各自的优点,所以 Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
Redis 持久化时,对过期键会如何处理的?
Redis 持久化文件有两种格式:RDB(Redis Database)和 AOF(Append Only File)
RDB 文件分为两个阶段,RDB 文件生成阶段和加载阶段。
RDB 文件生成阶段:从内存状态持久化成 RDB(文件)的时候,会对 key 进行过期检查,过期的键「不会」被保存到新的 RDB 文件中,因此 Redis 中的过期键不会对生成新 RDB 文件产生任何影响。
RDB 加载阶段:RDB 加载阶段时,要看服务器是主服务器还是从服务器,分别对应以下两种情况:
- 
如果 Redis 是「主服务器」运行模式的话,在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键「不会」被载入到数据库中。所以过期键不会对载入 RDB 文件的主服务器造成影响;
 - 
如果 Redis 是「从服务器」运行模式的话,在载入 RDB 文件时,不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。
 
Redis 主从模式中,对过期键会如何处理?
当 Redis 运行在主从模式下时,从库不会进行过期扫描,从库对过期的处理是被动的。也就是即使从库中的 key 过期了,如果有客户端访问从库时,依然可以得到 key 对应的值,像未过期的键值对一样返回。
从库的过期键处理依靠主服务器控制,主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
Redis 内存满了,会发生什么?
在 Redis 的运行内存达到了某个阀值,就会触发内存淘汰机制,这个阀值就是我们设置的最大运行内存,此值在 Redis 的配置文件中可以找到,配置项为 maxmemory。
Redis 内存淘汰策略有哪些?
不进行数据淘汰的策略
noeviction(Redis3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。
进行数据淘汰的策略
「在设置了过期时间的数据中进行淘汰」「在所有数据范围内进行淘汰」
- 
最久未使用的键值
 - 
最少使用的键值
 
LRU 算法和 LFU 算法有什么区别?
什么是 LRU 算法?
LRU 全称是 Least Recently Used 翻译为最近最少使用,会选择淘汰最近最少使用的数据。
传统 LRU 算法的实现是基于「链表」结构,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可,因为链表尾部的元素就代表最久未被使用的元素。
Redis 并没有使用这样的方式实现 LRU 算法,因为传统的 LRU 算法存在两个问题:
- 
需要用链表管理所有的缓存数据,这会带来额外的空间开销;
 - 
当有数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。
 
Redis 是如何实现 LRU 算法的?
Redis 实现的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间。
当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个。
Redis 实现的 LRU 算法的优点:
- 
不用为所有的数据维护一个大链表,节省了空间占用;
 - 
不用在每次数据访问时都移动链表项,提升了缓存的性能;
 
但是 LRU 算法有一个问题,无法解决缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染
什么是 LFU 算法?
LFU 全称是 Least Frequently Used 翻译为最近最不常用的,LFU 算法是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
所以, LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题,相比于 LRU 算法也更合理一些。
Redis 是如何实现 LFU 算法的?
Redis 缓存设计
如何避免缓存雪崩、缓存击穿、缓存穿透?
如何避免缓存雪崩?
通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

那么,当大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。
对于缓存雪崩问题,我们可以采用两种方案解决。
- 
将缓存失效时间随机打散: 我们可以在原有的失效时间基础上增加一个随机值(比如 1 到 10 分钟)这样每个缓存的过期时间都不重复了,也就降低了缓存集体失效的概率。
 - 
设置缓存不过期: 我们可以通过后台服务来更新缓存数据,从而避免因为缓存失效造成的缓存雪崩,也可以在一定程度上避免缓存并发问题。
 
如何避免缓存击穿?
我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。 应对缓存击穿可以采取前面说到两种方案:
- 
互斥锁方案(Redis 中使用 setNX 方法设置一个状态位,表示这是一种锁定状态),保证同一时间只有一个业务线程请求缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
 - 
不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;
 
如何避免缓存穿透?
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

应对缓存穿透的方案,常见的方案有、
非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
设置空值或者默认值:当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
如何设计一个缓存策略,可以动态缓存热点数据呢?
由于数据存储受限,系统并不是将所有数据都需要存放到缓存中的,而只是将其中一部分热点数据缓存起来,所以我们要设计一个热点数据动态缓存的策略。
热点数据动态缓存的策略总体思路:通过数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据。
以电商平台场景中的例子,现在要求只缓存用户经常访问的 Top 1000 的商品。具体细节如下:
- 
先通过缓存系统做一个排序队列(比如存放 1000 个商品),系统会根据商品的访问时间,更新队列信息,越是最近访问的商品排名越靠前;
 - 
同时系统会定期过滤掉队列中排名最后的 200 个商品,然后再从数据库中随机读取出 200 个商品加入队列中;
 - 
这样当请求每次到达的时候,会先从队列中获取商品 ID,如果命中,就根据 ID 再从另一个缓存数据结构中读取实际的商品信息,并返回。
 
在 Redis 中可以用 zadd 方法和 zrange 方法来完成排序队列和获取 200 个商品的操作。
说说常见的缓存更新策略?
实际开发中,Redis 和 MySQL 的更新策略用的是 Cache Aside,另外两种策略应用不了。
Cache Aside(旁路缓存)策略
Cache Aside(旁路缓存)策略是最常用的,应用程序直接与「数据库、缓存」交互,并负责对缓存的维护,该策略又可以细分为「读策略」和「写策略」。

写策略的步骤:
- 先更新数据库中的数据,再删除缓存中的数据。
 
读策略的步骤:
- 
如果读取的数据命中了缓存,则直接返回数据;
 - 
如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户
 
为什么「先更新数据库再删除缓存」不会有数据不一致的问题?
假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存中。

最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。 从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。
因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。而一旦请求 A 早于请求 B 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。
基于特定条件的事务执行——分布式锁
setnx 设置一个公共锁
利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
- 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
 - 对于返回设置失败的,不具有控制权,排队或等待
 - 操作完毕通过del操作释放锁
 
                    
                
                
            
        
浙公网安备 33010602011771号