一. list是什么?
list是redis的一种基础数据结构,内部使用双向链表(quicklist)实现,所以向列表两端添加元素的时间复杂度为O(1), 获取越接近两端的元素速度就越快。
二. list存储结构
1. Redis 3.2之前
list类型的value对象内部以linkedlist或者ziplist来实现。
当list的元素个数和单个元素的长度比较小的时候,Redis会采用 ziplist 压缩列表 来实现来减少内存占用。否则就会采用 linkedlist 双向链表结构。
这两种存储方式都有优缺点:
双向链表在链表两端进行push和pop操作,在插入节点上复杂度比较低,但是内存开销比较大;
ziplist存储在一段连续的内存上,所以存储效率很高,但是插入和删除都需要频繁申请和释放内存;
2. Redis 3.2之后的版本
使用quicklist,quicklist也是一个双向链表,只是列表的每个节点都是一个ziplist,其实就是linkedlist和ziplist的结合,quicklist 中每个节点ziplist都能够存储多个数据元素。quicklist保存了head和tail结点,可以很方便地对两端进行操作。
ziplist
先聊一下ziplist:压缩列表是redis为了节省内存而开发的,由一系列特殊编码的连续内存块组成的有序数据结构。
在list/hash/zset中都有使用。
先说ziplist的结构,如图所示,
[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
| | | | | |
zlbytes zltail entries "2" "5" end
上面是一个ziplist的结构,有两个entry,分别是“2”和“5”。
zlbyte:占4个字节,表示ziplist的长度;
zltail:占4个字节,表示最后一个entry的偏移量,即到“5”的偏移量,为0x0c;
entries:占2个字节,entry的个数,这里总共有两个entry;
entry:entry中保存了前一个entry的长度,便于方向遍历;
end:0xff,结束标记位。
ziplist entry的结构,详细说明见注释:
/* We use this function to receive information about a ziplist entry.
* Note that this is not how the data is actually encoded, is just what we
* get filled by a function in order to operate more easily. */
typedef struct zlentry { // 压缩列表节点
// prevrawlensize是指prevrawlen的大小,有1字节和5字节两种
unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
// prevrawlen是前一个zlentry节点的长度
unsigned int prevrawlen; /* Previous entry len. */
// lensize为编码len所需的字节大小
unsigned int lensize; /* Bytes used to encode this entry type/len.
For example strings have a 1, 2 or 5 bytes
header. Integers always use a single byte.*/
// len为当前节点长度
unsigned int len; /* Bytes used to represent the actual entry.
For strings this is just the string length
while for integers it is 1, 2, 3, 4, 8 or
0 (for 4 bit immediate) depending on the
number range. */
// 当前节点的header大小
unsigned int headersize; /* prevrawlensize + lensize. */
// 节点的编码方式
unsigned char encoding; /* Set to ZIP_STR_* or ZIP_INT_* depending on
the entry encoding. However for 4 bits
immediate integers this can assume a range
of values and must be range-checked. */
// 指向节点的指针
unsigned char *p; /* Pointer to the very start of the entry, that
is, this points to prev-entry-len field. */
} zlentry;
zlentry可以简单的理解由三部分组成:prevlength、encoding、data
- prevlength: 记录上一个节点的长度,为了方便反向遍历ziplist
- encoding: 当前节点的编码规则.
- data: 当前节点的值,可以是数字或字符串
- 若entry前8位小于254,则这8位就表示上一个节点的长度
- 若entry的前8位等于254,则意味着上一个节点的长度无法用8位表示,后面32位才是真实的prevlength。用254 不用255(11111111)作为分界是因为255是zlend的值,它用于判断ziplist是否到达尾部。
quicklist
quicklist.c - A doubly linked list of ziplists //ziplist 的双向链表
/* Minimum ziplist size in bytes for attempting compression. */
#define MIN_COMPRESS_BYTES 48
/* Minimum size reduction in bytes to store compressed quicklistNode data.
* This also prevents us from storing compression if the compression
* resulted in a larger size than the original data. */
#define MIN_COMPRESS_IMPROVE 8
从quicklist.c中可以看到,列表中数据是否压缩的依据:
* 元素长度小于 48,不压缩;
* 元素压缩前后长度差不超过 8,不压缩;
quicklistNode的结构说明如下,可以很清晰地看到每个节点就是一个ziplist:
/* 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 temporary decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 10 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的地址 */
unsigned int sz; /* ziplist 大小(字节) */
unsigned int count : 16; /* ziplist 元素个数 */
unsigned int encoding : 2; /* 编码类型,RAW==1 or LZF==2 */
unsigned int container : 2; /* 容器类型, NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
quicklist结构说明如下:
/* quicklist is a 40 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: 0 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.
* 'bookmakrs are an optional feature that is used by realloc this struct,
* so that they don't consume memory when not used. */
typedef struct quicklist {
quicklistNode *head; /* 头结点 */
quicklistNode *tail; /* 尾结点 */
unsigned long count; /* 在所有ziplist中entry的个数总和 */
unsigned long len; /* quicklistNodes的个数 */
int fill : QL_FILL_BITS; /* ziplist大小限定,由server.list_max_ziplist_size给定 */
unsigned int compress : QL_COMP_BITS; /* 节点压缩深度设置,由server.list-compress-depth给定,0表示关闭压缩 */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
三、基础命令
# 从队列的左侧入队一个或多个元素
LPUSH key value [value ...]
# 从队列的左侧弹出一个元素
LPOP key
# 从队列的右侧入队一个或多个元素
RPUSH key value [value ...]
# 从队列的右侧弹出一个元素
RPOP key
# 获取list从start到end之间的元素[start, end]
LRANGE key start end
# 移除从表头到表尾,最先发现的count 个 value
LREM key count value
# RPOP 的阻塞版本,当list中没有元素,该命令会阻塞
BRPOP key timeout
# 裁剪最近5条记录
ltrim key 0 4
四、应用场景
1、最新消息列表等功能,如微博、朋友圈、点赞等
2、消息队列(不建议使用,redis意外关闭时,可能丢失数据)
3、栈、队列、阻塞队列