【Redis】Redis常见面试题(3)
文章目录
- 【Redis】Redis常见面试题(3)
- 1. 特性&应用场景
- 1.1 Redis能实现什么功能
- 1.2 Redis支持分布式的原理
- 1.3 为什么Redis这么快
- 1.4 Redis实现分布式锁
- 1.5 Redis作为缓存
- 2. 数据类型
- 2.1 Redis常用的数据类型有哪些
- 2.2 有序列表的底层是如何实现的
- 2.3 什么是跳跃表
- 2.4 说一下跳跃表的查询流程
- 2.5 跳跃表的随机层数
- 2.6 跳跃表添加节点的流程
- 3. 持久化
- 3.1 Redis怎么保证数据不丢失
- 3.2 RDB和AOF有什么区别
- 3.2.1 AOF 持久化
- 3.2.2 RDB 持久化
- 3.2.3 AOF 持久化策略有哪些
- 4. 数据淘汰
- 4.1 Redis键值过期之后会直接被删除吗
- 4.2 Redis如何淘汰过期键值
- 4.2.1 过期删淘汰策略
- 4.2.2 定期删除得执行流程
- 4.3 Redis如何解决内存不够用问题
- 4.3.1 Redis内存用完会怎么样
- 4.3.1 内存淘汰策略
- 4.3.2 LRU和LFU有什么区别
- 4.4 过期淘汰策略和内存淘汰策略有何不同
- 5. 事务
- 5.1 Redis的事务的使用
- 5.2 Redis和MySQL事务地最大区别是啥
- 6. 多机部署
- 6.1 Redis多机部署有几种方式
- 6.2 主从、哨兵、集群模式有什么区别
【Redis】Redis常见面试题(3)
本章重点结构:
1. 特性&应用场景
Redis本质上就是一个数据库,MySQL是关系型数据库,而Redis是文档型数据库,或者说是非关系型数据库
- MySQL我们使用的很多了,几个实体类之间是有关系,例如用户表与文章表,作者与作者的文章的关系等等…
- 而Redis没有那么花里胡哨,它是动态的,它将一些数据存在一块儿,想存啥咋存都行,比较灵活
- 准确的来说是,键值对型的文档型的非关系型的数据库
而它们之间没有必要过多比较,因为它们分别适合不同的场景,在不同的场景下它们都有得天独厚的优势
1.1 Redis能实现什么功能
Redis的常见使用场景有以下几个:
- 会话存储,保存用户的登录信息
- 存储普通缓存,详情页等数据的缓存信息存储
- 实现分布式锁,Redis可以非常方便的实现微服务下的分布式锁,Redis天然就支持分布式服务
- 简单的消息队列,Redis自身提供的发布订阅模式,可以用来实现简单的消息队列
1.2 Redis支持分布式的原理
就以会话存储为例,原本我们用的是应用程序的Session存储
假设我们的分布式环境,对于一些请求:
本地内存:
- 要达到我们要的效果,要实现登录信息记录我们就需要每个服务器的本地内存都要存储用户信息
远程Redis:
- 共享信息的形式存储数据,这样登录信息记录的问题就解决了
- 对于Redis,无论是单机呢还是多机部署还是集群什么的,它们只是这个整体的分片存储罢了,Redis这个整体的数据是对外共享的!
- 也就是说,连接Redis的不同分片访问的是同一个Redis,可以获取同一个Redis来自不同分片的数据!
1.3 为什么Redis这么快
-
最主要的原因,数据存取发生在内存中,内存IO的速度非常快
-
**单线程模型:**Redis使用单线程模型来处理客户端请求。可能你会觉得没有多线程并发处理请求,怎么会快呢?
- 但其实,这样涉及可以有效避免多线程的竞争条件和锁开销,单线程就不需要考虑线程安全问题。Redis通过非阻塞的方式处理多个客户端请求,每个请求的执行时间极短,所以在单线程下,Redis也足够处理大量的并发请求!
- 所以,尽管操作Redis的应用的多线程,或者操作Redis的多个应用并发,在Redis中处理请求仍然是单线程~
对比:
Spring MVC框架帮助我们接受多个并发的请求,框架帮我们实现多线程处理这些请求(执行接口方法)
Redis接受多个并发的请求,内部还是以单线程模式进行
-
Redis数据结构的选取非常合适,采用全局的链式哈希表进行存储每一个key-value,同时,Redis对于哈希扩容的处理也很高效,查询时间非常快,是趋近于O(1)的,并且当哈希桶使用个数比较多时,会进行哈希表的扩容,避免时间复杂度的增高
- Redis也提供了多种高效的数据结构~
-
非阻塞I/O :Redis使用了非阻塞 I/O模型,这意味着当进行磁盘读写或者网络通信的时候,Redis并不是等待数据返回,而是继续处理其他请求。这样可以充分利用CPU的时间,提高整体的吞吐量
网络资料
后来一些功能用到了点cpp~
1.4 Redis实现分布式锁
博文链接:【Redis】Redis实现分布式锁_s:103的博客-CSDN博客
1.5 Redis作为缓存
博文链接:【Redis】Redis作为缓存_s:103的博客-CSDN博客
2. 数据类型
2.1 Redis常用的数据类型有哪些
- String(字符串),最常用,Session可序列化后以String类型进行存储
- List(列表类型)
- Hash(哈希表)
- Set(集合)
- Sorted Set(有序集合)
Sorted Set也被称为ZSet,原因是有序列表的底层数据库实现是zskiplist,所以也被称之为ZSet
具体使用在专门的章节讲解,不在这里讲解
2.2 有序列表的底层是如何实现的
当数据比较少的时候,有序集合是压缩列表 ziplist(字节数组,比较节省空间)实现的
- 反之则为跳跃表 skiplist实现的(数据比较多,压缩列表效率不高)
使用压缩列表存储必须满足的两个条件:
- 有序集合保存的元素个数要小于128个
- 有序集合保存的所有成员的长度都必须小于64个字节
如果不能同时满足这了两个条件,有序集合将会使用跳跃表skiplist进行存储!
2.3 什么是跳跃表
由多个链表来实现的数据结构
- 下一层的链表包含上一层链表的节点,最底层节点最多,包含全部数据,最顶层节点最少,含数据也就少~
为什么要用跳跃表呢,以及跳跃表为啥跳跃除了和它的一些链表的节点有跳跃性(跳着存)的还有别的原因
- 就是跳跃表的查询流程
2.4 说一下跳跃表的查询流程
例如刚才的那个跳跃表
- 我要查询17:
- 我要查询7:
- 我要查询6:
跳跃的之前,会进行判断,以此决定往哪跳:
- 如果下一个节点为null,(其实这里是没有下一个节点的意思),往下跳
- 如果下一个节点比要找的大,往下跳
- 如果下一个节点小于等于我们要找的,往右跳
所谓跳跃表,就是在普通链表的基础上增加了多层索引链表,这样在查找时就可以通过在上下不同层级的索引链表间跳跃,以达到快速查找的目的,所以跳跃表是可以降低时间复杂度的到 O(log2N),感兴趣的同学可以去试着证明以下~
索引就相当于,你要在本文中找到“跳跃表的查询流程”的讲解,我们通过索引:
2.5 跳跃表的随机层数
跳跃表,因为redis考虑性能,维护一个规律的成本比较大,所以采取一种随机的策略,就是层数的生成以至于影响节点跳跃的跨度,是随机的,不过并不是等概率的随机,而是生成越高层数的概率越低
- 在添加节点的时候,会生成一个随机层数(大于等于当前层数)
- 源码里最高规定是32层,这个概率很低
维护一个规律的成本比较大,就比如没三个节点,取其中一个节点,生成一层索引,那么添加操作的时候,可能会破坏之前的结构,导致每一层都要因为维护这个规律而去更新
2.6 跳跃表添加节点的流程
比如刚才的跳跃表,我要添加一个节点,6,随机生成了一个层数:5
生成5层链表:
从下到上插入节点:
这里的1节点其实是头节点,头节点实际上不是有效数据,这里只是个便于理解的例子,具体要复杂的多~
3. 持久化
3.1 Redis怎么保证数据不丢失
虽然Redis的读写都是在内存中,因为这样操作新能最高,但是Redis重启,内存中的数据就会丢失
为了保证数据不丢失,我们需要将内存中的数据存储到磁盘,以便Redis重启时能够从磁盘中恢复原有的数据,而整个过程就叫做Redis持久化
也就是说 Redis使用了“持久化”技术来保证Redis中的数据不丢失
Redis持久化有以下3种实现方式:
-
快照方式(RDB,Redis DataBase),将某一时刻的内存数据,照相机照下这个时刻,以二进制的形式存储到磁盘
-
文件追加方式(AOF,Append Only File),记录所有的操作命令(set/del…),并以文本的形式追加到文件中
-
混合持久化方式,Redis4.0之后新增的方式,结合RDB和AOF的优点
- 在写入的时候,先把当前的数据以RDB的形式写入文件的开头
- 再后续的操作命令以AOF的格式存入文件
- 这样既能保证Redis重启时的速度,又能减低数据丢失的风险
你会发现很多地方的最后一种最优秀的方式,都是综合了之前的几种方式,各取所长而诞生的新的方式
就像鬼灭之刃的日之呼吸十三型,就是前十二型融会贯通:
3.2 RDB和AOF有什么区别
3.2.1 AOF 持久化
将Redis的写操作以日志的形式追加到一个文件中,类似于数据库的事务日志。当Redis重启的时候,可以通过回放AOF文件中的写操作来恢复数据
-
优点:
- **数据完整性比较好:**AOF持久化是通过追加写操作(set/del…)来记录数据变化的,因此通常比RDB更可靠,数据丢失的可能性较低~
- 一次宕机最多就这一条操作指令对应的数据丢失了,而RDB是这一段还没来得及快照下来的一片信息全丢了
- 可读性高:AOF文件是一个文本文件,可以方便查看和分析其中的内容,甚至进行一些手动修改的操作
- 多种持久化策略:Redis提供了不同的AOF持久化策略,可以根据需要选择不同级别的数据安全性和性能
- **数据完整性比较好:**AOF持久化是通过追加写操作(set/del…)来记录数据变化的,因此通常比RDB更可靠,数据丢失的可能性较低~
-
缺点:
- 文件较大:由于是追加写,AOF文件随着时间增长,会越来越大,较大的AOF文件会影响重启恢复数据的效率
- 对磁盘的写入操作频繁:AOF持久化需要频繁地写入日志文件,磁盘因此有一点写入压力
3.2.2 RDB 持久化
通过快照的方式,将当前Redis内存中的数据以二进制格式保存到硬盘上的一个文件中。快照文件是一个经过压缩的二进制文件,它代表了某一个时间点上的数据库状态
- 优点:
- 数据恢复效率高:RDB持久化是将整个数据集以二进制格式保存到磁盘上,因此数据恢复时,速度较快
- 文件较小:相比AOF,RDB生成的快照文件通常较小,占用较少的磁盘空间
- 缺点:
- 数据丢失:由于RDB是定期保存快照(更新快照文件),如果Redis在快照之间发生故障,可能会导致部分的一片数据的丢失,因为这个转二进制存储到文件这个操作开销比较大,很合理能知道不能一直快照
3.2.3 AOF 持久化策略有哪些
有3种:
- always:每条Redis操作命令都写入磁盘,最多丢失一条数据
- everysec:每秒种写入一次磁盘,最多丢失一秒的数据,默认的策略
- no:不设置写入磁盘的规则,根据当前操作系统来决定何时写入磁盘,例如Linux默认30秒写入一次数据到磁盘,也就是跟随系统
这个配置可以在redis.conf,也就是Redis的配置文件中,通过设置appendfsync属性来决定是哪个策略
参考redis.conf的第622 - 624行:
4. 数据淘汰
4.1 Redis键值过期之后会直接被删除吗
Redis 中的键值过期之后
- 在物理层面来说,并不会立即去删除这个过期数据
- 因为 Redis 本身是单线程执行的,如果某个键值过期之后就立马删除它,那么删除操作可能就会影响主业务的执行,得不偿失!
- 所以当某个键值过期之后,在 Redis 的物理层面并不会立即删除此过期数据,而是等待某个时机一起删除多个过期键
4.2 Redis如何淘汰过期键值
4.2.1 过期删淘汰策略
常见的过期淘汰策略有以下三种:
- 定时删除,在设置键值过期时间的时候,创建一个定时事件,过期即删除
- 惰性删除,不主动去删除过期键,而是每次从Redis获取键值时,Redis判断该键值是否过期,如果过期则删除此键值,返回null
- 定期删除,每隔一段时间检查一次Redis,随机删除一些过期键
前两种都比较好理解:
- 定时删除 => 有点像饿汉模式,延迟队列/计时器…
- 惰性删除 => 有点像懒汉模式
只用惰性删除是不够的,因为Redis是有最大运行内存的,不能积累得太多;一般会用惰性删除 + 定期删除,而定时删除,开销太大了,不采用!
4.2.2 定期删除得执行流程
- 从过期字典中随机取出20个键
- 删除这20个键中过期得键
- 如果过期键得比例超过25%,重复此过程
同时,为了保证过期扫描不会循环过度,导致线程卡死得现象,算法还增加了扫描时间的上限,默认不会超过25ms,定期删除的执行流程,如下图所示:
4.3 Redis如何解决内存不够用问题
4.3.1 Redis内存用完会怎么样
当Redis运行内存被用完的时候,即超过Redis设置的最大内存(参考第537行设置maxmemory,不设置代表当前机器的可用内存空间)之后,Redis触发保护机制-内存淘汰机制来删除符合条件的键值对,以此保障Redis能正常运行
4.3.1 内存淘汰策略
早期版本的Redis有以下六种内存淘汰机制(内存淘汰策略):
- 报错
- noeviction,不淘汰任何数据,内存不足就报错,默认
- 最久未用 LRU
- allkeys-lru,淘汰所有键值中最久未使用的键值
- volatile-lru,淘汰所有设置了过期时间的键值中最久未使用的键值
- 随机
- allkeys-random,随机淘汰所有键值中的任意键值
- volatile-random,随机淘汰所有设置了过期时间的键值中的任意键值
- 最先过期
- volatile-ttl,优先淘汰最先过期的键值
在Redis 4.0版本中又新增了两种淘汰机制:
- 最少使用 LFU
- allkeys-lfu,淘汰所有键值中最少使用的键值
- volatile-lfu,淘汰所有设置了过期时间的键值中,最少使用的键值
有点像线程池的几个拒绝策略~
4.3.2 LRU和LFU有什么区别
侧重点不同
- LRU,Least Recently Used,最近最少使用,是基于时间的策略,理念为:最近被访问过的键值就可能再次被访问,因此应该淘汰最久未被访问的键值;LRU策略会维护一个访问顺序列表
- 每当一个键被访问的时候,它会被移动到列表的末尾,最近没有被访问的键值的位置会比较靠前,所以会被优先淘汰
- 关注键的访问顺序
- LFU,Least Frequently Used,最不常使用,是基于频率的策略,理念为:它认为被访问次数最少的键值最不常用,更不重要,因此在淘汰时会优先选择访问次数最少的键值;LFU策略会维护一个访问计数器
- 每当一个键被访问时,此计数器会增加,LFU策略会选择访问计数最低的键进行淘汰
- 关注键的访问频率
4.4 过期淘汰策略和内存淘汰策略有何不同
- 内存淘汰策略:解决Redis运行内存过大的问题
- 过期淘汰策略:主要是为了删除过期数据的
5. 事务
Redis有事务,但是Redis中的事务主要作用是将多个一次性加入到事务队列中,然后通过EXEC命令一次性执行所有命令,这样就可以减少网络往返次数,提高了批量操作的效率
5.1 Redis的事务的使用
- multi开启事务
- exec提交事务
- 最终事务队列为原子性地一次性执行
- discard回滚事务
- 回滚是事务的习惯叫法,而Redis压根就没执行,只不过放在队列里,谈不上什么回滚,取消事务,清空队列
示例:
exec:
discard:
出错默认自动discard取消(回滚):
- 既然已经取消自然无法再次exec和discard
5.2 Redis和MySQL事务地最大区别是啥
Redis事务地本质是将一组指令加入到队列最终一次性执行地,
没有提供像MySQL那样地事务隔离性和隔离级别的设置,在事务启动/执行期间,其他客户端的读写操作不受影响!
6. 多机部署
6.1 Redis多机部署有几种方式
-
主从同步:主从同步(主从复制)是 Redis 高可用服务的基石,也是多机运行中最基础的一个。我们把主要存储数据的节点叫做主节点(master),把其他通过复制主节点数据的副本节点叫做从节点slave)。在 Redis 中一个主节点可以拥有多个从节点,一个从节点也可以是其他服务器的主节点
如果“主”宕机了,其下的“从”就群龙无首,必须手动将“从”变成“主”,Redis才能继续运行;
虽然主从模式成本低,但是要死“主”节点崩溃了,只能通过人为的方式干预~
-
哨兵模式:哨兵模式 Redis Sentinel是 Redis 的一种运行模式,它专注于对 Redis 实例(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主及主从切换,实现故障转移,确保整个 Redis 系统的可用性;
“主”节点崩溃后,哨兵自动将一个“从”节点升级成“主”节点
- 一旦多个实例组成了哨兵集群,即使有哨兵实例出现故障挂掉了,其他哨兵还能继续协作完成主从库切换的工作,包括判定主库是不是处于下线状态,选择新主库,以及通知从库和客户端。
哨兵模式下,“主”节点依旧只有一个,写能力没有多少提升,很有限,规模和需求一大,“主”节点的压力就很大,也没有办法提供太多的价值
所以就有了集群模式
-
集群模式:Redis 3.0 之前使用的是哨兵模式,而集群模式 Redis Cluster 是 Redis 3.0 版本推出的 Redis 集群方案,它将数据分布在不同的服务区上,以此来降低系统对单主节点的依赖,并且可以大大的提高 Redis 服务的读写性能。
相比于主从同步多了一个自动容灾恢复的优点
集群模式可以实现多个集群、多主节点的平行的性能扩展,多个“主”节点可以分担写操作
6.2 主从、哨兵、集群模式有什么区别
哨兵模式在主从模式的基础上,多了个哨兵节点监视,主节点宕机后,某个从节点升级成主节点~
但无论是主从还是哨兵模式,它的性能扩展都是有限的,因为主节点只有一个,而集群模式可以实现多个集群、多主节点的平行扩展,多个“主”节点可以分担写操作!
- 集群模式也具备自动容灾恢复,自主选主的功能(在Redis集群模式中,每个Redis实例都有一个或多个哨兵实例与之关联),所以集群模式才是Redis多机部署的最终形态!
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆!Redis优化使用就是遇到的那些常见的问题,使用对应的适当的解决方案!
遇到问题灵活变通,一般遇到的问题都是套了层别的衣服问的!