List列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向List列表添加元素。
列表的最大长度是2^32-1,也就是每个列表支持超过40亿个元素。
底层数据结构是由双向链表或压缩列表实现。
512
个(默认值,可由 list-max-ziplist-entries
配置),列表每个元素的值都小于 64
字节(默认值,可由 list-max-ziplist-value
配置),Redis 会使用压缩列表作为 List 类型的底层数据结构;在Redis3.2版本之后,List数据类型底层数据结构就只由quicklist实现。
LPUSH key element [element ...]
将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
127.0.0.1:6379> lpush k1 1 2 3 4
(integer) 4
127.0.0.1:6379> lrange k1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
RPUSH key element [element ...]
将一个或多个值value插入到key列表的表尾(最右边)
127.0.0.1:6379> rpush k2 1 2 3 4
(integer) 4
127.0.0.1:6379> lrange k2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
LRANGE key start stop
返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
LPOP key [count]
移除并返回key列表的头元素,或者移除count个头元素
127.0.0.1:6379> lrange k2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> LPOP k2 2
1) "1"
2) "2"
127.0.0.1:6379> lrange k2 0 -1
1) "3"
2) "4"
127.0.0.1:6379> LPOP k2
"3"
127.0.0.1:6379> lrange k2 0 -1
1) "4"
RPOP key [count]
移除并返回key列表的尾元素
127.0.0.1:6379> lrange k2 0 -1
1) "4"
2) "k"
3) "u"
4) "a"
5) "n"
6) "g"
127.0.0.1:6379> RPOP k2
"g"
127.0.0.1:6379> lrange k2 0 -1
1) "4"
2) "k"
3) "u"
4) "a"
5) "n"
127.0.0.1:6379> RPOP k2 2
1) "n"
2) "a"
127.0.0.1:6379> lrange k2 0 -1
1) "4"
2) "k"
3) "u"
LINDEX key index
按照索引下标获得元素(从上到下)
127.0.0.1:6379> LRANGE k1 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> LINDEX k1 1
"3"
127.0.0.1:6379> LINDEX k1 0
"4"
127.0.0.1:6379> LINDEX k1 2
"2"
LLEN key
获取列表中元素的个数
127.0.0.1:6379> LRANGE k1 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> LLEN k1
(integer) 3
LREM key count element
删除count 个值等于element的元素,返回值为实际删除的数量。
count等于0时,表示删除全部给定的值。
127.0.0.1:6379> LRANGE k3 0 -1
1) "v4"
2) "v3"
3) "v3"
4) "v2"
5) "v2"
6) "v1"
7) "v1"
8) "v1"
127.0.0.1:6379> LREM k3 2 v1
(integer) 2
127.0.0.1:6379> LRANGE k3 0 -1
1) "v4"
2) "v3"
3) "v3"
4) "v2"
5) "v2"
6) "v1"
127.0.0.1:6379> LREM k3 0 v2
(integer) 2
127.0.0.1:6379> LRANGE k3 0 -1
1) "v4"
2) "v3"
3) "v3"
4) "v1"
LTRIM key start stop
截取指定索引区间的元素,然后赋值给key
127.0.0.1:6379> LRANGE k3 0 -1
1) "v4"
2) "v3"
3) "v3"
4) "v1"
127.0.0.1:6379> LTRIM k3 1 3
OK
127.0.0.1:6379> LRANGE k3 0 -1
1) "v3"
2) "v3"
3) "v1"
RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
127.0.0.1:6379> LRANGE k2 0 -1
1) "g"
2) "n"
3) "a"
4) "u"
5) "k"
127.0.0.1:6379> LRANGE k1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> RPOPLPUSH k1 k2
"1"
127.0.0.1:6379> LRANGE k2 0 -1
1) "1"
2) "g"
3) "n"
4) "a"
5) "u"
6) "k"
127.0.0.1:6379> LRANGE k1 0 -1
1) "4"
2) "3"
3) "2"
LSET key index element
设置索引为index的元素值为element。
127.0.0.1:6379> LSET k3 0 v5
OK
127.0.0.1:6379> LRANGE k3 0 -1
1) "v5"
2) "v3"
3) "v1"
LINSERT key
在list某个已有值pivot
的前后再添加具体值element
127.0.0.1:6379> LRANGE k3 0 -1
1) "v5"
2) "v3"
3) "v1"
127.0.0.1:6379> LINSERT k3 before v3 v4
(integer) 4
127.0.0.1:6379> LRANGE k3 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v1"
127.0.0.1:6379> LINSERT k3 after v1 v6
(integer) 5
127.0.0.1:6379> LRANGE k3 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v1"
5) "v6"
BLPOP key [key ...] timeout
从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
127.0.0.1:6379> lrange k2 0 -1
1) "u"
127.0.0.1:6379> BLPOP k2 0
1) "k2"
2) "u"
127.0.0.1:6379> BLPOP k2 0 #k2中没有元素,这时执行BLPOP,同时timeout为0,导致一直阻塞
BRPOP key [key ...] timeout
从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
消息队列在存取消息时,必须要满足三个需求,分别是消息保序、处理重复的消息和保证消息可靠性。
Redis 的 List 和 Stream 两种数据类型,就可以满足消息队列的这三个需求。我们先来了解下基于 List 的消息队列实现方法,后面在介绍 Stream 数据类型时候,在详细说说 Stream。
1、如何满足消息保序需求?
List 本身就是按先进先出的顺序对数据进行存取的,所以,如果使用 List 作为消息队列保存消息的话,就已经能满足消息保序的需求了。
List 可以使用 LPUSH + RPOP (或者反过来,RPUSH+LPOP)命令实现消息队列。
LPUSH key value[value...]
将消息插入到队列的头部,如果 key 不存在则会创建一个空的队列再插入消息。RPOP key
依次读取队列的消息,先进先出。在生产者往 List 中写入数据时,List 并不会主动地通知消费者有新消息写入,如果消费者想要及时处理消息,就需要在程序中不停地调用 RPOP
命令(比如使用一个while(1)循环)。如果有新消息写入,RPOP命令就会返回结果,否则,RPOP命令返回空值,再继续循环。
所以,即使没有新消息写入List,消费者也要不停地调用 RPOP 命令,这就会导致消费者程序的 CPU 一直消耗在执行 RPOP 命令上,带来不必要的性能损失。
为了解决这个问题,Redis提供了 BRPOP 命令。BRPOP命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。和消费者程序自己不停地调用RPOP命令相比,这种方式能节省CPU开销。
2、如何处理重复的消息?
消费者要实现重复消息的判断,需要 2 个方面的要求:
但是 List 并不会为每个消息生成 ID 号,所以我们需要自行为每个消息生成一个全局唯一ID,生成之后,我们在用 LPUSH 命令把消息插入 List 时,需要在消息中包含这个全局唯一 ID。
例如,我们执行以下命令,就把一条全局 ID 为 111000102、库存量为 99 的消息插入了消息队列:
> LPUSH mq "111000102:stock:99"
(integer) 1
3、如何保证消息可靠性?
当消费者程序从 List 中读取一条消息后,List 就不会再留存这条消息了。所以,如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从 List 中读取消息了。
为了留存消息,List 类型提供了 BRPOPLPUSH
命令,这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存。
这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份 List 中重新读取消息并进行处理了。
好了,到这里可以知道基于 List 类型的消息队列,满足消息队列的三大需求(消息保序、处理重复的消息和保证消息可靠性)。
但是,在用 List 做消息队列时,如果生产者消息发送很快,而消费者处理消息的速度比较慢,这就导致 List 中的消息越积越多,给 Redis 的内存带来很大压力。
要解决这个问题,就要启动多个消费者程序组成一个消费组,一起分担处理 List 中的消息。但是,List 类型并不支持消费组的实现。
Redis 从 5.0 版本开始提供的 Stream 数据类型了,Stream 同样能够满足消息队列的三大需求,而且它还支持「消费组」形式的消息读取。