Redis数据结构分析(二)

目录

List的数据结构

Hash数据结构

Set数据结构

 zset数据结构


List的数据结构

redis中的list的基本使用包括lpush向list中添加元素,同一个key可以添加多个元素类型的数据,

lpush list 100 list-value 100.0

可以想一下这种存储数据的方式,会用什么样的数据结构呢?

首先想到了java的LinkedList的哪种链表存储,类似下面这种

typedef struct list{
     void *val
	 list * pre;
	 list * next;
}

但是如果真要按这种方式存储数据,会造成内存空间的浪费和内存碎片化。因为两个指针都要占用16个字节的内存,再加上存储的数据内存,至少在16字节以上,要是list只存储一个字符的内容,却要用至少16字节的空间存储,而且,pre,next指针还可能造成空间碎片。

为了更好的存储list的内容,redis中Redis3.2及之后的底层实现方式:quickList
quickList是双向链表,而且是一个基于zipList的双向链表,quickList的每个节点都是一个zipList,结合了双向链表和zipList的优点。

压缩列表(zipList)

压缩链表. 好处:节省内存空间,因为它存储的内容都是在连续的内存区域当中的.当列表对象元素不大,每个元素也不大的时候,就采用zipList存储. 但当数据量过大时,zipList就不那么好用了. 因为为了保证它存储内容在内存中的连续性,插入的复杂度为O(N),即每次插入都会重新进行realloc.
zipList的结构如下:

zlbytes:32bit,表示ziplist占用的字节总数。
zltail:    32bit,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数。通过zltail我们可以很方便地找到最后一项,从而可以在ziplist尾端快速地执行push或pop操作
zlen:     16bit, 表示ziplist中数据项(entry)的个数。
entry:表示真正存放数据的数据项,长度不定
zlend: ziplist最后1个字节,是一个结束标记,值固定等于255。
prerawlen: 前一个entry的数据长度。
len: entry中数据的长度
data: 真实数据存储

entry里面的数据结构包含了三部分:

prerawlen: 前一个节点的数据大小,分为两种情况,1)前一个节点大小小于254,则它占一个字节,因为一个字节足够了。2)大于254,就用5个字节来表示了,第一个byte作为标记位,4个byte用做大小表示。

len: 表示的含义较多看图中所述。

data: 具体的数据存储。

为什么不直接使用ziplist作为list的存储结构呢?

因为zipList是一段连续的内存,插入的时间复杂度O(n),而且每当插入新的元素需要realloc做内存拓展;而且如果超出zipList内存大小,还会做重新分配的内存空间,并将内容复制到新的地址. 如果数量大的话,重新分配内存和拷贝内存会消耗大量时间. 所以不适合大型字符串,也不适合存储量多的元素.

quicklist 相关结构:

robj *createQuicklistObject(void) {
    quicklist *l = quicklistCreate();
    robj *o = createObject(OBJ_LIST,l);
    o->encoding = OBJ_ENCODING_QUICKLIST;
    return o;
}

quicklist *quicklistCreate(void) {
    struct quicklist *quicklist;

    quicklist = zmalloc(sizeof(*quicklist));
    quicklist->head = quicklist->tail = NULL;
    quicklist->len = 0;
    quicklist->count = 0;
    quicklist->compress = 0;
    quicklist->fill = -2;
    quicklist->bookmark_count = 0;
    return quicklist;
}

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;       
    unsigned long len;           
    int fill : QL_FILL_BITS;                
    unsigned int compress : QL_COMP_BITS;  
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    unsigned int sz;            
    unsigned int count : 16;    
    unsigned int encoding : 2;    
    unsigned int container : 2;  
    unsigned int recompress : 1; 
    unsigned int attempted_compress : 1; 
    unsigned int extra : 10;  
} quicklistNode;

 看到quicklist节点里面存储ziplist节点,ziplist节点里面存储具体的entry数据,对于每个ziplist节点存储的数据量是可控制的,太大对性能有影响。有两个参数可以参考:

list-max-ziplist-size -2 #长度,每个ziplist里面存储多少数据的进行分裂,就是再创建一个ziplist节点加入到quicklist中。

具体有下面几种配置:通常使用默认即可 8kb 

压缩深度:

list-compress-depth  1        //  0 代表所有节点,都不进行压缩,1, 代表从头节点往后走一个,尾节点往前走一个不用压缩,其他的全部压缩,2,3,4 ... 以此类推

Hash数据结构

Hash 数据结构底层实现Hash 数据结构底层实现为一个字典( dict ),也是RedisBb用来存储K-V的数据结构,当数据量比较小,或者单个元素比较小时,底层用ziplist存储,数据大小和元素数量阈值可以通过如下参数设置。

hash-max-ziplist-entries  512    //  ziplist 元素个数超过 512 ,将改为hashtable编码

hash-max-ziplist-value    64      //  单个元素大小超过 64 byte时,将改为hashtable编码

比如设置:hset testhash name guojia age 35,按照ziplist结构存储如上所示。

hashtable编码存储的时候,数据结构用的是dict,dictEntry中存储field-value,关于它的结构见上篇的介绍。

hash的结构转换如下:存储了很大的数据后,编码从ziplist转成了hashtable。 

Set数据结构

Set 为无序的,自动去重的集合数据类型,Set 数据结构底层实现为一个value 为 null 的 字典( dict ),当数据可以用整形表示时,Set集合将被编码为intset数据结构。两个条件任意满足时
Set将用hashtable存储数据。1, 元素个数大于 set-max-intset-entries , 2 , 元素无法用整形表示 
set-max-intset-entries 512       // intset 能存储的最大元素个数,超过则用hashtable编码

如果设置的元素都是数字,redis会自动去重且排序好,使用intset编码,这种占用内存更少。

当元素中增加了字符元素时,就不再使用intset编码了,改为hashtable编码,而且顺序会变成乱序,没法根据元素进行排序了。编码改为hashtabel。

 zset数据结构

 ZSet  为有序的,自动去重的集合数据类型,ZSet 数据结构底层实现为 字典(dict) + 跳表(skiplist) ,当数据比较少时,用ziplist编码结构存储。 

 当用ziplist存储的时候,分值和元素是两个节点存储。

zset-max-ziplist-entries  128    // 元素个数超过128 ,将用skiplist编码

zset-max-ziplist-value     64     //  单个元素大小超过 64 byte, 将用 skiplist编码

127.0.0.1:8001> zadd zsetkey 10 sc1 1 sc2 98 sc3 64 sc4 5 sc5
-> Redirected to slot [16039] located at 127.0.0.1:8003
(integer) 5
127.0.0.1:8003> ZRANGE zsetkey 0 -1
1) "sc2"
2) "sc5"
3) "sc1"
4) "sc4"
5) "sc3"
127.0.0.1:8003> ZRANGE zsetkey 0 -1 withscores
 1) "sc2"
 2) "1"
 3) "sc5"
 4) "5"
 5) "sc1"
 6) "10"
 7) "sc4"
 8) "64"
 9) "sc3"
10) "98"
127.0.0.1:8003> object encoding zsetkey
"ziplist"

自动排序,使用的是ziplist编码。具体的跳表结构skiplist的内容,我推荐一篇博客:

https://www.jianshu.com/p/e30eb0e1514c

比我想讲述的更细致些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姑苏冷

您的打赏是对原创文章最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值