rpush
用来往list的队尾加入值
> rpush mylist "a" "b"
(integer) 2
使用lrange
可以查看插入的值
> lrange mylist 0 2
1) "a"
2) "b"
linsert
可以在指定的元素之前或者之后插入值
> linsert mylist before "m" "l"
-1
> linsert mylist before "d" "e"
5
> lrange mylist 0 -1
1) "e"
2) "d"
3) "c"
4) "a"
5) "b"
指定的元素不存在则不会插入
rpop
可以对应弹出队尾的值
> lrange mylist 0 -1
1) "e"
2) "d"
3) "c"
4) "a"
5) "b"
6) "a"
7) "b"
8) "c"
> rpop mylist
"c"
rpush命令执行追踪
rpush的入口在 rpushCommand
Code.SLICE.source("robj *lobj = lookupKeyWrite(c->db,c->argv[1]);n"
"n"
" if (lobj && lobj->type != OBJ_LIST) {n"
" addReply(c,shared.wrongtypeerr);n"
" return;n"
" }")
.interpretation("查找之前是不是有过同名的key,如果有,但是key的编码方式不是 OBJ_LIST直接报错返回");
Code.SLICE.source("for (j = 2; j < c->argc; j ) ")
.interpretation("遍历所有的value,一个个的插入");
Code.SLICE.source("if (!lobj) {n"
" lobj = createQuicklistObject();n"
" quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,n"
" server.list_compress_depth);n"
" dbAdd(c->db,c->argv[1],lobj);n"
" }n"
" listTypePush(lobj,c->argv[j],where);n"
" pushed ;")
.interpretation("如果之前没有存在一模一样的key,重新创建一个,它的类型是 quicklist,然后存起来,再执行插入");
执行插入,和一个数据结构相关,就是quicklist,quicklist的每一个节点为quicklistNode
doubly linked list
一个常规的redis双向列表形式如下
代码语言:javascript复制[0] <-> [1] <-> [2] <-> ... <-> [N]
- 每一个节点的listNode包含3个指针:prev/next/value(3个指针的长度为24字节)。- 每个数据指向一个 redisObject 对象,它包括32bit的元数据,1个int的引用,1个指向内容的指针(总共16字节)
- 在redisObject里面的值是sds,它包括两个int的字段和string内容(总共 4字节 contents)
也就是说,每个节点,至少包含40个字节的元数据内容,还有其它的一些内部为了计算的分配,那么如果只往内部 插入 10个字符的string,显然元素据的空间超过了存储的内容,这显得有些浪费
ziplist
redis使用ziplist来解决存储小量数据 常规双向链表 的问题。它的结构如下
代码语言:javascript复制[total size][tail offset][cached element count][entry 0]...[entry N][END]
一个空的ziplist只占据了11 bytes
代码语言:javascript复制[size=4 bytes][tail offset=4 bytes][count=2 bytes][END=1 byte]
对于每一个entry来说,它的结构为
代码语言:javascript复制[length of previous entry][length of this entry][contents]
- 前一个entry的长度用来保证可以做逆向遍历。
- ziplist使用变长的编码,如果存储小的内容,偏移也更小
但是这种方式也带来了问题
- 每次插入元素需要将后面的元素后移,同时插入意味着需要重新分配内存
- 删除元素的时候,所有元素要往前移
这意味着ziplist最好保持一定的大小来做到空间和时间的最有效利用
quicklist
一个quicklist的结构大致如下
代码语言:javascript复制[ziplist 0] <-> [ziplist 1] <-> ... <-> [ziplist N]
通过 list-max-ziplist-entries 来控制每个节点的 ziplist的数目,超过限定则新建一个 quicklistnode。 优势
- 任何长度的list都能有效的利用内存
- 仍然是O(1)获取head和tail
- 删除某个区域的list效率提升
- 维持了原有的RDB和AOF格式
- 如果限制每个ziplist只保留1个entry,它就转换成了原始的linked list但却有更好的内存利用率
这种方式也带来了额外的操作
- 在quicklist的中间插入元素,可能需要拆开原有的ziplist并创建额外的quicklistNOde
- 从quicklist中删除元素,需要把多个ziplist进行合并
- 所有的插入意味着需要重新分配ziplist
- 在头部插入需要把原有的ziplist实体后移
quicklist的结构如下
代码语言:javascript复制Code.SLICE.source("typedef struct quicklist {"
" quicklistNode *head; /*头结点*/"
" quicklistNode *tail; /*尾结点*/"
" unsigned long count; /* 所有ziplists中的所有entry的个数 */n"
" unsigned long len; /* quicklistNodes节点的个数 */n"
" int fill : 16; /* ziplist大小设置,存放配置 list-max-ziplist-size */n"
" unsigned int compress : 16; /* 节点压缩深度设置,存放配置 list_compress_depth */n"
"} quicklist;")
.interpretation("head和tail两个函数指针最多8字节,count和len属于无符号long最多8字节,最后两字段共32bits,总共40字节")
.interpretation("list-max-ziplist-size 取正数按照个数来限制ziplist的大小,比如5表示每个quicklist节点ziplist最多包含5个数据项,最大为 1 << 15"
"-1表示每个quicklist节点上的ziplist大小不能超过 4kb,-2(默认值)表示不能超过 8kb依次类推,最大为 -5,不能超过 64kb")
.interpretation("list_compress_depth 0表示不压缩,1表示quicklist两端各有1个节点不压缩,其余压缩,2表示quicklist两端各有2个节点不压缩,其余压缩,依次类推,最大为 1 << 16");
//...
Code.SLICE.source("typedef struct quicklistNode {n"
" struct quicklistNode *prev; /*当前节点的前一个结点*/"
" struct quicklistNode *next; /*当前节点的下一个结点*/"
" unsigned char *zl; /*数据指针。如果当前节点没有被压缩,它指向的是一个ziplist,否则是 quicklistLZF*/"
" unsigned int sz; /* zl所指向的 ziplist 的总大小,计算被压缩了,指向的也是压缩前的大小*/n"
" unsigned int count : 16; /* ziplist中数据项的个数 */n"
" unsigned int encoding : 2; /* RAW==1(没有压缩) or LZF==2(压缩了) */n"
" unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */n"
" unsigned int recompress : 1; /* 识别这个数据之前是不是压缩过,比如再检查数据的过程中是要解压缩的过后需要还原*/n"
" unsigned int attempted_compress : 1; /* node can't compress; too small */n"
" unsigned int extra : 10; /* 扩展字段,目前没有用*/n"
"} quicklistNode;")
.interpretation("从前向和后项来看,quickList 本身就是一个 双向链表")
.interpretation("1:结构自身的大小 prev、next、zl 各8字节,sz无符号 int 为4字节,其余按照后面的bit算一共32bits共4字节,总共32字节");
quicklistnode本身还可以根据节点离head/tail的距离做压缩,达到更高的空间节约
结论
list在底层会使用quicklist的结构来存储,每一个quicklistNode的节点都会存储一个可配置的ziplist大小量,如果有多个quicklistNode,它会根据配置的压缩深度,来使用lzf算法进行压缩