《Redis学习整理--第三期Redis底层的数据结构》

本文章参考http://blog.csdn.net/caishenfans/article/details/44784131 

redis对象类型简介
redis 是一种key/value型数据库,每一个key和value都是使用对象表示的

redis有五种对象的类型


redis中一个对象的结构体表示如下




type表示该对象的对象类型 ,上面的五种的一个。但为了提高存储效率和程序的执行效率,每种对象的底层数据结构实现都可能不止一种,encoding就表示了对象底层所使用的编码。

redis对象底层数据结构

底层数据结构共有八种


字符串对象
字符串对象的编码可以是 int raw 或者embstr

字符串类型对象中的value值就是数字和字符串 ,如果一个字符串对象的内容可以转换为long。那么该字符串就会被转换成long类型,对象的ptr就会指向该long,并且对象的类型也用int表示。

普通的字符串有两种,embstr和raw。embstr是redis3.0新增的数据结构,如果字符串的长度小于39字节(3.2之后改成了44),就用embstr。否则就使用传统的raw对象。 redis中代码:


embstr的好处:
embstr的创建只需分配一次内存。raw为两次(一次为sds分配对象,另一次为objet分配对象,embstr省去了第一次)
相对的,释放内存的次数也有两次变为一次
embstr的objet和sds放在一起,更好的利用缓存带来的优势

需要注意的是redis没有对embstr提供任何修改方式,所以embstr是只读的,对embstr的修改实际上是转换为raw后的修改

列表对象
列表对象的编码可以是ziplist或者linkedlist。
ziplist是一种压缩链表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。如下图所示,对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次,它们在内存中是一块连续的区域。

linkedlist是一种双向链表。 它的结构比较简单,节点中存放pre和next两个指针,还有节点相关的信息。当每增加一个node的时候,就需要重新malloc一块内存。

哈希对象
哈希对象的底层实现可以是ziplist或者hashtable

ziplist中的哈希对象是按照key1,value1,key2,value2这样的顺序存放来存储的。当对象数目不多且内容不大时,这种方式效率是很高的。

hashtable的是由dict这个结构来实现的


dicht[0] 是用于真正存放数据,dicht[1]一般在哈希表元素过多进行rehash的时候用于中转数据。
dictht中的table用语真正存放元素了,每个key/value对用一个dictEntry表示,放在dictEntry数组中



一个指向dictType结构的指针(type)。它通过自定义的方式使得dict的key和value能够存储任何类型的数据。

一个私有数据指针(privdata)。由调用者在创建dict的时候传进来。

两个哈希表(ht[2])。只有在重哈希的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据。上图表示的就是重哈希进行到中间某一步时的情况。

当前重哈希索引(rehashidx)。如果rehashidx = -1,表示当前没有在重哈希过程中;否则,表示当前正在进行重哈希,且它的值记录了当前重哈希进行到哪一步了。

当前正在进行遍历的iterator的个数。这不是我们现在讨论的重点,暂时忽略。

一个dictEntry指针数组(table)。key的哈希值最终映射到这个数组的某个位置上(对应一个bucket)。如果多个key映射到同一个位置,就发生了冲突,那么就拉出一个dictEntry链表。

size:标识dictEntry指针数组的长度。它总是2的指数。

sizemask:用于将哈希值映射到table的位置索引。它的值等于(size-1),比如7, 15, 31, 63,等等,也就是用二进制表示的各个bit全1的数字。每个key先经过hashFunction计算得到一个哈希值,然后计算(哈希值 & sizemask)得到在table上的位置。相当于计算取余(哈希值 % size)。

used:记录dict中现有的数据个数。它与size的比值就是装载因子(load factor)。这个比值越大,哈希值冲突概率越高

集合对象
集合对象的数据结构可以是intset或者hashtable
intset是一个整数集合,里面存放的为某种同一类型的整数,支持如下三种长度的整数

intset是一个有序集合,查找元素的复杂度为O(logN),但插入时不一定为O(logN),因为有可能涉及到升级操作。比如当集合里全是int16_t型的整数,这时要插入一个int32_t,那么为了维持集合中数据类型的一致,那么所有的数据都会被转换成int32_t类型,涉及到内存的重新分配,这时插入的复杂度就为O(N)了。是intset不支持降级操作。

有序集合对象
有序集合的编码可能两种,一种是ziplist,另一种是skiplist与dict的结合。
ziplist作为集合和作为哈希对象是一样的,member和score顺序存放。按照score从小到大顺序排列。它的结构不再复述。
skiplist是一种跳跃表,它实现了有序集合中的快速查找,在大多数情况下它的速度都可以和平衡树差不多。但它的实现比较简单,可以作为平衡树的替代品。它的结构比较特殊。下面分别是跳跃表skiplist和它内部的节点skiplistNode的结构体:

head和tail分别指向头节点和尾节点,然后每个skiplistNode里面的结构又是分层的(即level数组)
用图表示,大概是下面这个样子:

每一列都代表一个节点,保存了member和score,按score从小到大排序。每个节点有不同的层数,这个层数是在生成节点的时候随机生成的数值。每一层都是一个指向后面某个节点的指针。这种结构使得跳跃表可以跨越很多节点来快速访问。

前面说到了,有序集合ZSET是有跳跃表和hashtable共同形成的。

猜你喜欢

转载自blog.csdn.net/qq794096244/article/details/79557360