> rpush numbers one two three
(integer) 3
> llen numbers
(integer) 3
> lpop numbers
"one"
> lpop numbers
"two"
> lpop numbers
"three"
> lpop numbers
(nil)
队列是先进先出的数据结构,常用语消息排队和异步逻辑处理。
> rpush numbers one two three
(integer) 3
> rpop numbers
"three"
> rpop numbers
"two"
> rpop numbers
"one"
> rpop numbers
(nil)
栈是先进先出的数据结构。用reids列表数据结构来做栈的业务场景并不多见。
这里的其他操作都是操作效率比较低的操作。
1、lindex
可以使用lindex对列表进行遍历,但是其性能将随着参数lindex的增大而变差
> rpush numbers one two three
(integer) 3
> lindex numbers 1
"two"
> lindex numbers -1 #-1表示倒数第一个元素,-2表示倒数第二个元数
"three"
2、lrange
可以使用lrange取出start_index到end_index间的元素。
> rpush numbers one two three
(integer) 3
> lrange numbers 1 -1
"two"
"three"
3、ltrim
可以使用ltrim来保留start_index到end_index间的值,区间外的统统被砍掉。故使用ltrim可以实现一个定长的链表。
> rpush numbers one two three
(integer) 3
> ltrim numbers 1 -1
OK
> lrange numbers 0 -1
"two"
"three"
> ltrim numbers 1 0 #这其实是清空了整个列表,因为区间范围长度为负
OK
> llen numbers
(integer) 0
redis 的 list的底层存储并不是一个简单的双向链表,而是一个称为“快速链表”(quicklist)的结构。该结构一般如下:
ziplist,即压缩表,是一块连续的内存,其中的所有元素是批次紧挨着的。其中的数据量是比较少的,当数据量比较大时就是多个ziplist,然后彼此间用双向指针串起来。这样就是quicklist结构。
该结构的优势为:
1、减少了内存的碎片化。
2、当数据比较少时,节省了指针的开销。(如,数据类型为int时,简单链表将会多出额外的两个指针)。
3、保持了快速插入和删除的性能。
除了list,zset和hash,在元素个数较少的时候也会采用ziplist进行存储。
1、结构
struct ziplist<T> {
int32 zlbytes; //整个压缩表所占的字节数
int32 zltail_offset; //最后一个元素距离压缩表起始位置的偏移量,用于快速定位到最后一个元素
int16 zllength; //元素个数
T[] entries; //元素内容列表,依次紧凑存储
int8 zlend; //结束符,值恒为0xFF
}
struct entry {
int<var> prevlen; //前一个entry的字节长度
int<var> encoding; //元素类型编码
optional byte[] content; //元素内容
}
prevlen是一个边长的整数,当字符串长度小于254(0xFE)时,其长度为一个字节;当字符串长度大于等于254时,其长度为5个字节(第一个字节为0xFE,剩下四个字节表示字符串长度)。
encoding表示存储元素的编码类型信息。为了节省存储空间,encoding的设计相当复杂。一般是前几位表示存储类型及规模,比如长度小于63为的短字符串(00xxxxxx,其中xxxxxx为字符串的长度),中等长度的字符串(01xxxxxx,xxxxxxxx),极小整数(1111xxxx)等,然后后面的位就开始表示元素的值了,比如极小整数(1111xxxx)中的xxxx就开始表示整数且范围时(0001-1101),即1-13。而0000、1110、1111在其他编码中已经被占用了。而真实值都应该是value-1,实际范围为(0-12)。
content 其类型为optional,即可选的,对于极小数而言,其内容已经内联到coding字段尾部了。
2、增加元素
由于ziplist是紧凑存储的,没有冗余空间,所以当插入新元素时,就需要用realloc扩展内存,并将之前的内容一次性拷贝到新的地址(有可能在原有地址上进行扩展)。正是由于这个特性,ziplist的元素不易过大过多,否则内存会消耗很大。
3、级联更新
由于entry中的prevlen字段的大小可能为1字节也可能为5字节,当某个entry经过更新,其大小可能会从小于254变为大于等于254时,其下一个entry的prevlen将会从1个字节变为5个字节,如果后面每个entry长度都为253,那么后面所有entry的prevlen都会从1个字节变为5个字节,故这个操作是比较小号计算资源的。
快速列表可以说是ziplist 和linkedlist的混合体。其将linkedlist按断切分,每一段使用ziplist让存储紧凑,多个ziplist间用双向指针串接起来,结构如下:
struct ziplist {
...
}
struct ziplist_compressed {
int32 size;
byte[] compressed_data;
}
struct quicklistNode {
quicklistNode* prev;
quicklistNode* next;
ziplist* zl; //指向压缩表
int32 size; //ziplist的字节总数
int16 count; //ziplist中的元数数量
int2 encoding; //存储形式2bit,原生字节数组还是LZF压缩存储
...
}
sturct quicklist {
quicklistNode* head;
quicklistNode* tail;
long count; //元素总数
int nodes; //ziplist节点个数
int compressDepth; //LZF算法压缩深度
}
其中ziplist结构可能会使用LZF算法进行压缩存储,这样可以进一步节约空间。
ziplist的长度
quicklist内部默认单个ziplist的长度为8KB,超过这个长度时,将会另起一个ziplist。如果需要自定义单个ziplist的长度,可以通过配置参数list-max-ziplist-size来决定。
压缩深度
quicklist默认的压缩深度为0,此时为不压缩。其配置参数为listcompress-depth。压缩深度为1时,quicklist的首尾两个ziplist不压缩,其他压缩。当压缩深度为2时,quicklist的首尾第一个和首尾第二个不压缩,其他的压缩。当压缩深度为1时,可以支持push/pop快速的进行操作。