rpush
用来往list的队尾加入值java
> rpush mylist "a" "b"
(integer) 2
复制代码
使用lrange
能够查看插入的值node
> lrange mylist 0 2
1) "a"
2) "b"
复制代码
linsert
能够在指定的元素以前或者以后插入值git
> 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"
复制代码
指定的元素不存在则不会插入github
rpop
能够对应弹出队尾的值redis
> lrange mylist 0 -1
1) "e"
2) "d"
3) "c"
4) "a"
5) "b"
6) "a"
7) "b"
8) "c"
> rpop mylist
"c"
复制代码
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的每个节点为quicklistNodebash
一个常规的redis双向列表形式以下数据结构
[0] <-> [1] <-> [2] <-> ... <-> [N]
复制代码
也就是说,每一个节点,至少包含40个字节的元数据内容,还有其它的一些内部为了计算的分配,那么若是只往内部 插入 10个字符的string,显然元素据的空间超过了存储的内容,这显得有些浪费运维
redis使用ziplist来解决存储小量数据 常规双向链表 的问题。它的结构以下函数
[total size][tail offset][cached element count][entry 0]...[entry N][END]
复制代码
一个空的ziplist只占据了11 bytes
[size=4 bytes][tail offset=4 bytes][count=2 bytes][END=1 byte]
复制代码
对于每个entry来讲,它的结构为
[length of previous entry][length of this entry][contents]
复制代码
可是这种方式也带来了问题
这意味着ziplist最好保持必定的大小来作到空间和时间的最有效利用
一个quicklist的结构大体以下
[ziplist 0] <-> [ziplist 1] <-> ... <-> [ziplist N]
复制代码
经过 list-max-ziplist-entries 来控制每一个节点的 ziplist的数目,超过限定则新建一个 quicklistnode。 优点
这种方式也带来了额外的操做
quicklist的结构以下
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算法进行压缩
rpush源码追踪
quicklist与其它list实现方式的对比以及性能测试说明 matt.sh
Redis内部数据结构详解(5)——quicklist 张铁蕾
Redis内部数据结构详解(4)——ziplist 张铁蕾 redis设计与实现 redis开发与运维