[Redis][List]详细讲解
目录
- 0.前言
- 1.常用命令
- 1.LPUSH / RPUSH
- 2.LPUSHX / RPUSHX
- 3.LRANGE
- 4.LPOP / RPOP
- 5.LINDEX
- 6.LINSERT
- 7.LLEN
- 8.LREM
- 9.LTRIM
- 10.LSET
- 2.阻塞版本命令
- 0.是什么?
- 1.BLPOP / BRPOP
- 3.内部编码(旧版本,仅供参考)
- 1.ziplist(压缩链表)
- 2.linkedlist(链表)
- 3.quicklist(快速链表) -> (现行方案)
- 4.使用场景
- 1.消息队列
- 2.分频道的消息队列
- 3.微博 Timeline
0.前言
-
列表类型是⽤来存储多个有序的字符串,相当于数组/顺序表
- 内部实现类似于”双端队列“(deque)
-
列表中的每个字符串称为元素(element),⼀个列表最多可以存储 2 32 − 1 2^{32} - 1 232−1个元素
-
在Redis中,可以对列表两端插⼊(
push
)和弹出(pop
),还可以获取指定范围的元素列表、 获取指定索引下标的元素等
-
列表是⼀种⽐较灵活的数据结构,它可以充当栈和队列的⻆⾊,在实际开发上有很多应⽤场景
-
列表类型的特点:
- 列表中的元素是有序的:这意味着可以通过索引下标获取某个元素或者某个范围的元素列表
- 区分获取和删除的区别:
rem
是删除,会导致列表的长度改变,index
是获取元素,列表长度不会变化 - 列表中的元素是允许重复的,而hash这样的类型,
field
是不能重复的
1.常用命令
1.LPUSH / RPUSH
- 功能:将一个或者多个元素从左侧(头插) / 右侧(尾插)放入到
list
中- 注意:是按照键入在命令中的顺序,从左向右将命令中的元素插入到
list
中的- 例如:
LPUSH key 1 2 3 4
,那么最后list
呈现的结果为:4 3 2 1
- 例如:
- 注意:是按照键入在命令中的顺序,从左向右将命令中的元素插入到
- 语法:
LPUSH/RPUSH key element [element ...]
- 返回值:插入后
list
的长度 - 时间复杂度:只插入一个元素为 O ( 1 ) O(1) O(1),插入多个元素为 O ( N ) O(N) O(N),N为插入元素个数
2.LPUSHX / RPUSHX
- 功能:在
key
存在时,将⼀个或者多个元素从左侧(头插) / 右侧(尾插)放⼊到list
中,不存在,直接返回 - 语法:
LPUSHX/RPUSHX key element [element ...]
- 返回值:插入后
list
的长度 - 时间复杂度:只插入一个元素为 O ( 1 ) O(1) O(1),插入多个元素为 O ( N ) O(N) O(N),N为插入元素个数
3.LRANGE
- 功能:获取从
start
到end
区间的所有元素,左闭右闭,下标支持负数 - 语法:
LRANGE key start stop
- 返回值:指定区间的元素
- 时间复杂度: O ( N ) O(N) O(N)
- 注意:Redis会尽可能地获取到给定区间的元素,如果给定区间非法,比如超出下标,就会尽可能地获取到对应的内容
- Redis对于下标越界地处理方式类似于Python的切片操作
4.LPOP / RPOP
- 功能:从
list
左侧取出元素(头删) / 右侧取出元素(尾删) - 语法:
LPOP/RPOP key
- 返回值:取出的元素或者
nil
- 时间复杂度: O ( 1 ) O(1) O(1)
5.LINDEX
- 功能:获取从左数第
index
位置的元素 - 语法:
LINDEX key index
- 返回值:取出的元素或者
nil
- 时间复杂度: O ( N ) O(N) O(N)
6.LINSERT
- 功能:在特定位置插入元素
- 语法:
LINSERT key <BEFORE | AFTER> pivot element
- 返回值:插入后的
list
长度 - 时间复杂度: O ( N ) O(N) O(N),N表示列表的长度
- 注意:
LINSERT
进行插入的时候,要根据基准值,找到对应的位置,从左往右找,找到第一个符合基准值的位置即可
7.LLEN
- 功能:获取
list
长度 - 语法:
LLEN key
- 返回值:
list
的长度 - 时间复杂度: O ( 1 ) O(1) O(1)
8.LREM
- 功能:删除元素
- 语法:
LREM key count element
count
:要删除的个数count > 0
:从头向尾删count < 0
:从尾向头删count = 0
:删除全部元素
element
:要删除的值
- 返回值:删除元素的个数
- 时间复杂度: O ( N + M ) O(N + M) O(N+M),N为
list
的长度,M为要删除元素的个数
9.LTRIM
- 功能:保留
[start, stop
区间内的元素,区间外面的元素就直接被删除了 - 语法:
LTRIM key start stop
- 时间复杂度: O ( N ) O(N) O(N),N为要删除元素的个数
10.LSET
- 功能:根据下标,修改元素
- 语法:
LSET key index element
- 时间复杂度: O ( N ) O(N) O(N)
2.阻塞版本命令
0.是什么?
blpop
和brpop
是lpop
和rpop
的阻塞版本,和对应⾮阻塞版本的作⽤基本⼀致,除了:- 在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的
- 但如果列表中没有元素,⾮阻塞版本会立即返回
nil
- 但阻塞版本会根据
timeout
,阻塞⼀段时间,期间Redis可以执⾏其他命令,但要求执 ⾏该命令的客⼾端会表现为阻塞状态
- 但如果列表中没有元素,⾮阻塞版本会立即返回
- 命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元素,命令⽴即返回
- 如果多个客⼾端同时对⼀个键执⾏
pop
,则最先执⾏命令的客⼾端会得到弹出的元素
- 在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的
- 阻塞版本的
blpop
和非阻塞版本lpop
的区别:-
列表不为空时:
-
列表为空时,且5秒内没有新元素加入:
-
列表为空时,且5秒内有新元素加入:
-
1.BLPOP / BRPOP
- 功能:
LPOP
/RPOP
的阻塞版本 - 语法:`BLPOP/BRPOP key [key …] timeout
- 返回值:取出的元素或者
nil
- 时间复杂度: O ( 1 ) O(1) O(1)
3.内部编码(旧版本,仅供参考)
1.ziplist(压缩链表)
- 当列表的元素个数⼩于
list-max-ziplist-entries
配置(默认512个),同时列表中每个元素的⻓度都⼩于list-max-ziplist-value
配置(默认64字节)时,Redis会选⽤ziplist
来作为列表的内部编码实现来减少内存消耗
2.linkedlist(链表)
- 当列表类型⽆法满⾜ziplist的条件时,Redis会使⽤linkedlist作为列表的内部实现
3.quicklist(快速链表) -> (现行方案)
- 相当于是链表和压缩列表的结合:整体还是一个链表,链表的每个节点,是一个压缩列表
- 每个压缩列表,都不让它太大,同时再把多个压缩列表通过链式结构连起来
4.使用场景
1.消息队列
- Redis可以使⽤
lpush + brpop
命令组合实现经典的阻塞式⽣产者-消费者模型队列, ⽣产者客⼾端使⽤lpush
从列表左侧插⼊元素,多个消费者客⼾端使⽤brpop
命令阻塞式地从队列中 "争抢"队⾸元素。通过多个客⼾端来保证消费的负载均衡和⾼可⽤性
2.分频道的消息队列
- Redis同样使⽤
lpush + brpop
命令,但**通过不同的键模拟频道的概念,不同的消费者可以通过brpop
不同的键值,实现订阅不同频道的理念**
3.微博 Timeline
- 每个⽤⼾都有属于⾃⼰的Timeline(微博列表),现需要分⻚展⽰⽂章列表。此时可以考虑使⽤列表,因为列表不但是有序的,同时⽀持按照索引范围获取元素
- 每篇微博使⽤哈希结构存储,例如微博中3个属性:
title、timestamp、content
:hmset mblog:1 title xx timestamp 1476536196 content xxxxx ... hmset mblog:n title xx timestamp 1476536196 content xxxxx
- 向⽤⼾Timeline添加微博,
user::mblogs
作为微博的键:lpush user:1:mblogs mblog:1 mblog:3 ... lpush user:k:mblogs mblog:9
- 分⻚获取⽤⼾的Timeline,例如获取⽤⼾1的前10篇微博:
keylist = lrange user:1:mblogs 0 9 for key in keylist {hgetall key }
- 此⽅案在实际中可能存在两个问题:
- 1+n问题:如果每次分⻚获取的微博个数较多,需要执⾏多次
hgetall
操作,此时可以考虑使⽤pipeline
(流⽔线)模式批量提交命令,或者微博不采⽤哈希类型,⽽是使⽤序列化的字符串类型,使⽤mget
获取 - 分裂获取⽂章时,
lrange
在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分
- 1+n问题:如果每次分⻚获取的微博个数较多,需要执⾏多次