Appearance
Redis 列表 - List
Redis 列表简介
Redis 列表是字符串值的链接列表。 Redis 列表通常用于:
- 实施堆栈和队列。
- 为后台 worker 系统构建队列管理。
基本命令
LPUSH
将新元素添加到列表的头部;RPUSH 添加到尾部。LPOP
从列表的头部删除并返回一个元素;RPOP 执行相同的操作,但从列表的尾部开始。LLEN
返回列表的长度。LMOVE
以原子方式将元素从一个列表移动到另一个列表。LRANGE
从列表中提取一系列元素。LTRIM
将列表缩减到指定的元素范围。
阻止命令
列表支持多个阻止命令。 例如:
BLPOP
从列表的头部删除并返回一个元素。 如果列表为空,则命令将阻止,直到元素可用或达到指定的超时。BLMOVE
以原子方式将元素从源列表移动到目标列表。 如果源列表为空,则命令将阻塞,直到有新元素可用。
请参阅完整的 list 命令系列。
例子
- 将列表视为队列(先进先出):
bash
> LPUSH bikes:repairs bike:1
(integer) 1
> LPUSH bikes:repairs bike:2
(integer) 2
> RPOP bikes:repairs
"bike:1"
> RPOP bikes:repairs
"bike:2"
python
res1 = r.lpush("bikes:repairs", "bike:1")
print(res1) # >>> 1
res2 = r.lpush("bikes:repairs", "bike:2")
print(res2) # >>> 2
res3 = r.rpop("bikes:repairs")
print(res3) # >>> bike:1
res4 = r.rpop("bikes:repairs")
print(res4) # >>> bike:2
js
const res1 = await client.lPush('bikes:repairs', 'bike:1');
console.log(res1); // 1
const res2 = await client.lPush('bikes:repairs', 'bike:2');
console.log(res2); // 2
const res3 = await client.rPop('bikes:repairs');
console.log(res3); // bike:1
const res4 = await client.rPop('bikes:repairs');
console.log(res4); // bike:2
java
long res1 = jedis.lpush("bikes:repairs", "bike:1");
System.out.println(res1); // >>> 1
long res2 = jedis.lpush("bikes:repairs", "bike:2");
System.out.println(res2); // >>> 2
String res3 = jedis.rpop("bikes:repairs");
System.out.println(res3); // >>> bike:1
String res4 = jedis.rpop("bikes:repairs");
System.out.println(res4); // >>> bike:2
go
res1, err := rdb.LPush(ctx, "bikes:repairs", "bike:1").Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // >>> 1
res2, err := rdb.LPush(ctx, "bikes:repairs", "bike:2").Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // >>> 2
res3, err := rdb.RPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // >>> bike:1
res4, err := rdb.RPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res4) // >>> bike:2
csharp
long res1 = db.ListLeftPush("bikes:repairs", "bike:1");
Console.WriteLine(res1); // >>> 1
long res2 = db.ListLeftPush("bikes:repairs", "bike:2");
Console.WriteLine(res2); // >>> 2
RedisValue res3 = db.ListRightPop("bikes:repairs");
Console.WriteLine(res3); // >>> "bike:1"
RedisValue res4 = db.ListRightPop("bikes:repairs");
Console.WriteLine(res4); // >>> "bike:2"
- 将列表视为堆栈(先进后出):
bash
> LPUSH bikes:repairs bike:1
(integer) 1
> LPUSH bikes:repairs bike:2
(integer) 2
> LPOP bikes:repairs
"bike:2"
> LPOP bikes:repairs
"bike:1"
python
res5 = r.lpush("bikes:repairs", "bike:1")
print(res5) # >>> 1
res6 = r.lpush("bikes:repairs", "bike:2")
print(res6) # >>> 2
res7 = r.lpop("bikes:repairs")
print(res7) # >>> bike:2
res8 = r.lpop("bikes:repairs")
print(res8) # >>> bike:1
js
const res5 = await client.lPush('bikes:repairs', 'bike:1');
console.log(res5); // 1
const res6 = await client.lPush('bikes:repairs', 'bike:2');
console.log(res6); // 2
const res7 = await client.lPop('bikes:repairs');
console.log(res7); // bike:2
const res8 = await client.lPop('bikes:repairs');
console.log(res8); // bike:1
java
long res5 = jedis.lpush("bikes:repairs", "bike:1");
System.out.println(res5); // >>> 1
long res6 = jedis.lpush("bikes:repairs", "bike:2");
System.out.println(res6); // >>> 2
String res7 = jedis.lpop("bikes:repairs");
System.out.println(res7); // >>> bike:2
String res8 = jedis.lpop("bikes:repairs");
System.out.println(res8); // >>> bike:1
go
res5, err := rdb.LPush(ctx, "bikes:repairs", "bike:1").Result()
if err != nil {
panic(err)
}
fmt.Println(res5) // >>> 1
res6, err := rdb.LPush(ctx, "bikes:repairs", "bike:2").Result()
if err != nil {
panic(err)
}
fmt.Println(res6) // >>> 2
res7, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res7) // >>> bike:2
res8, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res8) // >>> bike:1
csharp
long res9 = db.ListLength("bikes:repairs");
Console.WriteLine(res9); // >>> 0
- 检查列表的长度:
bash
> LLEN bikes:repairs
(integer) 0
python
res9 = r.llen("bikes:repairs")
print(res9) # >>> 0
js
const res9 = await client.lLen('bikes:repairs');
console.log(res9); // 0
java
long res9 = jedis.llen("bikes:repairs");
System.out.println(res9); // >>> 0
go
res9, err := rdb.LLen(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res9) // >>> 0
csharp
long res9 = db.ListLength("bikes:repairs");
Console.WriteLine(res9); // >>> 0
- 从一个列表中原子地弹出一个元素并推送到另一个列表:
bash
> LPUSH bikes:repairs bike:1
(integer) 1
> LPUSH bikes:repairs bike:2
(integer) 2
> LMOVE bikes:repairs bikes:finished LEFT LEFT
"bike:2"
> LRANGE bikes:repairs 0 -1
1) "bike:1"
> LRANGE bikes:finished 0 -1
1) "bike:2"
python
res10 = r.lpush("bikes:repairs", "bike:1")
print(res10) # >>> 1
res11 = r.lpush("bikes:repairs", "bike:2")
print(res11) # >>> 2
res12 = r.lmove("bikes:repairs", "bikes:finished", "LEFT", "LEFT")
print(res12) # >>> 'bike:2'
res13 = r.lrange("bikes:repairs", 0, -1)
print(res13) # >>> ['bike:1']
res14 = r.lrange("bikes:finished", 0, -1)
print(res14) # >>> ['bike:2']
js
const res10 = await client.lPush('bikes:repairs', 'bike:1');
console.log(res10); // 1
const res11 = await client.lPush('bikes:repairs', 'bike:2');
console.log(res11); // 2
const res12 = await client.lMove('bikes:repairs', 'bikes:finished', 'LEFT', 'LEFT');
console.log(res12); // 'bike:2'
const res13 = await client.lRange('bikes:repairs', 0, -1);
console.log(res13); // ['bike:1']
const res14 = await client.lRange('bikes:finished', 0, -1);
console.log(res14); // ['bike:2']
java
long res10 = jedis.lpush("bikes:repairs", "bike:1");
System.out.println(res10); // >>> 1
long res11 = jedis.lpush("bikes:repairs", "bike:2");
System.out.println(res11); // >>> 2
String res12 = jedis.lmove("bikes:repairs", "bikes:finished", ListDirection.LEFT, ListDirection.LEFT);
System.out.println(res12); // >>> bike:2
List<String> res13 = jedis.lrange("bikes:repairs", 0, -1);
System.out.println(res13); // >>> [bike:1]
List<String> res14 = jedis.lrange("bikes:finished", 0, -1);
System.out.println(res14); // >>> [bike:2]
go
res10, err := rdb.LPush(ctx, "bikes:repairs", "bike:1").Result()
if err != nil {
panic(err)
}
fmt.Println(res10) // >>> 1
res11, err := rdb.LPush(ctx, "bikes:repairs", "bike:2").Result()
if err != nil {
panic(err)
}
fmt.Println(res11) // >>> 2
res12, err := rdb.LMove(ctx, "bikes:repairs", "bikes:finished", "LEFT", "LEFT").Result()
if err != nil {
panic(err)
}
fmt.Println(res12) // >>> bike:2
res13, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res13) // >>> [bike:1]
res14, err := rdb.LRange(ctx, "bikes:finished", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res14) // >>> [bike:2]
csharp
long res10 = db.ListLeftPush("{bikes}:repairs", "bike:1");
Console.WriteLine(res10); // >>> 1
long res11 = db.ListLeftPush("{bikes}:repairs", "bike:2");
Console.WriteLine(res11); // >>> 2
RedisValue res12 = db.ListMove("{bikes}:repairs", "{bikes}:finished", ListSide.Left, ListSide.Left);
Console.Write(res12); // >>> "bike:2"
RedisValue[] res13 = db.ListRange("{bikes}:repairs", 0, -1);
Console.WriteLine(string.Join(", ", res13)); // >>> "bike:1"
RedisValue[] res14 = db.ListRange("{bikes}:finished", 0, -1);
Console.WriteLine(string.Join(", ", res14)); // >>> "bike:2"
- 要限制列表的长度,您可以调用
LTRIM
:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs 0 2
OK
> LRANGE bikes:repairs 0 -1
1) "bike:1"
2) "bike:2"
3) "bike:3"
python
res48 = r.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5")
print(res48) # >>> 5
res49 = r.ltrim("bikes:repairs", 0, 2)
print(res49) # >>> True
res50 = r.lrange("bikes:repairs", 0, -1)
print(res50) # >>> ['bike:5', 'bike:4', 'bike:3']
js
const res48 = await client.lPush(
'bikes:repairs', ['bike:1', 'bike:2', 'bike:3', 'bike:4', 'bike:5']
);
console.log(res48); // 5
const res49 = await client.lTrim('bikes:repairs', 0, 2);
console.log(res49); // 'OK'
const res50 = await client.lRange('bikes:repairs', 0, -1);
console.log(res50); // ['bike:5', 'bike:4', 'bike:3']
java
long res48 = jedis.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5");
System.out.println(res48); // >>> 5
String res49 = jedis.ltrim("bikes:repairs", 0, 2);
System.out.println(res49); // >>> OK
List<String> res50 = jedis.lrange("bikes:repairs", 0, -1);
System.out.println(res50); // >>> [bike:5, bike:4, bike:3]
go
res51, err := rdb.LPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
if err != nil {
panic(err)
}
fmt.Println(res51) // >>> 5
res52, err := rdb.LTrim(ctx, "bikes:repairs", 0, 2).Result()
if err != nil {
panic(err)
}
fmt.Println(res52) // >>> OK
res53, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res53) // >>> [bike:5 bike:4 bike:3]
csharp
long res49 = db.ListLeftPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2", "bike:3", "bike:4", "bike:5" });
Console.WriteLine(res49); // >>> 5
db.ListTrim("bikes:repairs", 0, 2);
RedisValue[] res50 = db.ListRange("bikes:repairs", 0, -1);
Console.WriteLine(string.Join(", ", res50)); // >>> "bike:5, bike:4, bike:3"
什么是列表?
要解释 List 数据类型,最好从一点理论开始, 因为信息技术经常以不适当的方式使用术语 List 人。例如,“Python Lists” 并不是名称所暗示的(链接 Lists),而是 Arrays(相同的数据类型在 实际上是 Ruby)。
从非常一般的角度来看,List 只是一个有序的序列 elements: 10,20,1,2,3 是一个列表。但是使用 数组与使用链表实现的 List 的属性非常不同。
Redis 列表是通过链表实现的。这意味着即使您有 列表中的数百万个元素,在 列表的 head 或 in tail 在恒定时间内执行。添加 new 元素替换为 LPUSH 命令到包含 10 的列表的开头 元素与将元素添加到列表的头部 10 相同 万个元素。
缺点是什么?在列表中按索引访问元素非常快 使用 Array (恒定时间索引访问) 实现,并且在 列表(其中操作需要 work 与 accessed 元素的索引成正比)。
Redis 列表是使用链表实现的,因为对于数据库系统来说,它 对于能够以非常快速的方式将元素添加到很长的列表中至关重要。 正如您稍后将看到的,另一个强大的优势是 Redis 列表可以 在恒定时间内以恒定长度拍摄。
当快速访问大量元素的中间很重要时, 可以使用一种不同的数据结构,称为 Sorted Sets。 有序集教程页面中介绍了有序集。
Redis Lists 的第一步
LPUSH 命令将新元素添加到列表中,在 left(在头部),而 RPUSH 命令会添加新的 元素放入一个列表中,位于右侧(尾部)。最后,LRANGE 命令从列表中提取元素范围:
bash
> RPUSH bikes:repairs bike:1
(integer) 1
> RPUSH bikes:repairs bike:2
(integer) 2
> LPUSH bikes:repairs bike:important_bike
(integer) 3
> LRANGE bikes:repairs 0 -1
1) "bike:important_bike"
2) "bike:1"
3) "bike:2"
python
res15 = r.rpush("bikes:repairs", "bike:1")
print(res15) # >>> 1
res16 = r.rpush("bikes:repairs", "bike:2")
print(res16) # >>> 2
res17 = r.lpush("bikes:repairs", "bike:important_bike")
print(res17) # >>> 3
res18 = r.lrange("bikes:repairs", 0, -1)
print(res18) # >>> ['bike:important_bike', 'bike:1', 'bike:2']
js
const res15 = await client.rPush('bikes:repairs', 'bike:1');
console.log(res15); // 1
const res16 = await client.rPush('bikes:repairs', 'bike:2');
console.log(res16); // 2
const res17 = await client.lPush('bikes:repairs', 'bike:important_bike');
console.log(res17); // 3
const res18 = await client.lRange('bikes:repairs', 0, -1);
console.log(res18); // ['bike:important_bike', 'bike:1', 'bike:2']
java
long res15 = jedis.rpush("bikes:repairs", "bike:1");
System.out.println(res15); // >>> 1
long res16 = jedis.rpush("bikes:repairs", "bike:2");
System.out.println(res16); // >>> 2
long res17 = jedis.lpush("bikes:repairs", "bike:important_bike");
System.out.println(res17); // >>> 3
List<String> res18 = jedis.lrange("bikes:repairs", 0, -1);
System.out.println(res18); // >>> [bike:important_bike, bike:1, bike:2]
go
res15, err := rdb.RPush(ctx, "bikes:repairs", "bike:1").Result()
if err != nil {
panic(err)
}
fmt.Println(res15) // >>> 1
res16, err := rdb.RPush(ctx, "bikes:repairs", "bike:2").Result()
if err != nil {
panic(err)
}
fmt.Println(res16) // >>> 2
res17, err := rdb.LPush(ctx, "bikes:repairs", "bike:important_bike").Result()
if err != nil {
panic(err)
}
fmt.Println(res17) // >>> 3
res18, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res18) // >>> [bike:important_bike bike:1 bike:2]
csharp
long res15 = db.ListRightPush("bikes:repairs", "bike:1");
Console.WriteLine(res15); // >>> 1
long res16 = db.ListRightPush("bikes:repairs", "bike:2");
Console.WriteLine(res16); // >>> 2
long res17 = db.ListLeftPush("bikes:repairs", "bike:important_bike");
Console.WriteLine(res17); // >>> 3
RedisValue[] res18 = db.ListRange("bikes:repairs", 0, -1);
Console.WriteLine(string.Join(", ", res18)); // >>> "bike:important_bike, bike:1, bike:2"
请注意,LRANGE 采用两个索引,第一个和最后一个 元素。这两个索引都可以为负数,这告诉 Redis 从末尾开始计数:所以 -1 是最后一个元素,-2 是 列表的倒数第二个元素,依此类推。
如您所见,RPUSH 将元素附加到列表右侧,而 最后一个 LPUSH 将元素附加到左侧。
这两个命令都是可变参数命令,这意味着您可以自由推送 多个元素合并到一个列表中:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> LPUSH bikes:repairs bike:important_bike bike:very_important_bike
> LRANGE mylist 0 -1
1) "bike:very_important_bike"
2) "bike:important_bike"
3) "bike:1"
4) "bike:2"
5) "bike:3"
python
res19 = r.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3")
print(res19) # >>> 3
res20 = r.lpush("bikes:repairs", "bike:important_bike", "bike:very_important_bike")
print(res20) # >>> 5
res21 = r.lrange("bikes:repairs", 0, -1)
print(
res21
) # >>> ['bike:very_important_bike', 'bike:important_bike', 'bike:1', ...
js
const res19 = await client.rPush('bikes:repairs', ['bike:1', 'bike:2', 'bike:3']);
console.log(res19); // 3
const res20 = await client.lPush(
'bikes:repairs', ['bike:important_bike', 'bike:very_important_bike']
);
console.log(res20); // 5
const res21 = await client.lRange('bikes:repairs', 0, -1);
console.log(res21); // ['bike:very_important_bike', 'bike:important_bike', 'bike:1', 'bike:2', 'bike:3']
java
long res19 = jedis.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3");
System.out.println(res19); // >>> 3
long res20 = jedis.lpush("bikes:repairs", "bike:important_bike", "bike:very_important_bike");
System.out.println(res20); // >>> 5
List<String> res21 = jedis.lrange("bikes:repairs", 0, -1);
System.out.println(res21); // >>> [bike:very_important_bike, bike:important_bike, bike:1, bike:2, bike:3]
go
res19, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3").Result()
if err != nil {
panic(err)
}
fmt.Println(res19) // >>> 3
res20, err := rdb.LPush(ctx, "bikes:repairs", "bike:important_bike", "bike:very_important_bike").Result()
if err != nil {
panic(err)
}
fmt.Println(res20) // >>> 5
res21, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res21) // >>> [bike:very_important_bike bike:important_bike bike:1 bike:2 bike:3]
csharp
long res19 = db.ListRightPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2", "bike:3" });
Console.WriteLine(res19); // >>> 3
long res20 = db.ListLeftPush("bikes:repairs", new RedisValue[] { "bike:important_bike", "bike:very_important_bike" });
Console.WriteLine(res20); // >>> 5
RedisValue[] res21 = db.ListRange("bikes:repairs", 0, -1);
Console.WriteLine(string.Join(", ", res21));
// >>> "bike:very_important_bike, bike:important_bike, bike:1, bike:2, bike:3"
在 Redis 列表上定义的一个重要操作是弹出元素的能力。 弹出元素是从列表中检索元素的操作, 同时将其从列表中删除。您可以弹出元素 从左侧和右侧,类似于在两侧推送元素的方式 的列表。我们将添加 3 个元素并弹出 3 个元素,因此在此结束时 命令序列 列表为空,并且没有更多元素 流行:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> RPOP bikes:repairs
"bike:3"
> LPOP bikes:repairs
"bike:1"
> RPOP bikes:repairs
"bike:2"
> RPOP bikes:repairs
(nil)
python
res22 = r.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3")
print(res22) # >>> 3
res23 = r.rpop("bikes:repairs")
print(res23) # >>> 'bike:3'
res24 = r.lpop("bikes:repairs")
print(res24) # >>> 'bike:1'
res25 = r.rpop("bikes:repairs")
print(res25) # >>> 'bike:2'
res26 = r.rpop("bikes:repairs")
print(res26) # >>> None
js
const res22 = await client.rPush('bikes:repairs', ['bike:1', 'bike:2', 'bike:3']);
console.log(res22); // 3
const res23 = await client.rPop('bikes:repairs');
console.log(res23); // 'bike:3'
const res24 = await client.lPop('bikes:repairs');
console.log(res24); // 'bike:1'
const res25 = await client.rPop('bikes:repairs');
console.log(res25); // 'bike:2'
const res26 = await client.rPop('bikes:repairs');
console.log(res26); // None
java
long res22 = jedis.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3");
System.out.println(res22); // >>> 3
String res23 = jedis.rpop("bikes:repairs");
System.out.println(res23); // >>> bike:3
String res24 = jedis.lpop("bikes:repairs");
System.out.println(res24); // >>> bike:1
String res25 = jedis.rpop("bikes:repairs");
System.out.println(res25); // >>> bike:2
String res26 = jedis.rpop("bikes:repairs");
System.out.println(res26); // >>> null
go
res22, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3").Result()
if err != nil {
panic(err)
}
fmt.Println(res22) // >>> 3
res23, err := rdb.RPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res23) // >>> bike:3
res24, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res24) // >>> bike:1
res25, err := rdb.RPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res25) // >>> bike:2
res26, err := rdb.RPop(ctx, "bikes:repairs").Result()
if err != nil {
fmt.Println(err) // >>> redis: nil
}
fmt.Println(res26) // >>> <empty string>
csharp
long res22 = db.ListRightPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2", "bike:3" });
Console.WriteLine(res22); // >>> 3
RedisValue res23 = db.ListRightPop("bikes:repairs");
Console.WriteLine(res23); // >>> "bike:3"
RedisValue res24 = db.ListLeftPop("bikes:repairs");
Console.WriteLine(res24); // >>> "bike:1"
RedisValue res25 = db.ListRightPop("bikes:repairs");
Console.WriteLine(res25); // >>> "bike:2"
RedisValue res26 = db.ListRightPop("bikes:repairs");
Console.WriteLine(res26); // >>> <Empty string>
Redis 返回一个 NULL 值,以表示 列表。
列表的常见使用案例
列表对于许多任务都很有用,这是两个非常有代表性的用例 如下所示:
- 记住用户发布到社交网络的最新更新。
- 进程之间的通信,使用使用者-生产者模式,其中生产者将项目推送到列表中,消费者(通常是工作线程)使用这些项目并执行操作。Redis 具有特殊的 list 命令,可使此用例更加可靠和高效。
例如,流行的 Ruby 库 resque 和 sidekiq 都在后台使用 Redis 列表,以便 实现后台作业。
流行的 Twitter 社交网络将用户发布的最新推文纳入 Redis 列表。
为了逐步描述一个常见的使用案例,假设您的主页显示了最新的 在照片共享社交网络中发布的照片,并且您希望加快访问速度。
- 每次用户发布新照片时,我们都会使用 LPUSH 将其 ID 添加到列表中。
- 当用户访问主页时,我们使用
LRANGE 0 9
以获取最新的 10 个发布项目。
上限列表
在许多用例中,我们只想使用 list 来存储最新的项目, 无论它们是什么:社交网络更新、日志或其他任何内容。
Redis 允许我们将列表用作上限集合,只记住最新的 N 个项目并使用 LTRIM 命令丢弃所有最早的项目。
LTRIM 命令类似于 LRANGE,但不显示 指定的元素范围,它将此范围设置为新的 List 值。都 超出给定范围的元素将被删除。
例如,如果要在维修列表的末尾添加自行车,但仅 想担心在名单上时间最长的 3 个:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs 0 2
OK
> LRANGE bikes:repairs 0 -1
1) "bike:1"
2) "bike:2"
3) "bike:3"
python
res27 = r.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5")
print(res27) # >>> 5
res28 = r.ltrim("bikes:repairs", 0, 2)
print(res28) # >>> True
res29 = r.lrange("bikes:repairs", 0, -1)
print(res29) # >>> ['bike:5', 'bike:4', 'bike:3']
js
const res27 = await client.lPush(
'bikes:repairs', ['bike:1', 'bike:2', 'bike:3', 'bike:4', 'bike:5']
);
console.log(res27); // 5
const res28 = await client.lTrim('bikes:repairs', 0, 2);
console.log(res28); // true
const res29 = await client.lRange('bikes:repairs', 0, -1);
console.log(res29); // ['bike:5', 'bike:4', 'bike:3']
java
long res27 = jedis.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5");
System.out.println(res27); // >>> 5
String res28 = jedis.ltrim("bikes:repairs", 0, 2);
System.out.println(res28); // >>> OK
List<String> res29 = jedis.lrange("bikes:repairs", 0, -1);
System.out.println(res29); // >>> [bike:5, bike:4, bike:3]
go
res27, err := rdb.LPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
if err != nil {
panic(err)
}
fmt.Println(res27) // >>> 5
res28, err := rdb.LTrim(ctx, "bikes:repairs", 0, 2).Result()
if err != nil {
panic(err)
}
fmt.Println(res28) // >>> OK
res29, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res29) // >>> [bike:5 bike:4 bike:3]
csharp
long res27 = db.ListLeftPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2", "bike:3", "bike:4", "bike:5" });
Console.WriteLine(res27); // >>> 5
db.ListTrim("bikes:repairs", 0, 2);
RedisValue[] res28 = db.ListRange("bikes:repairs", 0, -1);
Console.WriteLine(string.Join(", ", res28)); // "bike:5, bike:4, bike:3"
上面的 LTRIM 命令告诉 Redis 只保留索引中的列表元素 0 到 2,其他所有内容都将被丢弃。这允许一个非常简单但 有用的模式:一起执行 List push 操作 + List trim 操作 添加新元素并丢弃超出限制的元素。然后,使用带有负索引的 LTRIM 可以只保留最近添加的 3 个:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs -3 -1
OK
> LRANGE bikes:repairs 0 -1
1) "bike:3"
2) "bike:4"
3) "bike:5"
python
res27 = r.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5")
print(res27) # >>> 5
res28 = r.ltrim("bikes:repairs", -3, -1)
print(res28) # >>> True
res29 = r.lrange("bikes:repairs", 0, -1)
print(res29) # >>> ['bike:3', 'bike:4', 'bike:5']
js
const res27eol = await client.rPush(
'bikes:repairs', ['bike:1', 'bike:2', 'bike:3', 'bike:4', 'bike:5']
);
console.log(res27eol); // 5
const res28eol = await client.lTrim('bikes:repairs', -3, -1);
console.log(res28eol); // 'OK'
const res29eol = await client.lRange('bikes:repairs', 0, -1);
console.log(res29eol); // ['bike:3', 'bike:4', 'bike:5']
java
res27 = jedis.rpush("bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5");
System.out.println(res27); // >>> 5
res28 = jedis.ltrim("bikes:repairs", -3, -1);
System.out.println(res2); // >>> OK
res29 = jedis.lrange("bikes:repairs", 0, -1);
System.out.println(res29); // >>> [bike:3, bike:4, bike:5]
go
res30, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
if err != nil {
panic(err)
}
fmt.Println(res30) // >>> 5
res31, err := rdb.LTrim(ctx, "bikes:repairs", -3, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res31) // >>> OK
res32, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res32) // >>> [bike:3 bike:4 bike:5]
csharp
long res29 = db.ListRightPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2", "bike:3", "bike:4", "bike:5" });
Console.WriteLine(res29); // >>> 5
db.ListTrim("bikes:repairs", -3, -1);
RedisValue[] res30 = db.ListRange("bikes:repairs", 0, -1);
Console.WriteLine(string.Join(", ", res30)); // >>> "bike:3, bike:4, bike:5"
上述组合添加了新元素,仅保留 3 个 列表中的最新元素。使用 LRANGE,您可以访问热门项目 无需记住非常旧的数据。
注意:虽然 LRANGE 在技术上是一个 O(N) 命令,但访问较小的范围 朝向列表的头部或尾部是一个恒定时间操作。
阻止对列表的操作
列表有一个特殊的功能,使它们适合实现队列, 通常作为进程间通信系统的构建块: blocking 操作。
假设您希望使用一个进程将项目推送到列表中,并使用 一个不同的过程,以便实际使用这些 项目。这是通常的 producer / consumer 设置,可以实现 采用以下简单方法:
- 为了将项目推送到列表中,创建者调用 LPUSH。
- 要从列表中提取/处理项目,使用者调用 RPOP。
但是,有时列表可能是空的,什么都没有 进行处理,因此 RPOP 只返回 NULL。在这种情况下,消费者被迫等待 请稍后重试,然后使用 RPOP 重试。这称为轮询,而不是 在这种情况下,这是一个好主意,因为它有几个缺点:
- 强制 Redis 和客户端处理无用的命令(当列表为空时,所有请求都不会完成实际工作,它们只会返回 NULL)。
- 为项目的处理添加延迟,因为在 worker 收到 NULL 后,它会等待一段时间。为了减小延迟,我们可以减少对 RPOP 的调用之间的等待时间,从而放大问题 1,即对 Redis 的更多无用调用。
因此,Redis 实现了称为 BRPOP 和 BLPOP 的命令,它们是版本 的 RPOP 和 LPOP 能够在列表为空时阻止:它们将返回到 仅当将新元素添加到列表中时,或者当用户指定的 已达到 timeout 时。
这是我们可以在 worker 中使用的 BRPOP 调用的示例:
bash
> RPUSH bikes:repairs bike:1 bike:2
(integer) 2
> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:2"
> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:1"
> BRPOP bikes:repairs 1
(nil)
(2.01s)
python
res31 = r.rpush("bikes:repairs", "bike:1", "bike:2")
print(res31) # >>> 2
res32 = r.brpop("bikes:repairs", timeout=1)
print(res32) # >>> ('bikes:repairs', 'bike:2')
res33 = r.brpop("bikes:repairs", timeout=1)
print(res33) # >>> ('bikes:repairs', 'bike:1')
res34 = r.brpop("bikes:repairs", timeout=1)
print(res34) # >>> None
js
const res31 = await client.rPush('bikes:repairs', ['bike:1', 'bike:2']);
console.log(res31); // 2
const res32 = await client.brPop('bikes:repairs', 1);
console.log(res32); // { key: 'bikes:repairs', element: 'bike:2' }
const res33 = await client.brPop('bikes:repairs', 1);
console.log(res33); // { key: 'bikes:repairs', element: 'bike:1' }
const res34 = await client.brPop('bikes:repairs', 1);
console.log(res34); // null
java
long res31 = jedis.rpush("bikes:repairs", "bike:1", "bike:2");
System.out.println(res31); // >>> 2
List<String> res32 = jedis.brpop(1, "bikes:repairs");
System.out.println(res32); // >>> (bikes:repairs, bike:2)
List<String> res33 = jedis.brpop(1,"bikes:repairs");
System.out.println(res33); // >>> (bikes:repairs, bike:1)
List<String> res34 = jedis.brpop(1,"bikes:repairs");
System.out.println(res34); // >>> null
go
res33, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2").Result()
if err != nil {
panic(err)
}
fmt.Println(res33) // >>> 2
res34, err := rdb.BRPop(ctx, 1, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res34) // >>> [bikes:repairs bike:2]
res35, err := rdb.BRPop(ctx, 1, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res35) // >>> [bikes:repairs bike:1]
res36, err := rdb.BRPop(ctx, 1, "bikes:repairs").Result()
if err != nil {
fmt.Println(err) // >>> redis: nil
}
fmt.Println(res36) // >>> []
csharp
long res31 = db.ListRightPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2" });
Console.WriteLine(res31); // >>> 2
Tuple<RedisKey, RedisValue>? res32 = db.BRPop(new RedisKey[] { "bikes:repairs" }, 1);
if (res32 != null)
Console.WriteLine($"{res32.Item1} -> {res32.Item2}"); // >>> "bikes:repairs -> bike:2"
Tuple<RedisKey, RedisValue>? res33 = db.BRPop(new RedisKey[] { "bikes:repairs" }, 1);
if (res33 != null)
Console.WriteLine($"{res33.Item1} -> {res33.Item2}"); // >>> "bikes:repairs -> bike:1"
Tuple<RedisKey, RedisValue>? res34 = db.BRPop(new RedisKey[] { "bikes:repairs" }, 1);
Console.WriteLine(res34); // >>> "Null"
它的意思是:“等待列表中的元素bikes:repairs
,但如果在 1 秒后返回 没有可用的元素”。
请注意,你可以使用 0 作为超时来永远等待元素,并且你可以 还要指定多个列表,而不仅仅是一个,以便等待多个 列表,并在第一个列表收到 元素。
关于 BRPOP 需要注意的几点:
- 客户端以有序的方式提供服务:第一个阻止等待列表的客户端,当其他客户端推送元素时,首先提供服务,依此类推。
- 与 RPOP 相比,返回值不同:它是一个双元素数组,因为它还包括键的名称,因为 BRPOP 和 BLPOP 能够阻止等待来自多个列表的元素。
- 如果达到超时,则返回 NULL。
关于列表和阻止操作,您应该了解更多内容。我们 建议您阅读更多以下内容:
- 可以使用 LMOVE 构建更安全的队列或轮换队列。
- 该命令还有一个阻止变体,称为 BLMOVE。
自动创建和删除key
到目前为止,在我们的示例中,我们从来没有在推送之前创建空列表 元素,或者在空列表中不再包含元素时删除空列表。 当列表留空时,Redis 负责删除键,或者创建 如果键不存在并且我们正在尝试添加元素,则为空列表 例如,使用 LPUSH。
这并不特定于列表,它适用于所有 Redis 数据类型 由多个元素组成 -- Streams、Sets、Sorted Sets 和 Hashes。
基本上,我们可以用三个规则来总结行为:
- 当我们向聚合数据类型添加元素时,如果目标键不存在,则会在添加元素之前创建一个空的聚合数据类型。
- 当我们从 aggregate 数据类型中删除元素时,如果值保持为空,则会自动销毁 key。Stream 数据类型是此规则的唯一例外。
- 使用空键调用只读命令(如 LLEN)(返回列表的长度)或删除元素的写入命令始终会产生相同的结果,就像键持有命令期望查找的类型的空聚合类型一样。
规则 1 的示例:
bash
> DEL new_bikes
(integer) 0
> LPUSH new_bikes bike:1 bike:2 bike:3
(integer) 3
python
res35 = r.delete("new_bikes")
print(res35) # >>> 0
res36 = r.lpush("new_bikes", "bike:1", "bike:2", "bike:3")
print(res36) # >>> 3
js
const res35 = await client.del('new_bikes');
console.log(res35); // 0
const res36 = await client.lPush('new_bikes', ['bike:1', 'bike:2', 'bike:3']);
console.log(res36); // 3
java
long res35 = jedis.del("new_bikes");
System.out.println(res35); // >>> 0
long res36 = jedis.lpush("new_bikes", "bike:1", "bike:2", "bike:3");
System.out.println(res36); // >>> 3
go
res37, err := rdb.Del(ctx, "new_bikes").Result()
if err != nil {
panic(err)
}
fmt.Println(res37) // >>> 0
res38, err := rdb.LPush(ctx, "new_bikes", "bike:1", "bike:2", "bike:3").Result()
if err != nil {
panic(err)
}
fmt.Println(res38) // >>> 3
csharp
bool res35 = db.KeyDelete("new_bikes");
Console.WriteLine(res35); // >>> False
long res36 = db.ListRightPush("new_bikes", new RedisValue[] { "bike:1", "bike:2", "bike:3" });
Console.WriteLine(res36); // >>> 3
但是,如果 key 存在,我们就不能对错误的类型执行操作:
bash
> SET new_bikes bike:1
OK
> TYPE new_bikes
string
> LPUSH new_bikes bike:2 bike:3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
python
res35 = r.delete("new_bikes")
print(res35) # >>> 0
res36 = r.lpush("new_bikes", "bike:1", "bike:2", "bike:3")
print(res36) # >>> 3
js
const res35 = await client.del('new_bikes');
console.log(res35); // 0
const res36 = await client.lPush('new_bikes', ['bike:1', 'bike:2', 'bike:3']);
console.log(res36); // 3
java
long res35 = jedis.del("new_bikes");
System.out.println(res35); // >>> 0
long res36 = jedis.lpush("new_bikes", "bike:1", "bike:2", "bike:3");
System.out.println(res36); // >>> 3
go
res37, err := rdb.Del(ctx, "new_bikes").Result()
if err != nil {
panic(err)
}
fmt.Println(res37) // >>> 0
res38, err := rdb.LPush(ctx, "new_bikes", "bike:1", "bike:2", "bike:3").Result()
if err != nil {
panic(err)
}
fmt.Println(res38) // >>> 3
csharp
bool res37 = db.StringSet("new_bikes", "bike:1");
Console.WriteLine(res37); // >>> True
RedisType res38 = db.KeyType("new_bikes");
Console.WriteLine(res38); // >>> RedisType.String
try
{
long res39 = db.ListRightPush("new_bikes", new RedisValue[] { "bike:2", "bike:3" });
}
catch (Exception e)
{
Console.WriteLine(e);
}
规则 2 的示例:
bash
> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> EXISTS bikes:repairs
(integer) 1
> LPOP bikes:repairs
"bike:3"
> LPOP bikes:repairs
"bike:2"
> LPOP bikes:repairs
"bike:1"
> EXISTS bikes:repairs
(integer) 0
python
r.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3")
print(res36) # >>> 3
res40 = r.exists("bikes:repairs")
print(res40) # >>> 1
res41 = r.lpop("bikes:repairs")
print(res41) # >>> 'bike:3'
res42 = r.lpop("bikes:repairs")
print(res42) # >>> 'bike:2'
res43 = r.lpop("bikes:repairs")
print(res43) # >>> 'bike:1'
res44 = r.exists("bikes:repairs")
print(res44) # >>> False
js
await client.lPush('bikes:repairs', ['bike:1', 'bike:2', 'bike:3']);
console.log(res36); // 3
const res40 = await client.exists('bikes:repairs')
console.log(res40); // true
const res41 = await client.lPop('bikes:repairs');
console.log(res41); // 'bike:3'
const res42 = await client.lPop('bikes:repairs');
console.log(res42); // 'bike:2'
const res43 = await client.lPop('bikes:repairs');
console.log(res43); // 'bike:1'
const res44 = await client.exists('bikes:repairs');
console.log(res44); // 0
java
jedis.lpush("bikes:repairs", "bike:1", "bike:2", "bike:3");
System.out.println(res36); // >>> 3
boolean res40 = jedis.exists("bikes:repairs");
System.out.println(res40); // >>> true
String res41 = jedis.lpop("bikes:repairs");
System.out.println(res41); // >>> bike:3
String res42 = jedis.lpop("bikes:repairs");
System.out.println(res42); // >>> bike:2
String res43 = jedis.lpop("bikes:repairs");
System.out.println(res43); // >>> bike:1
boolean res44 = jedis.exists("bikes:repairs");
System.out.println(res44); // >>> false
go
res42, err := rdb.LPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3").Result()
if err != nil {
panic(err)
}
fmt.Println(res42) // >>> 3
res43, err := rdb.Exists(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res43) // >>> 1
res44, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res44) // >>> bike:3
res45, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res45) // >>> bike:2
res46, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res46) // >>> bike:1
res47, err := rdb.Exists(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res47) // >>> 0
csharp
long res40 = db.ListLeftPush("bikes:repairs", new RedisValue[] { "bike:1", "bike:2", "bike:3" });
Console.WriteLine(res40); // >>> 3
bool res41 = db.KeyExists("bikes:repairs");
Console.WriteLine(res41); // >>> True
RedisValue res42 = db.ListLeftPop("bikes:repairs");
Console.WriteLine(res42); // >>> "bike:3"
RedisValue res43 = db.ListLeftPop("bikes:repairs");
Console.WriteLine(res43); // >>> "bike:2"
RedisValue res44 = db.ListLeftPop("bikes:repairs");
Console.WriteLine(res44); // >>> "bike:1"
bool res45 = db.KeyExists("bikes:repairs");
Console.WriteLine(res45); // >>> False
在弹出所有元素后,该键不再存在。
规则 3 的示例:
bash
> DEL bikes:repairs
(integer) 0
> LLEN bikes:repairs
(integer) 0
> LPOP bikes:repairs
(nil)
python
res45 = r.delete("bikes:repairs")
print(res45) # >>> 0
res46 = r.llen("bikes:repairs")
print(res46) # >>> 0
res47 = r.lpop("bikes:repairs")
print(res47) # >>> None
js
const res45 = await client.del('bikes:repairs');
console.log(res45); // 0
const res46 = await client.lLen('bikes:repairs');
console.log(res46); // 0
const res47 = await client.lPop('bikes:repairs');
console.log(res47); // null
java
long res45 = jedis.del("bikes:repairs");
System.out.println(res45); // >>> 0
long res46 = jedis.llen("bikes:repairs");
System.out.println(res46); // >>> 0
String res47 = jedis.lpop("bikes:repairs");
System.out.println(res47); // >>> null
go
res48, err := rdb.Del(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res48) // >>> 0
res49, err := rdb.LLen(ctx, "bikes:repairs").Result()
if err != nil {
panic(err)
}
fmt.Println(res49) // >>> 0
res50, err := rdb.LPop(ctx, "bikes:repairs").Result()
if err != nil {
fmt.Println(err) // >>> redis: nil
}
fmt.Println(res50) // >>> <empty string>
csharp
bool res46 = db.KeyDelete("bikes:repairs");
Console.WriteLine(res46); // >>> False
long res47 = db.ListLength("bikes:repairs");
Console.WriteLine(res47); // >>> 0
RedisValue res48 = db.ListLeftPop("bikes:repairs");
Console.WriteLine(res48); // >>> Null
限制
Redis 列表的最大长度为 2^32 - 1 (4,294,967,295) 个元素。
性能
访问其 head 或 tail 的列表操作是 O(1),这意味着它们非常高效。 但是,操作列表中元素的命令通常是 O(n)。 这些示例包括 LINDEX、LINSERT 和 LSET。 运行这些命令时要小心,主要是在对大型列表进行操作时。
选择
当您需要存储和处理一系列不确定的事件时,请考虑将 Redis 流作为列表的替代方案。
了解更多信息
- Redis 列表解释是关于 Redis 列表的简短而全面的视频解释器。
- Redis 大学的 RU101 详细介绍了 Redis 列表。