Redis对象——列表(list)

 


 

一. 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、栈、队列、阻塞队列

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值