目录
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
比我想讲述的更细致些。