Redis基本类型学习之List(2)
前言
上篇文章中,我们介绍了Redis 列表的部分命令,本篇文章我们继续学习剩余命令,如果对上次命令淡忘的小伙伴可以先复习下哟。
LLEN
可用版本:>= 1.0.0
时间复杂度: O(1)
命令格式
LLEN key
命令描述
- 返回列表长度
- 如果列表不存在,返回0
- 如果key对应的value类型不是列表,返回error
返回值
整数:列表长度
示例
127.0.0.1:6379> lpush mylist world
(integer) 1
127.0.0.1:6379> lpush mylist hello
(integer) 2
127.0.0.1:6379> llen mylist
(integer) 2
# 不存在的key
127.0.0.1:6379> llen notexistkey
(integer) 0
# value类型不是列表,llen报错
127.0.0.1:6379> set key1 value1
127.0.0.1:6379> llen key1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
LREM
可用版本:>= 1.0.0
时间复杂度: O(N+M),N为列表长度,M为移除元素个数
命令格式
LREM key count element
命令描述
-
根据给定的参数
element
,移除列表中前count
个与element
相等的元素 - count>0: 从表头至表尾顺序移除
- count<0: 从表尾至表头顺序移除
- count=0: 移除列表中所有与element相等的元素
-
LREM list -2 "hello"
相当于移除list
中最后的两个hello
值
返回值
整数:被移除的元素个数(key不存在时,返回0)
示例
127.0.0.1:6379> rpush llenlist hello
(integer) 1
127.0.0.1:6379> rpush llenlist hello
(integer) 2
127.0.0.1:6379> rpush llenlist world
(integer) 3
127.0.0.1:6379> rpush llenlist hello
(integer) 4
127.0.0.1:6379> rpush llenlist hello
(integer) 5
127.0.0.1:6379> lrange llenlist 0 -1
1) "hello"
2) "hello"
3) "world"
4) "hello"
5) "hello"
# count>0,从表头开始移除
127.0.0.1:6379> lrem llenlist 1 hello
(integer) 1
127.0.0.1:6379> lrange llenlist 0 -1
1) "hello"
2) "world"
3) "hello"
4) "hello"
# count<0,从表尾开始移除
127.0.0.1:6379> lrem llenlist -1 hello
(integer) 1
127.0.0.1:6379> lrange llenlist 0 -1
1) "hello"
2) "world"
3) "hello"
# count=0,移除所有指定元素
127.0.0.1:6379> lrem llenlist 0 hello
(integer) 2
127.0.0.1:6379> lrange llenlist 0 -1
1) "world"
LINSERT
可用版本:>= 2.2.0
时间复杂度:O(N),N为遍历到指定元素pivot需访问的元素个数,pivot为表头则为O(1),表尾则为O(N)
命令格式
LINSERT key BEFORE|AFTER pivot element
命令描述
-
将一个元素值
element
,插入到列表中指定元素pivot
的之前或之后 - key不存在时,不执行任何操作
- 如果key对应的value类型不是列表,返回error
返回值
整数:插入元素后列表长度(如果未找到
pivot
,则返回 -1 )
示例
127.0.0.1:6379> rpush insertlist hello
(integer) 1
127.0.0.1:6379> rpush insertlist world
(integer) 2
127.0.0.1:6379> lrange insertlist 0 -1
1) "hello"
2) "world"
# before插入
127.0.0.1:6379> linsert insertlist before hello my
(integer) 3
127.0.0.1:6379> lrange insertlist 0 -1
1) "my"
2) "hello"
3) "world"
# after插入
127.0.0.1:6379> linsert insertlist after world end
(integer) 4
127.0.0.1:6379> lrange insertlist 0 -1
1) "my"
2) "hello"
3) "world"
4) "end"
# 指定pivot不存在
127.0.0.1:6379> linsert insertlist after name lifelmy
(integer) -1
LSET
可用版本:>= 1.0.0
时间复杂度:O(N),N为列表长度,如果设置的索引为表头或表尾,则复杂度为O(1)
命令格式
LSET key index element
命令描述
-
将索引为
index
的值设置为element
- 索引超出列表范围,返回error
返回值
字符串:“OK”
示例
127.0.0.1:6379> lpush mylist world
(integer) 1
127.0.0.1:6379> lpush mylist hello
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
# 设置索引0
127.0.0.1:6379> lset mylist 0 west
127.0.0.1:6379> lrange mylist 0 -1
1) "west"
2) "world"
# 超出列表范围
127.0.0.1:6379> lset mylist 3 test
(error) ERR index out of range
LPOS
可用版本:>= 6.0.6
时间复杂度:O(N),N为指定元素在列表中的个数。
命令格式
LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len]
命令描述
-
返回指定元素
element
在列表中的索引 - 没有指定可选参数时,默认从表头搜索至表尾,返回第一个匹配的索引;如果没有匹配到,返回 nil
> RPUSH mylist a b c 1 2 3 c c
> LPOS mylist c
-
可选参数
rank
:当列表中有多个匹配时,返回第rank
个匹配的索引。当rank
为正数时,从表头搜索至表尾返回索引;当rank
为负数时,从表尾搜索至表头 -
无论
rank
是正数或者负数,返回的索引始终是正数,即以0开始的索引值
# 返回第二个匹配的索引
> LPOS mylist c RANK 2
# 从表尾开始搜索,返回第一个匹配的索引
> LPOS mylist c RANK -1
-
可选参数
count
: 返回多个匹配到的索引;count=0 表示返回所有匹配索引
> LPOS mylist c COUNT 2
[2,6]
> LPOS mylist c COUNT 0
[2,6,7]
-
rank
+count
: 从第rank
个匹配到的索引开始,返回count
个索引
> LPOS mylist c RANK -1 COUNT 2
[7,6]
-
当没有匹配到值时,如果提供了可选参数
count
,返回空数组;否则返回nil
-
可选参数
MAXLEN
:至多访问列表中的MAXLEN
个元素进行比较,0
表示遍历所有元素比较
返回值
没有可选参数:返回元素索引 或者 nil (元素不存在)
提供count参数: 返回数组
示例
127.0.0.1:6379> rpush mykey a b c d a b c a
(integer) 8
# 默认从表头开始
127.0.0.1:6379> lpos mykey a
(integer) 0
# rank参数,从表头开始搜索,返回第二个匹配到的索引
127.0.0.1:6379> lpos mykey a rank 2
(integer) 4
# rank参数,从表尾开始搜索,返回第二个匹配的索引
127.0.0.1:6379> lpos mykey a rank -2
(integer) 4
# 指定count参数,返回多个匹配索引
127.0.0.1:6379> lpos mykey a count 3
1) (integer) 0
2) (integer) 4
3) (integer) 7
# rank+count, 从第二个匹配开始返回3个索引值(没有更多匹配了,只返回2个)
127.0.0.1:6379> lpos mykey a rank 2 count 3
1) (integer) 4
2) (integer) 7
LTRIM
可用版本:>= 1.0.0
时间复杂度:O(N),N为被移除的元素个数
命令格式
LTRIM key start stop
命令描述
- 对一个列表就行截取,保留给定范围内的元素,其余元素会被删除
-
start
、stop
可以是正数或者负数:正数表示从表头开始(索引为0),负数表示从表尾开始(索引为-1) - 给定参数超出列表范围不会报错:
-
start
超出列表范围或者start > stop
,结果是空列表 -
stop
超出列表范围,处理完元素就结束
返回值
字符串:“OK”
示例
127.0.0.1:6379> rpush mylist a b c d e
(integer) 5
127.0.0.1:6379> ltrim mylist 1 3
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "c"
3) "d"
127.0.0.1:6379> rpush anotherlist a b c d e
(integer) 5
127.0.0.1:6379> ltrim anotherlist 2 -2
127.0.0.1:6379> lrange anotherlist 0 -1
1) "c"
2) "d"
# start 超出范围
127.0.0.1:6379> ltrim anotherlist 3 -1
127.0.0.1:6379> lrange anotherlist 0 -1
(empty array)
BLPOP
可用版本:>= 2.0.0
时间复杂度:O(N),N为命令提供key的个数
6.0版本后,timeout参数为double浮点类型,而非integer整形
命令格式
BLPOP key [key ...] timeout
命令描述
-
BLPOP
是列表的阻塞式弹出原语 -
BLPOP
是LPOP
命令的阻塞版本,当给定的列表中没有元素可以弹出时,会一直阻塞住,直到列表中有元素可以弹出或者阻塞时间超时。当指定了多个列表时,会按照顺序弹出第一个非空的列表元素
- 非阻塞行为
- 当调用BPOP时,会 按照顺序 检查列表是否非空,返回第一个非空列表名称和其表头元素值。
-
当调用
BLPOP list1 list2 list3 0
时, 如果list1为空,list2 list3非空,那么会返回list2以及其表头元素
127.0.0.1:6379> rpush list1 hello
(integer) 1
127.0.0.1:6379> rpush list4 world
(integer) 1
127.0.0.1:6379> blpop list1 list2 list3 list4 0
1) "list1"
2) "hello"
127.0.0.1:6379> flushdb
127.0.0.1:6379> rpush list1 hello
(integer) 1
127.0.0.1:6379> rpush list4 world
(integer) 1
127.0.0.1:6379> blpop list2 list3 list4 list1 0
1) "list4"
2) "world"
- 阻塞行为
-
当给定的
key
都不存在时,即列表不存在,BLPOP
会一直阻塞,直到有其他客户端执行了LPUSH
或RPUSH
命令创建了列表 -
当列表中有值后,
BLPOP
就会返回列表名及弹出的元素值 -
timeout
指定阻塞时间,0
表示阻塞时间可以无限期延长,当指定非零参数时,阻塞时间到且列表中还无数据,返回nil
# 列表不存在,指定了timeout=10, 10s后返回nil
127.0.0.1:6379> blpop notexistslist 10
(nil)
(10.06s)
# 客户端1调用不存在的key,会一直阻塞
127.0.0.1:6379> blpop blocklist 0
1) "blocklist"
2) "name"
(48.18s)
# 另起一个客户端2,使用rpush插入数据,第一个客户端就会返回
127.0.0.1:6379> rpush blocklist name
(integer) 1
- 优先级问题
BLPOP命令指定了多个列表:按照从左到右的顺序,返回第一个非空列表中的元素。假设执行
BLPOP key1 key2 key3 key4 0
,
key2
和
key4
非空,那么会返回
key2
中的元素
多个客户端阻塞在同一个key上: 按照先来先服务的原则,返回给当前所有客户端中到达最早的一个客户端
# 客户端1
client1 > blpop priorlist 0
# 客户端2
clietn2 > blpop priorlist 0
# 客户端3
client3 > lpush priorlist lifelmy
(integer) 1
# 客户端1
client1 > blpop priorlist 0
1) "priorlist"
2) "lifelmy"
(83.44s)
-
多个元素
push
到列表问题
当执行
MULTI
EXEC
事务一次插入多个元素,或者使用类似
LPUSH mylist a b c
的命令一次插入多个元素时,
BLPOP
在不同版本中返回的数据是不一样的。
假如客户端A执行
BLPOP
被阻塞, 客户端B 执行
LPUSH
命令:
Client A: BLPOP foo 0
Client B: LPUSH foo a b c
Redis 2.4版本 : 返回
a
:当客户端B开始插入时,客户端A发现数据就会返回。因为第一个插入的是
a
,所以客户端A就返回
a
Redis 2.6及更高版本: 返回
c
:当客户端B将命令执行完后,数据才会对阻塞的客户端A可见。客户端B执行
LPUSH
后,
c
在表头,因此返回
c
# 6.2.4版本
cliet1 > blpop foo 0
1) "foo"
2) "3"
(26.31s)
client2 > lpush foo 1 2 3
(integer) 3
- 在MULTI/EXEC事务中使用BLPOP
在
MULTI、EXEC
事务中执行
BLPOP
没有意义,因为事务会阻塞其他命令执行
LPUSH
、
RPUSH
等命令,这样事务就一直阻塞在
BLPOP
无法返回,形成死锁。因此在事务中执行
BLPOP
,和执行
LPOP
的效果一样,不阻塞直接返回
# 1. 执行事务时,list中有数据直接返回
127.0.0.1:6379> flushdb
127.0.0.1:6379> lpush mylist 1
(integer) 1
127.0.0.1:6379> multi
127.0.0.1:6379(TX)> blpop mylist 0
QUEUED
127.0.0.1:6379(TX)> exec
1) 1) "mylist"
2) "1"
# 2. 列表不存在,事务中的 BLPOP 直接返回 nil
127.0.0.1:6379> multi
127.0.0.1:6379(TX)> blpop otherlist 0
QUEUED
127.0.0.1:6379(TX)> exec
1) (nil)
返回值
nil: 列表无数据且超时
数组:获取到数据,数组第一个元素为列表名,第二个为元素值
使用姿势:事件提醒
有时候,为了等待一个新元素到达数据中,需要使用轮询的方式对数据进行探查。
另一种更好的方式是,使用系统提供的阻塞原语,在新元素到达时立即进行处理,而新元素还没到达时,就一直阻塞住,避免轮询占用资源。BLPOP就可以解决这个问题。
比如我们要处理其他类型的数据,比如集合类型(Set)数据,而set没有阻塞原语,我们可以结合set和list一起使用
消费者: 使用
SPOP
从
set
中弹出元素,如果没有元素就阻塞到
BLPOP
命令。当有数据到来时,
BLPOP
停止阻塞,
SPOP
开始遍历
set
。
LOOP forever
WHILE SPOP(key) returns elements
... process elements ...
BLPOP helper_key
生产者:生产数据时,同时添加到
set
和
list
中
MULTI
SADD key element
LPUSH helper_key x
BRPOP
可用版本:>= 2.0.0
时间复杂度:O(N),N为命令提供key的个数
6.0版本后,timeout参数为double浮点类型,而非integer整形
命令格式
BRPOP key [key ...] timeout
命令描述
-
BRPOP
是列表的阻塞式弹出原语 -
BLPOP
是RPOP
命令的阻塞版本,当没有元素可以弹出时会一直阻塞;列表非空时从队尾弹出。 -
具体命令描述见
BLPOP
返回值
nil: 列表无数据且超时
数组:获取到数据,数组第一个元素为列表名,第二个为元素值
示例
127.0.0.1:6379> flushdb
127.0.0.1:6379> rpush list a b c
(integer) 3
127.0.0.1:6379> brpop list 0
1) "list"
2) "c"
BRPOPLPUSH
可用版本:>= 2.2.0
时间复杂度: O(1)
自6.2.0,该命令已废弃,建议使用 BLMOVE,见下个命令
命令格式
BRPOPLPUSH source destination timeout
命令描述
-
BRPOPLPUSH
是RPOPLPUSH
的阻塞版本 -
当
source
中无数据时,会一直阻塞;当有数据时,将source
表尾数据插入至destination
表头
返回值
nil: 操作超时
字符串:弹出的元素值
示例
127.0.0.1:6379> flushdb
# 客户端1 阻塞
client1 > brpoplpush sourcelist destlist 0
(13.87s)
# 客户端2 push数据后客户端1 返回
127.0.0.1:6379> lpush sourcelist a
(integer) 1
使用姿势
- 安全队列
当我们从列表1中弹出数据进行处理时,列表1会删除该数据,此时数据只在我们的客户端中存在,如果此时客户端突然宕机了,那么这条数据就会丢失。为了防止数据丢失,我们可以使用
RPOPLPUSH
或者
BRPOPLPUSH
,在弹出元素的同时将元素加入到另一个列表中用于备份,客户端处理完本条后将备份列表中的元素删除。
- 循环队列
通过使用相同的
key
作为
RPOPLPUSH
或
BRPOPLPUSH
命令的两个参数,客户端可以一个接一个地获取列表元,最终取得列表的所有元素,而不必像
LRANGE
命令那样一下子将所有列表元素都从服务器传送到客户端中。
以上的模式甚至在以下的两个情况下也能正常工作:
- 多个客户端同时对同一个列表进行旋转,它们获取不同的元素,直到所有元素都被读取完,之后又从头开始。
- 有其他客户端向列表尾部添加新元素。
比如我们要实现一个监控报警系统,多个服务器尽可能快速的检查多个网站是否正常,可以将多个网站存入列表中,多个服务器同时对该列表进行旋转处理。
BLMOVE
可用版本:>= 6.2.0
时间复杂度: O(1)
自6.2.0,该命令已废弃,建议使用 BLMOVE,见下个命令
命令格式
BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout
命令描述
-
BLMOVE
是LMOVE
的阻塞版本 -
当
source
中无数据时,会一直阻塞,其余操作和LMOVE
一致
返回值
nil: 操作超时
字符串:弹出的元素值
示例
# 客户端1阻塞
client 1> blmove sourcelist destlist right left 0
"hello"