Redis数据结构

Redis的应用非常广泛,一提到Redis,你的第一反应是快。那为什么它那么快呢?有很多原因:

  1. 基于内存操作,内存操作本来就很快;
  2. 采用单线程模型(注意区分单线程的使用地方),避免了线程切换的额外开销;
  3. 优秀的数据结构,这里的数据结构指Redis底层的数据结构,包括动态字符串、双向链表、压缩列表、跳表、整型数组、哈希表。

Redis的数据结构有哪些呢?
基本数据类型包括:String、Hash、List、Set、Sorted Set。
扩展数据类型包括:GEO、Bitmap、HyperLogLog等

Redis是键值对数据库,提供高效的数据操作,那键值对是怎么维护的呢?答案是Hash表,Redis提供了全局Hash表来维护键值对关系,大家都知道Hash表的时间复杂度是O(1),当然Hash冲突严重时时间复杂度会增加,不过Redis也提供了完美的ReHash过程,下面来介绍Redis的全局Hash的组成,下看下图:


全局hash表

从图中可以看出,哈希桶中的entry有key、value及next,分别占用了8字节。next指向下一个entry,当发生哈希冲突时,会用拉链法解决哈希冲突。key和value分别指向了RedisObject对象,这样不管value是什么数据类型,都可以通过RedisObject来维护,图中value是string类型。

RedisObject包括元数据和指针,其中元数据包括数据类型、最后访问时间(lru、lfu)、编码方式等等。下面以string类型为例进行介绍。

当保存的数据是Long类型的数据时,数据会直接保存到指针ptr所在的位置,这样就避免了指针占用的空间,这种方式称为int编码方式。
当保存的数据是字符串,且长度小于44字节时,数据会和RedisObject的ptr保存到一起,占用一块连续的内存空间,这样就避免了内存碎片的可能,这种方式称为embstr编码方式;当长度大于44字节时,就不会用连续的内存空间来保存数据了,这种方式称为raw编码方式。


编码格式.png

通过上面的分析可以得知,如果保存的字符串本身并不大,但由于Redis使用了DictEntry、RedisObject、SDS等结构,额外需要很多空间,如DictEntry结构就占用了24字节的指针空间;RedisObject中一共16字节的空间,其中元数据占用了8字节,指针又占用了8字节;具体到SDS时,又需要len和alloc各占4字节。所以我们可以得出结论,如果用Redis保存大量字符串数据时,由于额外的空间占用,会消耗非常大的内存空间。Redis是基于内存的数据库,如果内存消耗过大,势必会影响整体的性能。

那怎么解决这种问题呢,Redis底层提供了多种数据类型,如动态字符串、双向链表、压缩列表、跳表、整型数组、哈希表。其中压缩列表根据名字可以判断出,他是非常节省内存空间的,Redis数据结构对应底层数据结构如下:


Redis常用数据结构对应底层数据结构关系图.png

可以看到,除了String类型只使用了简单动态字符串外,其他数据类型都使用了两种底层数据结构。

双向链表和哈希表比较简单这里不做介绍。

整数列表也叫整数集合,是集合类型底层的数据结构,当保存的数据是整数值元素,而且元素不多时,使用这种数据结构。

我们都知道使用链表时只能一个元素接着一个元素的查找数据,时间复杂度时O(N),当列表元素个数很多时,性能可想而知,跳表是在链表的基础上增加了多级索引,进而提升了查询效率。


跳表.png

假如需要查询34这个元素,在原始的链表中,需要一个元素一个元素的查询,需要查询6次,随着多级索引的不断建立,查询效率越来越高。这就是跳表的原理。接下来我们看一下压缩列表。

压缩列表可以节省内存空间,压缩列表由表头,元素、表尾组成,表头有三个字段zlbytes、zltail、zllen,分别表示列表长度、列表尾、列表尾的偏移量。列表尾还有一个zlend,表示压缩列表结束。


压缩列表.png
  1. prev_entry:表示前一个entry的长度;
  2. len:表示自身长度
  3. encoding:表示编码方式
  4. content:表示实际内容
    entry在内存中会紧挨着排布在内存中,这就避免了大量指针所占用的空间,节省了大量空间。回到前面提到的String类型消耗太多内存空间的问题。

String类型是单值键值对类型,即一个键对应一个值,从DictEntry上看,一个value只能保存一个值,而列表的话一个value可以保存大量的值,底层使用压缩列表,可以很大程度上节省内存空间,那单值得键值对怎么保存成集合类型呢?答案是二级编码,将单值类型转换成集合类型,可以使用哈希表保存二级编码后的字符串。哈希表的底层数据结构是哈希表和压缩列表,那什么时候用压缩列表什么时候用哈希表呢?其实Redis在使用压缩列表时设置了两个阈值,一旦超过了阈值就会使用哈希表来保存,这两个阈值如下:

  • hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中最大元素个数;
  • hash-max-ziplist-value:表示用压缩课表保存时哈希集合中单个元素的最大长度;
    一旦从压缩列表转换为了哈希表,就不可能在转换回去了,在节省内存方面,哈希表远不如压缩列表,所以在使用二级编码时注意一下就行了。

最后讲一下Redis的Rehash过程,当全局哈希表满足一定条件后,就会触发Rehash过程。Redis会有两个哈希表,哈希表1和哈希表2,开始时哈希表2并没有分配内存空间,当哈希表1符合Rehash时便会触发Rehash过程,此时会给哈希表2分配更大的空间,如哈希表1的2倍,然后开始迁移数据,注意迁移不是一下迁移的,因为Redis的键值对操作时单线程的,Rehash过程会阻塞Redis的正常使用,这肯定是不能接收的。Redis使用了渐进式Rehash的方式,每次操作键值对时从第一个索引开始顺便迁移到新的哈希表上,这样就避免了主线程的阻塞。

好了,到这里基本介绍完了,相信你已经掌握了相关内容。

你可能感兴趣的:(Redis数据结构)