Redis学习之数据类型List详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis学习之数据类型List详解相关的知识,希望对你有一定的参考价值。

本文和大家分享的主要是redis数据类型中的list相关内容,一起来看看吧,希望对大家学习redis有所帮助。

  list即链表,它是一个能维持数据先后顺序的列表,便于在表的两端追加和删除数据,中间位置的存取具有O(N)的时间复杂度,是一个双向链表。

一、内部原理

redis内部实现代码在quicklist.c(注释:A doubly linked list of ziplists)中,它确实是一个双向链表,并且是一个ziplist双向列表。

ziplist是什么?

一个经过特殊编码的的双向链表,它的设计目的是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是真正的二进制进行编码的,而不是编码成字符串序列。普通的双向链表每一项都独立的占用一块内存,各项之间用地址指针连接起来。这中方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist将列表的每一项存放在前后连续的地址空间内,一个大的ziplist整体占用一大块内存,它是一个列表,但不是一个链表。ziplist为了在细节上节省内存,对于值的存储采用了变长的编码方式,对于大的整数,就多一些字节来存储,对于小的少一些字节来存储。

ziplist的数据结构如下:

...

含义:

:32字节,表示ziplist占用的字符总数(本身占用4个字节)。

: 32字节,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数。的存在,使得我们可以很方便地找到最后一项,从而可以在ziplist尾端快速地执行push或pop操作。

: 16字节, 表示ziplist中数据项(entry)的个数。zllen字段因为只有16bit,所以可以表达的最大值为2^16-1。这里需要特别注意的是,如果ziplist中数据项个数超过了16bit能表达的最大值,ziplist仍然可以来表示。那怎么表示呢?这里做了这样的规定:如果小于等于2^16-2(也就是不等于2^16-1),那么就表示ziplist中数据项的个数;否则,也就是等于16bit全为1的情况,那么就不表示数据项个数了,这时候要想知道ziplist中数据项总数,那么必须对ziplist从头到尾遍历各个数据项,才能计数出来。

: 表示真正存放数据的数据项,长度不定。一个数据项(entry)也有它自己的内部结构。

: ziplist最后1个字节,是一个结束标记,值固定等于255。

entry的数据结构:


: 表示前一个数据项占用的总字节数。这个字段的用处是为了让ziplist能够从后向前遍历(从后一项的位置,只需向前偏移prevrawlen个字节,就找到了前一项)。这个字段采用变长编码。

。它有两种可能,或者是1个字节,或者是5个字节:

1. 如果前一个数据项占用字节数小于254,那么就只用一个字节来表示,这个字节的值就是前一个数据项的占用字节数。

2. 如果前一个数据项占用字节数大于等于254,那么就用5个字节来表示,其中第1个字节的值是254(作为这种情况的一个标记),而后面4个字节组成一个整型值,来真正存储前一个数据项的占用字节数。

: 表示当前数据项的数据长度(即部分的长度)。也采用变长编码。根据第一个字节的不同分为下面九种方式

|00pppppp| - 1 byte  String value with length less than or equal to 63 bytes (6 bits).

|01pppppp|qqqqqqqq| - 2 bytes  String value with length less than or equal to 16383 bytes (14 bits).

|10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes  String value with length greater than or equal to 16384 bytes.

|11000000| - 1 byte  Integer encoded as int16_t (2 bytes).

|11010000| - 1 byte  Integer encoded as int32_t (4 bytes).

|11100000| - 1 byte  Integer encoded as int64_t (8 bytes).

|11110000| - 1 byte  Integer encoded as 24 bit signed (3 bytes).

|11111110| - 1 byte  Integer encoded as 8 bit signed (1 byte).

|1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer. Unsigned integer from 0 to 12. The encoded value is actually from 1 to 13 because 0000 and 1111 can not be used, so 1 should be  subtracted from the encoded 4 bit value to obtain the right value.

|11111111| - End of ziplist.

quicklist是什么?

双向链表都是有多个node组成,而quicklist的每个节点都是一个ziplist。ziplist本身也是一个能维持数据项先后顺序的列表,而且内存是紧凑的,例如一个包含2个node的quicklist,如果每个节点的ziplist包含了四个数据项

那么对外表现就是8个数据项。quicklist的设计是一个空间和时间的折中,双向链表便于在表的两端进行push和pop操作,但是它的内存开销很大。开销如下

1.每个节点上除了要保存数据之外,还要额外的保存两个指针。

2.各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。

ziplist是一块连续的内存,所以存储效率很高。但是,它不利于修改操作,每次数据变动都会引发内存的realloc。一次realloc可能会导致大量的数据拷贝,进一步降低性能。

quicklist结合了双向链表和ziplist的优点,但是同样也存在一个问题,一个quicklist包含多长的ziplist合适呢?需要找到一个平衡点

1.ziplist太短,内存碎片越多。

2.ziplist太长,分配大块连续内存空间的难度就越大。

如果保持ziplist的合理长度,取决于具体的应用场景。redis提供了默认配置

list-max-ziplist-size -2

参数的含义解释,取正值时表示quicklist节点ziplist包含的数据项。取负值表示按照占用字节来限定quicklist节点ziplist的长度。

-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。

-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。

-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。

-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(默认值)

-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。

list设计最容易被访问的是列表两端的数据,中间的访问频率很低,如果符合这个场景,list还有一个配置,可以对中间节点进行压缩(采用的LZF——一种无损压缩算法),进一步节省内存。配置如下

list-compress-depth 0

含义:

0: 是个特殊值,表示都不压缩。这是Redis的默认值。

1: 表示quicklist两端各有1个节点不压缩,中间的节点压缩。

2: 表示quicklist两端各有2个节点不压缩,中间的节点压缩。

以此类推

quicklist数据结构:

/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.

* We use bit fields keep the quicklistNode at 32 bytes.

* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).

* encoding: 2 bits, RAW=1, LZF=2.

* container: 2 bits, NONE=1, ZIPLIST=2.

* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.

* attempted_compress: 1 bit, boolean, used for verifying during testing.

* extra: 12 bits, free for future use; pads out the remainder of 32 bits */typedef struct quicklistNode {

struct quicklistNode *prev; /*指向链表前一个节点的指针*/

struct quicklistNode *next; /*指向链表后一个节点的指针*/

unsigned char *zl;/*数据指针。如果当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。*/

unsigned int sz; /*表示zl指向的ziplist的总大小(包括zlbytes, zltail, zllen, zlend和各个数据项)。需要注意的是:如果ziplist被压缩了,那么这个sz的值仍然是压缩前的ziplist大小。/*

unsigned int count : 16;     /*  表示ziplist里面包含的数据项个数。 */

unsigned int encoding : 2;   /* RAW==1(未压缩) or LZF==2 (压缩了并采用LZF压缩算法)*/

unsigned int container : 2;  /* 使用的容器 NONE==1 or ZIPLIST==2(默认值) */

unsigned int recompress : 1; /* 我们使用类似lindex这样的命令查看了某一项本来压缩的数据时,需要把数据暂时解压,这时就设置recompress=1做一个标记,等有机会再把数据重新压缩 */

unsigned int attempted_compress : 1; /* node can’t compress; too small */

unsigned int extra : 10; /* 其他扩展字段(未使用) */

} quicklistNode;

/* quicklistLZF is a 4+N byte struct holding ’sz’ followed by ’compressed’.

* ’sz’ is byte length of ’compressed’ field.

* ’compressed’ is LZF data with total (compressed) length ’sz’

NOTE: uncompressed length is stored in quicklistNode->sz.

* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */typedef struct quicklistLZF {

unsigned int sz; /* 表示压缩后的ziplist大小*/

char compressed[]; /*是个柔性数组(flexible array member),存放压缩后的ziplist字节数组/*

} quicklistLZF;

/* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.

* ’count’ is the number of total entries.

* ’len’ is the number of quicklist nodes.

* ’compress’ is: -1 if compression disabled, otherwise it’s the number

*                of quicklistNodes to leave uncompressed at ends of quicklist.

* ’fill’ is the user-requested (or default) fill factor. */typedef struct quicklist {

quicklistNode *head; ?/*指向头节点(左侧第一个节点)的指针。*/

quicklistNode *tail; /*指向尾节点(右侧第一个节点)的指针。*/

unsigned long count;        /* quicklist节点的个数 */

unsigned int len;           /* number of quicklistNodes */

int fill : 16;              /* ziplist大小设置,存放list-max-ziplist-size参数的值 */

unsigned int compress : 16; /* 节点压缩深度设置,存放list-compress-depth参数的值 */

}

二:相关命令

lpush key value[value...]将一个或多个value插入到列表的表头,如果有多个   value  值,那么各个   value  值按从左到右的顺序依次插入到表头: 比如说,对空列表   mylist  执行命令   LPUSH mylist a b c ,列表的值将是   c b a  ,这等同于原子性地执行   LPUSH mylist a  、   LPUSH mylist b  和   LPUSH mylist c  三个命令。 如果   key  不存在,一个空列表会被创建并执行  lpush  操作。 当   key  存在但不是列表类型时,返回一个错误。

lpushx key value将值   value  插入到列表   key  的表头,若key不存在,不执行任何操作。

lpop key移除并返回列表key的头元素(后进先出),若key不存在返回nil。

blpop key[key...] timeoutlpop的阻塞版本,若给定列表中没有任何元素可供弹出时,链接会被blpop命令阻塞,直到等待超时(单位:秒)或发现可弹出元素时为止,若发现其中任何一个列表中有值则返回列表key和第一个元素的值。

rpush key value[value...]将一个或多个值   value  插入到列表   key  的表尾(最右边), 如果有多个   value  值,那么各个   value  值按从左到右的顺序依次插入到表尾:比如对一个空列表   mylist  执行   RPUSH mylist a b c  ,得出的结果列表为   a b c  ,等同于执行命令  RPUSH mylist a  、   RPUSH mylist b  、   RPUSH mylist c  。 如果   key  不存在,一个空列表会被创建并执行  Rpush  操作。 当   key  存在但不是列表类型时,返回一个错误。

rpushx key value将值   value  插入到列表   key  的表尾,若key不存在,不执行任何操作。

rpop key移除并返回列表的末尾,若key不存在则返回nil。

brpop key[key...] timeout它是   rpop 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被  brpop  命令阻塞,直到等待超时或发现可弹出元素为止。 当给定多个   key  参数时,按参数   key  的先后顺序依次检查各个列表,弹出第一个非空列表的尾.

rpoplpush source destination命令  rpoppush  在一个原子时间内,执行以下两个动作: 将列表  source  中的最后一个元素(尾元素)弹出,并返回给客户端。 将   source  弹出的元素插入到列表  destination  ,作为  destination  列表的的头元素。 如果   source  不存在,值   nil  被返回,并且不执行其他动作。 如果   source  和   destination  相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。

brpoplpush source destinationbrpoplpush 是  rpoplpush 的阻塞版本,当给定列表   source  不为空时, brpoplpush  的表现和  rpoplpush  一样。 当列表   source  为空时,  brpoplpush  命令将阻塞连接,直到等待超时,或有另一个客户端对   source  执行   lpush 或   rpush  命令为止。 超时参数   timeout  接受一个以秒为单位的数字作为值。超时参数设为   0  表示阻塞时间可以无限期延长(block indefinitely)

lset key index value将列表   key  下标为   index  的元素的值设置为   value  。 当   index  参数超出范围,或对一个空列表(   key  不存在)进行  lset 时,返回一个错误。

linsert key before|after pivot value将值   value  插入到列表   key  当中,位于值   pivot  之前或之后。 当   pivot  不存在于列表   key  时,不执行任何操作。 当   key  不存在时,   key  被视为空列表,不执行任何操作。 如果   key  不是列表类型,返回一个错误。

llen key返回列表   key  的长度。 如果   key  不存在,则   key  被解释为一个空列表,返回   0  .如果   key  不是列表类型,返回一个错误。

lindex key index返回列表   key  中,下标为   index  的元素。 下标(index)参数   start  和   stop  都以   0  为底,也就是说,以   0  表示列表的第一个元素,以   1  表示列表的第二个元素,以此类推。 你也可以使用负数下标,以   -1  表示列表的最后一个元素,   -2  表示列表的倒数第二个元素,以此类推。 如果   key  不是列表类型,返回一个错误。

lrange key start stop返回列表   key  中指定区间内的元素,区间以偏移量   start  和   stop  指定。 下标(index)参数   start  和   stop  都以   0  为底,也就是说,以   0  表示列表的第一个元素,以   1  表示列表的第二个元素,以此类推。 你也可以使用负数下标,以   -1  表示列表的最后一个元素,   -2  表示列表的倒数第二个元素,以此类推。

ltrim key start stop对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。下标(index)参数   start  和   stop  都以   0  为底,也就是说,以   0  表示列表的第一个元素,以   1  表示列表的第二个元素,以此类推。 你也可以使用负数下标,以   -1  表示列表的最后一个元素,   -2  表示列表的倒数第二个元素,以此类推。 当   key  不是列表类型时,返回一个错误。

lrem key count value移除列表中与value相等的元素,若count>0从左到右移除与count个与value相等的元素;若count<0从右向左移除count个与value相等的元素;若count==0移除所有与value相等的元素。

 

 

来源:博客园


以上是关于Redis学习之数据类型List详解的主要内容,如果未能解决你的问题,请参考以下文章

redis学习之——五大基本数据类型

Redis学习之二 数据类型和相关命令

Redis学习之列表类型详解

Redis 学习之简介及安装

Python学习之通用序列类型数据详解

python学习之第九课时--基本数据类型(list)