Redis-List篇

基础命令

  • lpush:将一个或多个值插入到列表头部
myRedis:0>lpush list a b c d
"4"

在这里插入图片描述

  • rpush:将一个或多个值插入到列表尾部
myRedis:0>rpush list2 a b c d
"4"

在这里插入图片描述

  • lpop :从表头弹出元素
myRedis:0>lpop list
"d"

myRedis:0>lpop list
"c"
  • rpop::从列表的尾部弹出元素
myRedis:0>rpop list2
"d"

myRedis:0>rpop list2
"c"

在这里插入图片描述

  • lindex :通过索引获取列表中的元素
myRedis:0>lindex list 0
"d"

myRedis:0>lindex list 10
null

myRedis:0>lindex list -1
"a"

  • lrange :查询指定范围的元素(不删除,只是查询)
myRedis:0>lrange list  0 2
 1)  "d"
 2)  "c"
 3)  "b"
  • llen : 获取列表的元素数量
myRedis:0>llen list
"4"
  • ltrim :对一个列表进行修剪,只保留指定区间内的元素,其他元素都会被删除
myRedis:0>ltrim list 0 2
"OK"

myRedis:0>llen list
"3"

myRedis:0>lrange list 0 3
 1)  "d"
 2)  "c"
 3)  "b"

适用场景

  • 消息队列
  • 栈存储

数据类型

  • linkedlist:双向链表
  • ziplist:特殊编码的压缩列表
  • quicklist:linkedlist与ziplist结合

redis在3.2版本之前使用的是linkedlist和ziplist两种编码存储,3.2版本之后统一使用quicklist存储,而quicklist是一种结合了linkedlist和ziplist的数据结构

linkedlist 解析

概念:linkedlist 是一个双向链接,在每个节点上除了保存value之外还会保存上一个节点和下一个节点的指针,并且linkedlist的空间分配并不是连续的

源码解析

linkedlist 的基础数据结构list存储的是链表的首节点和尾节点的指针,指向首尾节点的listNode,而listNode中存有下个节点和上一个节点的指针和string类型的value值,通过上下节点指针组成了一个数据链表

注意:首节点的prev为null,尾节点的next也是null

typedef struct list {
    listNode *head;//头节点
    listNode *tail;//尾节点
    void *(*dup)(void *ptr);//节点值复制函数
    void (*free)(void *ptr);//节点值释放函数
    int (*match)(void *ptr, void *key);//节点值对比函数
    unsigned long len;//节点数量
} list;


typedef struct listNode {
    struct listNode *prev;//前一个节点
    struct listNode *next;//后一个节点
    void *value;//值(字符串对象)
} listNode;
图解分析

在这里插入图片描述

ziplist解析

定义:一种压缩列表的数据结构,本质是为了节省内存而创建的
ziplist的内存分配连续,并且没有上下节点的指针,只存了上个节点和当前节点的长度,通过长度推算链表的元素在什么地方,是用时间置换空间的理念,比较适合字段个数少和字段值少

ziplist包含多个entry,每个entry包含一个字节数组或者一个整数
数据结构如下:

<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
字段属性含义空间长度
zlbytes压缩列表占用内存字节数4字节
zltail压缩列表尾节点距离压缩列表的起始地址有多少个字节4字节
zllen压缩列表中包含的节点数量2字节
entry压缩列表节点由实际内容决定
zlend标记压缩列表的末端1字节
源码解析
typedef struct zlentry {
    unsigned int prevrawlensize;//存储prevrawlen所占用的字节数
    unsigned int prevrawlen;//上一个链表节点长度
    unsigned int lensize;//存储len所占用的字节数
    unsigned int len;//当前节点的字节数
    unsigned int headersize;//当前链表节点的头部大小(prevrawlensize + lensize)
    unsigned char encoding;//编码方式
    unsigned char *p;//指向当前节点的起始位置
} zlentry;

// 以上结构只是接收数据结构,不是实际存储结构
图解分析

在这里插入图片描述

previous_entry_length长度定义

previous_entry_length属性是以字节为单位,记录压缩列表前一个节点的长度,可以为1个字节或者5个字节

  • 如果前一个节点的长度小于254字节,previous_entry_length属性的长度为1字节,保存前一个列表的长度
  • 如果前一个结点的长度大于等于254字节,previous_entry_length长度为5个字节,第一个字节会设置为0xFE(十进制254),之后的四个字节用于保存前一个字节的长度
ziplist遍历规则

根据以上规则,previous_entry_length保存了上一个节点的长度,如果知道ziplist中任意一个节点,即可知道上一个元素的位置,进而知道上上个元素的位置,因此最终可以便利到列表的头部首节点(ziplist是空间连续的,可以直接通过指针运算获取上个节点的位置)
在这里插入图片描述

ziplist连锁更新问题

时间置换空间,看起来可以很好的提高空间利用率,但是技术没有完美的情况,因为存储方式的原因,previous_entry_length有两种长度1个字节和5个字节,如果场景转换下:
场景:
假设有一个ziplist有N多个节点,并且每个节点都是251~254(为啥不是250,因为是我写的博客,我不喜欢)那么存储这个ziplist的entry.previous_entry_length 属性都是1个字节,而此时恰巧有一个300字节的新元素要插入ziplist的头部,会如何?

  • 原始结构如下**(length是表示previous_entry_length属性占用字节数,不是上个节点实际大小)**
    在这里插入图片描述

  • 新增元素后结构
    在这里插入图片描述

解析:

1. 第一节点长度为0是因为没有上一个节点了
2. 当表头插入一个新元素,entry1的previous_entry_length属性占用空间就由1字节更改成5字节
3. 当entry1更改占用空间大小后,entry1总长度已经大于254
4. 而此时entry2也要随之改变previous_entry_length的空间来保存entry1的长度
5. 依次类推,需要从entry1到entryN全部重新分配空间

以上就是连锁更新问题

综上:虽然ziplist的连锁更新问题会导致内存的重新分配,但是实际情况下触发频率极低,属于极限情况,并且如果ziplist只有十来个节点,即使重新分配内存,影响也比较小,可放心使用list

数据结构选取规则

3.2版本以前会存储数据会按照指定规则进行切换

  • 列表对象保存的所有字符串元素的长度都小于64字节
  • 列表对象保存的元素数量小于512个

满足以上两个条件,则用ziplist存储,否则会用linkedlist存储

quicklist解析

redis3.2版本对list也做了比较大的改动,对linkedlist 和ziplist进行了封装,引用了一种新的组合数据结构quicklist,简单理解为linkedlist 存储的value不再是简单的listNode ,节点的value存储的是ziplist

图解分析

在这里插入图片描述

quicklist的改进策略

quicklist采用linkedlist 和ziplist结合的方式同时具备了linkedlist双向列表的特性也具备了ziplist时间置换空间的特性,平衡了空间碎片(linkedlist空间不连续,存在空间碎片)和读写性能(ziplist读写性能不高)使用quicklist有两个极端情况需要注意

  • 如果每个节点ziplist的entry个数过少或者只有一个,此时quicklist相当于一个linkedlist
  • 如果每个节点ziplist的entry过多,也会导致quicklist的读写性能变差
ziplist中的entry个数可以用list-max-ziplist-size配置来修改:
list-max-ziplist-size 1
如果是正数则表示每个ziplist限制的entry数量
如果是负数则表示ziplist的大小
含义
-1最多只能为4KB
-2最多只能为8KB
-3最多只能为16KB
-4最多只能为32KB
-5最多只能为64KB

总结

以上介绍了list涉及到的三种数据结构,也对底层的linkedlist 和ziplist做了图解分析,其中针对字段少空间少的数据结构ziplist,也将在下一篇章hash中涉及,这种时间置换空间的理念也是redis为了将内存最大化利用的体现

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值