Redis面试题
文章目录
简述下Redis(为什么要选用Redis)
Redis是一个开源的使用C语言编写,可基于内存,可持久化的Key-Value
数据库,和Memcached
类似,它支持存储的value
类型相对更多,包括(字符串)
,链表
,set
、zset
和hash
-
优势在于
速度更快,性能高,可持久化性,丰富的数据类型,支持数据的备份 -
Redis和Memcached区别
-
类型:
Redis是一个开源的呢欧村数据结构存储系统,用作数据库,缓存和消息代理 -
数据结构
Redis支持字符串,散列,列表,集合,有序集,位图。超级日志和空间索引;而Memcached支持字串和整数 -
执行速度
Memcached的读写速度高于Redis -
复制
Memcahced不支持复制,而Redis支持主从复制,允许从属Redos服务器成为主服务器的精确副本;来自任何Redis服务器的数据都可以复制到任意数量的丛属服务器 -
秘钥长度
Redis的秘钥长度最大为2GB,而Memcacherd的秘钥差精度最大为250字节 -
线程
Redis是单线程的,而Memcahced是多线程
-
为什么说Redis块或者性能高
- 完全基于内存中,类似于HashMap,HashMap的优势还就是查找和操作的时间复杂是读都是O(1)
- 数据结构专门设计,比如SDS结构中字符擦混长度len,压缩链表;(可以引申到redis的8大数节后)
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存子多进程或者多线程切换而导致消耗CPU,不用取考虑各种锁问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗(这里锁说的单线程是工作线程是一个,但是会有其他辅助线程,比如哨兵线程,或者单线程延伸出的子线程)
- 使用多路IO复用模型,非阻塞IO:多路IO复用模型是利用
select
,poll
,epooll
可以同时监察多个流的IO事件的能力,在空闲的时候,会把当前想线程阻塞掉,当有一个或多个流有IO事件时,就从阻塞状态中唤醒,于是程序就会轮询以便所有的流,并且只一次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 -
RESP(Redis的序列化协议)协议,文本协议,解析迅速,虽然浪费流量,这是这种把
1 - 持久化采用子线程进行磁盘操作
Redis中的5中数据类型,8大数据结构
简单动态字符串SDS和C语言自带的字符串有什么不同
常数复杂度获取字符串长度
由于
len
属性的存在,我们获取SDS
字符串长度终止需要读取len属性,时间复杂度为O(1),而对于C语言,获取字符串长度通常是经过遍历计数来实现的,时间复杂度为O(n)
避免缓冲区溢出
对于SDS数据类型,在进行字符串修改的时候,会首先根据记录的
len
属性检查内存空间是否满足需求,如果不满足,会进行相应空间扩展,然后在进行修改记录,所以不会出现缓冲区溢出
减少修改字符串的内存重新分配次数
C语言由于不记录字符串的长度,所以如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,,字符串长度减小会造成内存泄露;
而对于SDS,由于len
属性和free
属性的存在,对于修改字符串SDS
实现空间预分配和惰性空间释放策略:
-
空间预分配
:对于字符串进行空间扩展的时候,对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需要的内存重分配次数。 -
惰性空间释放
:对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后的多余字节,而是使用free
属性将这些字节的数量记录下来,等待后序使用(当然SDS也提供了相应的API,当我们需要的时,也可以手动释放这些为正确使用的空间) -
二进制安全
:因为C
字符串以空字符串作为字符串结束的标志
,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串
无法正确存取;而所有SDS
的API都是以二进制处理buf
里面的元素,并且SDS
并不是以空字符串
来判断是否结束,而是以len
属性表示的长度来判断字符串是否结束 兼容C字符串函数
Redis字典的底层实现hashTable相关问题
-
解决冲突
:链地址法,和java
的HashMap
一样 -
扩容
:复制出另外一个hash
表,并且会重算hash
值,进行渐进式
hash,这一点和java
不同,特别是java8
的不用重复算hash
机制的优化点。
什么是渐进式rehash?
也就是说扩容和收缩操作不是一次性,集中式完成的,而是分多次,渐进式完成的。如果保存在
Redis
中的键值对只有几个几十个,那么hash
操作可以瞬间完成,但是如果键值对右几百万,几千万甚至几亿,那么要一次性的进行rehash
,势必会造成Redis
一段时间内不能进行别的操作。所以Redis
采用渐进式rehash
,这样在进行渐进式rehash
期间,字典的删除查找更新等操作可能会在两个哈希表上进行,第一个哈夏表上没有找到,就会去第二个哈希表上进行查找。但是进行增加操作,一定是在新的哈希表上进行的
压缩列表原理
压缩列表是
Redis
为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值
zset底层跳表原理(为什么不选择平衡树)
本质就是多级链表,并有序skiplist与平衡树,哈希表的比较
’
-
skiplist
和各种平衡树(如AVL
,红黑树
等)的元素是有序排列的,而哈希表不是有序的,因此在哈希表上只能做单个key
的查找,不适宜左范围查找。所谓范围查找,指的是查找哪些大小在指定的两个值之间的所有节点 - `在做范围查找的时候,平衡树比skiplisty操作要复杂。在平衡树上,我们找到指定范围的最小值后,还需要以中序遍历的熟悉怒继续寻找其他不超过大值的节点。而在skiplisy上进行范围查找就非常简单,只需要在找到最小值之后,对第一层链表进行若干步的遍历就可以实现
- 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而
skiplist
的插入和删除只需要修改相邻节点的指针,操作简单又快速 - 从内存占用上来说,
skiplist
比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针,而跳表每个节点包含的指针数目平均为 - 查找单个
key
,skiplist
和平衡树的时间复杂度都为O(nlogn)
,大体相当;而哈希表在保持较低的哈夏值冲突概率的前提下,查找时间复杂度接近o(1)
,性能更高一些。所以我们平时要使用的各种Map
或者dictionary
结构,大致都是基于哈希表实现的 - 从算法实现复杂度上来比较,
skiplist
比平衡树要简单的多
Redis中过期策略与内存淘汰机制
过期/删除策略(出现缓存雪崩的问题)
两种策略
-
定期删除
:redis
默认每隔100ms
检查,是否有过期的key
,如果有过去的key则删除。需要说明的是,redis
不是每隔>100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除 -
惰性删除
:获取某个key的时候,redis会检查一下,如果过去了此时就删除
可能出现缓存雪崩的问题
从库的删除策略
从库不会进行过期扫描,从库对过期的处理是被动的。主库在
keu
到期时,会在AOF
文件里增加一条del
指令,同步到所有的从库,从库通过执行del
指令来删除过期的key
内存淘汰机制
Redis中持久化机制
介绍
- Redis的持久化有两种机制:一个是RDB,也就是快照,快照是一次全量的备份,会把所有的
Redis
的内存数据进行二进制的序列化存储到磁盘。(Redus会Fork一个子进程,快照的持久化就交给子进程去处理,而父进程继续处理线上业务的请求
)。- 另一种是
AOF日志
,AOF日志记录的是数据操作修改的指令记录日志,可以类比 MySql的binlog,AOF日值随着时间的推移会无限增量。* ==Redis4.0之后,随着新的持久化模式,混合持久化,将RDB的文件和局部增量的
AOF文件结合,
RDB可以使用相隔较长的时间保存策略,
AOF不需要是全量日志哦,只需要保存前一些
RDB存储开始到这段时间增量
AOF`日志即可,一般来说,这个日志量是非常小的==
对比
Redis集群的主从复制
采用完整重同步和部分重同步两种模式
实现同步的两种机制
完整重同步
完整重同步用于处理初次复制情况:通过让主服务器创建并发送
RDB
文件,以及向从服务器发送保存在缓冲区的写命令发来实现同步
部分重同步
部分重同步是用用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接受并执行这些写命令,就可以将数据库更新之主服务器当前所处的状态
部分重同步的实现(增量同步实现)
部分重同步功能由以下三个部分构成
主服务器的复制偏移量和从服务器的复制偏移量
主服务器的复制积压缓冲区
服务器运行ID