Redis中的List数据类型
1435597771 ·
Redis List 是什么
在redis中, List类型是字符串列表,按照插入顺序排序,可以从头部或尾部向list中添加元素,一个list类型的列表最多可以存储2^32-1个元素。
在Redis中,可以对列表的两端插入和弹出,还可以获取指定范围的元素列表,获取指定索引下标的元素,可以充当栈和队列的角色。
列表有以下特点:
列表中的元素是按照插入顺序排序的
列表中的元素是可以重复的
list的底层结构
list的底层结构由quicklist(快速链表)组成,他的本质上是一个双向链表,但是他的每一个节点中存储了一个listpack(紧凑列表),我们分别来介绍:
listpack
listpack是一块连续的空间,作为一个数组用来紧凑的存储序列化的字符串或者数字,他的结构由三个字段组成:
encoding: 表示content的编码,有字符串编码和整数编码两种
content: 表示实际存储的内容
length:表示当前节点的总长度,被放在了当前节点的最后。
我们从一个例子中来理解ListPack结构,
LPUSH animals 1, dog, cat
我们往animals这个list中 push了 1,dog,cat。 那么在链表中就构建了 cat,dog,1 顺序的链表,想一想,他用了几个listpack存储了这三个元素?三个还是一个listpack?
答案是指用了1个listpack来存储这三个元素。那么这三个元素在listpack中是如何存储的呢?
我们先来看1这个元素,encoding对整数编码有3类,分别是:
7位小整数:encoding占用1字节,以0开头,剩下7位用来存储数据,因此可以存储0~127范围内的数据,同时这意味着content为空,因为数据直接存储在encoding中了
13位整数:encoding占用2字节,content依然被设置为0字节。
16/24/32/64位整数:那么就是encoding占1字节,content分别占用不同字节的空间。
那么1这个元素是7位小整数,所以他的encoding占8位,1字节,然后他的length也占用8位,也是1字节,那么这就是1元素在listpack的存储形式。
接下来我们看dog和cat这两个元素,encoding对给字符串分成了3类,分别是
短字符串:encoding占1字节,前两位是10,后六位用来表示字符串的长度(最大可表示63字节)
中字符串:encoding占2个字节,前四位是1110,后面12位记录字符串的长度(最大4095字节)
长字符串:encoding占5个字节,编码占用1字节的固定前缀,后面四个字节表示字符串的长度(最大4GB的字符串)
那么dog和cat两个元素都是短字符串,都占用3个字节。那么就是encoding占用1字节,content占用3字节,length占用1字节。
他们三个元素按照顺序,紧紧存放在一个listpack中。 那么一个listpack可以存放多少个字符串呢?他的大小由list-max-listpack-size 来控制,默认为-2代表每个listpack占用的总内存不超过8kb
quicklist
我们上面讲过,quicklist是由listpack组成的双向链表。quicklist中有四个字段需要注意:
head和tail:分别指向链表的头节点和尾节点
count:所有节点的Entry总数(即所有listpack中存储的所有字符串的个数)
len:节点的数量
quicklist的节点的结构是一个叫做quicklistnode的结构体,他也有几个字符需要注意:
prev和next:双向链表指针,分别指向前一个和后一个节点
zl:指向listpack的指针
sz:记录这个listpack占用率多少字节的内存
count:记录这个listpack一共塞了多少个entry(元素)
那么我们已经知道里quicklist实际上就是由listpack组成的双向链表,那么数据是如何push进去的呢?我们前面讲过listpack也是可以存入多个元素的,还记得吗?
那么还按照我们上面的例子讲解:在LPUSH的时候,找到list的head节点,观察head节点中的listpack是否超过8kb(默认配置),如果没有超过,那么redis不会创建新的quicklistnode,而是给元素存入同一个listpack中,也就是我们上面讲的,cat,dog,1都在一个listpack中。
如果超了那么就要创建一个新的quicklistnode了,Redis 会把全局的 head 指向这个新节点,并把新节点的 next 指针与旧 head 节点连起来(旧节点的 prev 指向新节点)。
所以对于list的LPUSH和RPUSH也就是意味着从head还是从tail中操作数据
常用命令
LPUSH key [value...] 将一个值或者多个值按照顺序插入key的最左边
RPUSH key [value...] 将一个值或者多个值按照顺序插入key的最右边
LPOP key 移除并返回最左边的数据
RPOP key 移除并返回最右边的数据
LRANGE key start stop: 返回列表中key中指定区域内的元素
BLPOP key [key...] timeout: 从key列表中 左边弹出一个元素,如果没有就阻塞timeout秒
BRPOP key [key...] timeout: 从key列表中 右边弹出一个元素,如果没有就阻塞timeout秒
应用场景
消息队列:因为 List 是严格按照顺序存储的,可以利用 LPUSH 生产者写入,RPOP(或阻塞版本的 BRPOP)消费者读取,天然实现先进先出(FIFO)的队列模式。虽然大型分布式系统更倾向于使用 Kafka 或 RabbitMQ,但对于中小型、轻量级的异步任务,Redis List 是一种极低成本且高效的消息队列实现方案。