目录
认识Redis
1.介绍一下Redis
2.使用Redis的好处在哪里
Redis的数据类型
3.Redis的数据类型有哪些
String
三种编码方式
常见面试题
4.在Redis中String数据类型的编码方式有几种,区别是什么?
5.浮点型在String使用什么编码方式
6.为什么EMBSTR的阈值是44字节
7.String 可以有多大?
8.SDS有什么作用?
List
常见面试题
9.List对象底层存储的编码实现是怎么样的?
10.ZipList怎么压缩数据的
11.ZipList是怎么获取节点个数的?
Set
常见面试题
12.Set是有序的嘛?
13.Set有几种编码方式,为什么要这些编码方式?
ZSet
常见面试题
14.Zset底层有几种编码方式?
15.跳表插入一条数据的平均时间复杂度时多少?
16.SkipList和dict在ZSet分别起什么作用
Hash
常见面试题
17.Hash的编码方式是什么?
18.Hash找某个key的时间复杂度是多少?
19.Hash中dict编码方式查找总元素个数的时间复杂度是多少?
20.dict什么时候会进行扩缩容,怎么扩缩容的?
先一句话概括一下Redis,Redis是一个基于内存,支持多种数据结构的存储系统,可以作为数据库,缓存和消息中间件。再从数据类型和支持功能的角度,分别列举Redis拥有的功能,Redis支持的数据结构有字符串,哈希,列表,集合和有序集合等,在此基础上Redis还添加了bitmaps, hyperloglogs和geo等数据类型。Redis还内置了复杂,LUA脚本,LRU驱动事件,事务和持久化,还可以通过哨兵和集群来实现高可用。
其实还是和介绍Redis一样,只需要概括一下Redis,可以回答一下三点
回答类似的题目也可以有意识得往这三个方向回答,主要的内容就是概括Redis。
先对Redis有点数据结构进行一下总结,还是那句话,Redis支持的数据结构有字符串,哈希,列表,集合和有序集合等,在此基础上Redis还添加了bitmaps, hyperloglogs和geo等数据类型。
但是不能只知道有这些类型,还得知道基本的五种类型的原理。
基本编码方式是RAW,基于SDS实现,存储上限是512mb
小于44个字节的字符串,会采用EMBSTR(与RAW不同,RAW中的头信息和实际内容不在同一片区域,通过指针寻址,而EMBSTR的头信息和SDS是一段连续空间,只需要调用一次内存分配)。
INT编码,由于INT类型可以转换成2进制形式,直接将数据保存在RedisObject的ptr指针位置,不再需要SDS了。
答:String数据类型有三种编码方式,分别是RAW,EMBSTR,INT编码。RAW在头信息的结构中会有一个指针,通过指针寻址的方式指向一个SDS类型的地址,该地址就是实际存储内容的地址。而对于整数类型,可以直接将存储内容替换指针,这样就只需一次内存分配。
答: 在Redis中,INT编码只用来存储整数,所以使用RAW还是EMBSTR取决于该浮点数转化成字符串后的长度,对于小于等于44字节的字符串使用EMBSTR,否者使用RAW
根据RedisObject的头信息和sds的结构而定的,RedisObject的结构中包括以下内容:type(类型 string占4个字节),encoding(编码方式 string占4个字节),lru(最近使用该对象的时间),refcount,指针ptr。sdst的结构中包括以下内容:len(已保存的字符串字节数,不包含结束标识 当小于等于254字节都可以用uint8表示) 占用1个字节,alloc(申请的总字节数,不包含结束标识) 占用1个字节,flags(字符类型)占用1个字节,结束标识1个字节。
答:RedisObject是固定16个字节,sds也有4个用来记录的字节,16 + 4 + 44 = 64,Redis中内存分配64字节刚好是一个分片大小,不会产生内存碎片。s
答: 最大为512MB,在官方文档中有标注
SDS有点就像c语言的字符数组,不过他和c语言字符数组还是有些差别的,可以从差别入手回答
答:
在3.2之前,Redis采用ZipList和LinkedList实现list,当元素数量小于512并且元素大小小于64字节时,采用ZipLIst编码,否者使用LinkedList编码。
在3.2之后,统一使用QuickList来实现List。
答: 在3.2之前,Redis采用ZipList和LinkedList实现list,当元素数量小于512并且元素大小小于64字节时,采用ZipLIst编码,否者使用LinkedList编码。
在3.2之后,统一使用QuickList来实现List。
答:压缩列表是一块连续的内存空间,元素之间是紧挨着存储的,一个压缩列表中可以包含多个节点,是在连续的内存空间上实现的双端链表。
在ZipList中,会存储上一个节点的长度和该节点的长度,方便找到上一个节点和下一个节点。
对于一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。这种方式会带来大量的内存碎片。
答:zipList的表头结构中有记录节点个数的字段zllen,通过zllen可以得到节点个数,时间复杂度是o(1),但是zipList中zllen只有两个字段,只能存储2的16次方,即65536个节点,当节点数大于65536时,就需要遍历整个链表来得到节点个数,这时时间复杂度时o(n)。遍历的流程是,在ZipLIst每个节点中保存这该节点的长度,通过计算偏移量来找到下一个节点,迭代获取到总的节点个数。
Redis中的set有以下的特点:
为了查询效率和唯一性,set采用Dict编码,其中key用来存储元素,value统一为null。
当存储的所以数据都是整数,并且元素数量不超过set-max-intset-entries时,set会使用IntSet编码
set的底层有两种编码方式Intset和dict,前者只用来存储整数类型,是有序的,后者是无序的。
set的底层有两种编码方式Intset和dict,当存储类型是整数,且数据量少时会选择使用intset,这样会更加节约内存,但是查找时底层使用的是二分查找,时间复杂度为o(logn),当数据量变大时候,会使用dict,查找元素更快,一般情况下整数元素小于512时会使用intset,否者就会使用dict来进行编码。
ZSet底层有两种编码方式ZipList或者 SkipList + dict,很常见的方式,当数据量小时使用ZipList,因为ZipList相比较SkipList + dict占用的内存更小,使用ZipList编码实现ZSet的特性,消耗时间来减少内存的占用,但是当数据量大时,查询以及排序的效率很低,只能使用SkipList + dict来进行编码。
如果一个ZSet对象中的所有元素同时满足:元素数量小于128个以及所有元素成员的长度都小于64字节,那么会使用ziplist编码,户否则使用skiplist+字典编码。
跳表的查询效率接近二分查找,他插入一条数据时,需要先进行查找位置,找到之后对行索引进行重建,整体的时间复杂度时o(logn)。
可以结合这两种数据结构的优势,当需要查找分值,可以通过dict来实现,时间复杂度为o(1),当要进行范围操作时,如进行排行等操作,如ZRANK命名,就可以使用有序的跳表来实现。
hash从要实现的功能角度上来讲和zset类似,但是hash没有范围操作,使用hash的编码结构有ZipList和dict两种。
Hash主要有两种编码方式,当数据量较小且单个元素较短的时候使用ZipList,否者使用dict。
当其中元素少于512个,值和键都小于64字节使用ZipList。
找key的时间复杂度得根据其底层使用的编码方式,若使用的是ZipList则需要进行遍历,时间复杂度是o(logn),使用dict的话则只需要o(1)。
在dict的表头结构中有专门的字段存储总元素个数,used的值表示元素总个数,所以查找元素总个数的时间复杂度时o(1)。
扩容在两种情况下会进行,1.当前服务器没有子进程执行bgsave 且 负载因子 >= 1,2.当前服务器有子进程正在执行bgsave,且负载因子 >= 5。
缩容其实就是扩容的反向条件,当负载因子 <= 0.1时会进行缩容。
由于hash中存的内容可能过多,进行扩缩容的时候,可能无法一次全部迁移过去,所以使用的时渐进式rehash,即每次进行增删改查的时候迁移一个下标的链表,指导全部迁移到新表。
扩容流程:
首先程序会为dict的1号表分配空间,空间大小是第一个大于等于0号表大小*2的2^n。在rehash进行期间,标记位rehashidx从0开始,每次对字典的键值对执行增删改查操作后,都会将rehashidx位置的数据迁移到1号表,然后将rehashidx加1,随着字典操作的不断执行,最终0号表的所有键值对都会被rehash到1号表上。之后,1号表会被设置成0号表,接着在1号表的位置创建一个新的空白表。 这里注意 当进行新增操作时,只会对1号表操作,当进行删改查操作时,会同时对新表和旧表进行查找或操作。
缩容流程和扩容流程类似,区别是新表大小为第一个大于等于原表used的2次方幂。