Redis 有序集合
提示
来自deepseek解释
原文链接:https://redis.io/docs/latest/develop/data-types/sorted-sets/
代码示例图例
以下代码示例展示了如何在不同编程语言和客户端库中执行相同的操作:
- Redis CLI:Redis 命令行接口
- C# (同步):StackExchange.Redis 同步客户端
- C# (异步):StackExchange.Redis 异步客户端
- Go:go-redis 客户端
- Java (同步 - Jedis):Jedis 同步客户端
- Java (异步 - Lettuce):Lettuce 异步客户端
- Java (响应式 - Lettuce):Lettuce 响应式/流式客户端
- JavaScript (Node.js):node-redis 客户端
- PHP:Predis 客户端
- Python:redis-py 客户端
- Rust (同步):redis-rs 同步客户端
- Rust (异步):redis-rs 异步客户端
每个代码示例都展示了在不同语言中相同的基本操作。具体语法和模式因语言和客户端库而异,但底层的 Redis 命令和行为保持一致。
有序集合命令摘要
本组共 35 个命令:
| 命令 | 摘要 | 复杂度 | 引入版本 |
|---|---|---|---|
| BZMPOP | 从一个或多个有序集合中按分值移除并返回一个成员。若无成员可用则阻塞。若弹出最后一个元素则删除有序集合。 | O(K) + O(M*log(N)),其中 K 是提供的键数量…… | 7.0.0 |
| BZPOPMAX | 从一个或多个有序集合中移除并返回分值最高的成员。若无成员可用则阻塞。若弹出最后一个元素则删除有序集合。 | O(log(N)),N 为集合中元素数量 | 5.0.0 |
| BZPOPMIN | 从一个或多个有序集合中移除并返回分值最低的成员。若无成员可用则阻塞。若弹出最后一个元素则删除有序集合。 | O(log(N)),N 为集合中元素数量 | 5.0.0 |
| ZADD | 向有序集合添加一个或多个成员,或更新其分值。若键不存在则创建。 | 每添加一项 O(log(N)),N 为有序集合中元素数量 | 1.2.0 |
| ZCARD | 返回有序集合中的成员数量。 | O(1) | 1.2.0 |
| ZCOUNT | 返回有序集合中分值在指定范围内的成员数量。 | O(log(N)),N 为集合中元素数量 | 2.0.0 |
| ZDIFF | 返回多个有序集合的差集。 | 最坏情况 O(L + (N-K)log(N)),其中 L 为所有集合成员总数…… | 6.2.0 |
| ZDIFFSTORE | 将多个有序集合的差集存储到一个键中。 | 最坏情况 O(L + (N-K)log(N)),其中 L 为所有集合成员总数…… | 6.2.0 |
| ZINCRBY | 递增有序集合中某个成员的分值。 | O(log(N)),N 为集合中元素数量 | 1.2.0 |
| ZINTER | 返回多个有序集合的交集。 | 最坏情况 O(NK)+O(Mlog(M)),N 为最小输入集合的大小…… | 6.2.0 |
有序集合命令摘要(第 2 部分)
| 命令 | 摘要 | 复杂度 | 引入版本 |
|---|---|---|---|
| ZINTERCARD | 返回多个有序集合交集的成员数量。 | 最坏情况 O(N*K),N 为最小输入集合的大小…… | 7.0.0 |
| ZINTERSTORE | 将多个有序集合的交集存储到一个键中。 | 最坏情况 O(NK)+O(Mlog(M)),N 为最小输入集合的大小…… | 2.0.0 |
| ZLEXCOUNT | 返回有序集合中字典序范围内的成员数量。 | O(log(N)),N 为集合中元素数量 | 2.8.9 |
| ZMPOP | 从一个或多个有序集合中移除并返回分值最高或最低的成员。若弹出最后一个成员则删除有序集合。 | O(K) + O(M*log(N)),其中 K 是提供的键数量…… | 7.0.0 |
| ZMSCORE | 返回有序集合中一个或多个成员的分值。 | O(N),N 为请求的成员数量 | 6.2.0 |
| ZPOPMAX | 从有序集合中移除并返回分值最高的成员。若弹出最后一个成员则删除有序集合。 | O(log(N)*M),N 为集合中元素数量…… | 5.0.0 |
| ZPOPMIN | 从有序集合中移除并返回分值最低的成员。若弹出最后一个成员则删除有序集合。 | O(log(N)*M),N 为集合中元素数量…… | 5.0.0 |
| ZRANDMEMBER | 从有序集合中返回一个或多个随机成员。 | O(N),N 为返回的成员数量 | 6.2.0 |
| ZRANGE | 返回有序集合中指定索引范围内的成员。 | O(log(N)+M),N 为集合中元素数量…… | 1.2.0 |
| ZRANGEBYLEX | 返回有序集合中字典序范围内的成员。 | O(log(N)+M),N 为集合中元素数量…… | 2.8.9 |
有序集合命令摘要(第 3 部分)
| 命令 | 摘要 | 复杂度 | 引入版本 |
|---|---|---|---|
| ZRANGEBYSCORE | 返回有序集合中分值范围内的成员。 | O(log(N)+M),N 为集合中元素数量…… | 1.0.5 |
| ZRANGESTORE | 将有序集合中指定范围的成员存储到一个键中。 | O(log(N)+M),N 为集合中元素数量…… | 6.2.0 |
| ZRANK | 返回有序集合中按分值升序排列时某成员的索引。 | O(log(N)) | 2.0.0 |
| ZREM | 从有序集合中移除一个或多个成员。若所有成员被移除则删除有序集合。 | O(M*log(N)),N 为集合中元素数量…… | 1.2.0 |
| ZREMRANGEBYLEX | 移除有序集合中字典序范围内的成员。若所有成员被移除则删除有序集合。 | O(log(N)+M),N 为集合中元素数量…… | 2.8.9 |
| ZREMRANGEBYRANK | 移除有序集合中指定索引范围内的成员。若所有成员被移除则删除有序集合。 | O(log(N)+M),N 为集合中元素数量…… | 2.0.0 |
| ZREMRANGEBYSCORE | 移除有序集合中分值范围内的成员。若所有成员被移除则删除有序集合。 | O(log(N)+M),N 为集合中元素数量…… | 1.2.0 |
| ZREVRANGE | 返回有序集合中指定索引范围内的成员,按降序排列。 | O(log(N)+M),N 为集合中元素数量…… | 1.2.0 |
| ZREVRANGEBYLEX | 返回有序集合中字典序范围内的成员,按降序排列。 | O(log(N)+M),N 为集合中元素数量…… | 2.8.9 |
| ZREVRANGEBYSCORE | 返回有序集合中分值范围内的成员,按降序排列。 | O(log(N)+M),N 为集合中元素数量…… | 2.2.0 |
有序集合命令摘要(第 4 部分)
| 命令 | 摘要 | 复杂度 | 引入版本 |
|---|---|---|---|
| ZREVRANK | 返回有序集合中按分值降序排列时某成员的索引。 | O(log(N)) | 2.0.0 |
| ZSCAN | 迭代有序集合中的成员和分值。 | 每次调用 O(1)。完整迭代 O(N) | 2.8.0 |
| ZSCORE | 返回有序集合中某成员的分值。 | O(1) | 1.2.0 |
| ZUNION | 返回多个有序集合的并集。 | O(N)+O(M*log(M)),N 为所有输入集合大小之和…… | 6.2.0 |
| ZUNIONSTORE | 将多个有序集合的并集存储到一个键中。 | O(N)+O(M log(M)),N 为所有输入集合大小之和…… | 2.0.0 |
Redis 有序集合是由唯一字符串(成员)按关联分值排序的集合。 当多个字符串具有相同分值时,它们按字典序排列。 有序集合的一些使用场景包括:
- 排行榜。例如,你可以使用有序集合轻松维护大型在线游戏中的最高分有序列表。
- 限流器。特别是,你可以使用有序集合构建滑动窗口限流器,以防止过多的 API 请求。
你可以将有序集合视为集合(Set)和哈希(Hash)的混合体。与集合一样,有序集合由唯一、不重复的字符串元素组成,因此在某种意义上,有序集合也是集合。
但是,集合中的元素是无序的,而有序集合中的每个元素都关联一个浮点数,称为分值(这也是为什么该类型也类似于哈希,因为每个元素都映射到一个值)。
此外,有序集合中的元素是按顺序排列的(因此它们不是按请求时才排序,排序是有序集合底层数据结构的特性)。它们按照以下规则排序:
- 如果 B 和 A 是两个分值不同的元素,则当 A.score > B.score 时,A > B。
- 如果 B 和 A 具有完全相同的分值,则当 A 字符串在字典序上大于 B 字符串时,A > B。B 和 A 字符串不可能相等,因为有序集合只有唯一元素。
让我们从一个简单的例子开始,我们将所有赛车手及其在第一场比赛中的得分添加到有序集合中:
基础:向有序集合添加成员及其分值(创建新成员或更新已有成员)
命令: ZADD
复杂度:
- ZADD:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZADD racer_scores 10 "Norem"
(integer) 1
> ZADD racer_scores 12 "Castilla"
(integer) 1
> ZADD racer_scores 8 "Sam-Bodden" 10 "Royce" 6 "Ford" 14 "Prickett"
(integer) 4C#
bool res1 = db.SortedSetAdd("racer_scores", "Norem", 10);
Console.WriteLine(res1); // >>> True
bool res2 = db.SortedSetAdd("racer_scores", "Castilla", 12);
Console.WriteLine(res2); // >>> True
long res3 = db.SortedSetAdd("racer_scores", [
new("Sam-Bodden", 8),
new("Royce", 10),
new("Ford", 6),
new("Prickett", 14),
new("Castilla", 12)
]);
Console.WriteLine(res3); // >>> 4Go
res1, err := rdb.ZAdd(ctx, "racer_scores",
redis.Z{Member: "Norem", Score: 10},
).Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // >>> 1
res2, err := rdb.ZAdd(ctx, "racer_scores",
redis.Z{Member: "Castilla", Score: 12},
).Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // >>> 1
res3, err := rdb.ZAdd(ctx, "racer_scores",
redis.Z{Member: "Norem", Score: 10},
redis.Z{Member: "Sam-Bodden", Score: 8},
redis.Z{Member: "Royce", Score: 10},
redis.Z{Member: "Ford", Score: 6},
redis.Z{Member: "Prickett", Score: 14},
redis.Z{Member: "Castilla", Score: 12},
).Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // >>> 4Java (异步 - Lettuce)
CompletableFuture<Void> zadd = asyncCommands.zadd("racer_scores", ScoredValue.just(10d, "Norem"))
.thenCompose(res1 -> {
System.out.println(res1); // >>> 1
return asyncCommands.zadd("racer_scores", ScoredValue.just(12d, "Castilla"));
}).thenCompose(res2 -> {
System.out.println(res2); // >>> 1
return asyncCommands.zadd("racer_scores", ScoredValue.just(8d, "Sam-Bodden"),
ScoredValue.just(10d, "Royce"), ScoredValue.just(6d, "Ford"),
ScoredValue.just(14d, "Prickett"));
})
.thenAccept(System.out::println) // >>> 4
.toCompletableFuture();Java (同步 - Jedis)
long res1 = jedis.zadd("racer_scores", 10d, "Norem");
System.out.println(res1); // >>> 1
long res2 = jedis.zadd("racer_scores", 12d, "Castilla");
System.out.println(res2); // >>> 1
long res3 = jedis.zadd("racer_scores", new HashMap<String,Double>() {{
put("Sam-Bodden", 8d);
put("Royce", 10d);
put("Ford", 6d);
put("Prickett", 14d);
put("Castilla", 12d);
}});
System.out.println(res3); // >>> 4JavaScript (Node.js)
const res1 = await client.zAdd('racer_scores', { score: 10, value: 'Norem' });
console.log(res1); // >>> 1
const res2 = await client.zAdd('racer_scores', { score: 12, value: 'Castilla' });
console.log(res2); // >>> 1
const res3 = await client.zAdd('racer_scores', [
{ score: 8, value: 'Sam-Bodden' },
{ score: 10, value: 'Royce' },
{ score: 6, value: 'Ford' },
{ score: 14, value: 'Prickett' },
{ score: 12, value: 'Castilla' }
]);
console.log(res3); // >>> 4PHP
$res1 = $r->zadd('racer_scores', ['Norem' => 10]);
echo $res1 . PHP_EOL;
// >>> 1
$res2 = $r->zadd('racer_scores', ['Castilla' => 12]);
echo $res2 . PHP_EOL;
// >>> 1
$res3 = $r->zadd('racer_scores', [
'Sam-Bodden' => 8,
'Royce' => 10,
'Ford' => 6,
'Prickett' => 14,
'Castilla' => 12,
]);
echo $res3 . PHP_EOL;
// >>> 4Python
res1 = r.zadd("racer_scores", {"Norem": 10})
print(res1) # >>> 1
res2 = r.zadd("racer_scores", {"Castilla": 12})
print(res2) # >>> 1
res3 = r.zadd(
"racer_scores",
{"Sam-Bodden": 8, "Royce": 10, "Ford": 6, "Prickett": 14, "Castilla": 12},
)
print(res3) # >>> 4Ruby
res1 = r.zadd('racer_scores', [[10, 'Norem']])
puts res1 # 1
res2 = r.zadd('racer_scores', [[12, 'Castilla']])
puts res2 # 1
res3 = r.zadd('racer_scores', [
[8, 'Sam-Bodden'],
[10, 'Royce'],
[6, 'Ford'],
[14, 'Prickett'],
[12, 'Castilla']
])
puts res3 # 4Rust (异步)
if let Ok(res) = r.zadd("racer_scores", "Norem", 10).await {
let res: usize = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.zadd("racer_scores", "Castilla", 12).await {
let res: usize = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r
.zadd_multiple(
"racer_scores",
&[
(8, "Sam-Bodden"),
(10, "Royce"),
(6, "Ford"),
(14, "Prickett"),
(12, "Castilla"),
],
)
.await
{
let res: usize = res;
println!("{res}"); // >>> 4
}Rust (同步)
if let Ok(res) = r.zadd("racer_scores", "Norem", 10) {
let res: usize = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.zadd("racer_scores", "Castilla", 12) {
let res: usize = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.zadd_multiple(
"racer_scores",
&[
(8, "Sam-Bodden"),
(10, "Royce"),
(6, "Ford"),
(14, "Prickett"),
(12, "Castilla"),
],
) {
let res: usize = res;
println!("{res}"); // >>> 4
}正如你所见,ZADD 与 SADD 类似,但多了一个参数(放在要添加的元素之前),即分值。 ZADD 也是可变参数的,因此你可以自由指定多个分值-值对,如上例所示。
对于有序集合,返回按分值排序的赛车手列表非常简单,因为它们已经是有序的。
实现说明:有序集合通过一个双端口数据结构实现,包含跳表和哈希表,因此每次添加元素时,Redis 执行 O(log(N)) 操作。这很好,所以当我们请求有序元素时,Redis 不需要做任何额外工作,它们已经有序了。注意,ZRANGE 的顺序是从低到高,而 ZREVRANGE 的顺序是从高到低:
使用 ZRANGE 和 ZREVRANGE 按升序或降序检索成员(无需排序,已有序)
基于: zadd
命令: ZRANGE, ZREVRANGE
复杂度:
- ZRANGE:O(log(N))
- ZREVRANGE:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZRANGE racer_scores 0 -1
1) "Ford"
2) "Sam-Bodden"
3) "Norem"
4) "Royce"
5) "Castilla"
6) "Prickett"
> ZREVRANGE racer_scores 0 -1
1) "Prickett"
2) "Castilla"
3) "Royce"
4) "Norem"
5) "Sam-Bodden"
6) "Ford"C#
RedisValue[] res4 = db.SortedSetRangeByRank("racer_scores", 0, -1);
Console.WriteLine(string.Join(", ", res4)); // >>> Ford, Sam-Bodden, Norem, Royce, Castilla, Prickett
RedisValue[] res5 = db.SortedSetRangeByRank("racer_scores", 0, -1, Order.Descending);
Console.WriteLine(string.Join(", ", res5)); // >>> Prickett, Castilla, Royce, Norem, Sam-Bodden, FordGo
res4, err := rdb.ZRange(ctx, "racer_scores", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res4)
// >>> [Ford Sam-Bodden Norem Royce Castilla Prickett]
res5, err := rdb.ZRevRange(ctx, "racer_scores", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res5)
// >>> [Prickett Castilla Royce Norem Sam-Bodden Ford]Java (异步 - Lettuce)
CompletableFuture<Void> zrange = asyncCommands.zrange("racer_scores", 0, -1).thenCompose(res3 -> {
System.out.println(res3);
// >>> [Ford, Sam-Bodden, Norem, Royce, Castilla, Prickett]
return asyncCommands.zrevrange("racer_scores", 0, -1);
})
.thenAccept(System.out::println)
// >>> [Prickett, Castilla, Royce, Norem, Sam-Bodden, Ford]
.toCompletableFuture();Java (同步 - Jedis)
List<String> res4 = jedis.zrange("racer_scores", 0, -1);
System.out.println(res4); // >>> [Ford, Sam-Bodden, Norem, Royce, Castil, Castilla, Prickett]
List<String> res5 = jedis.zrevrange("racer_scores", 0, -1);
System.out.println(res5); // >>> [Prickett, Castilla, Castil, Royce, Norem, Sam-Bodden, Ford]JavaScript (Node.js)
const res4 = await client.zRange('racer_scores', 0, -1);
console.log(res4); // >>> ['Ford', 'Sam-Bodden', 'Norem', 'Royce', 'Castilla', 'Prickett']PHP
$res4 = $r->zrange('racer_scores', 0, -1);
echo json_encode($res4) . PHP_EOL;
// >>> ["Ford","Sam-Bodden","Norem","Royce","Castilla","Prickett"]
$res5 = $r->zrevrange('racer_scores', 0, -1);
echo json_encode($res5) . PHP_EOL;
// >>> ["Prickett","Castilla","Royce","Norem","Sam-Bodden","Ford"]Python
res4 = r.zrange("racer_scores", 0, -1)
print(res4) # >>> ['Ford', 'Sam-Bodden', 'Norem', 'Royce', 'Castilla', 'Prickett']
res5 = r.zrevrange("racer_scores", 0, -1)
print(res5) # >>> ['Prickett', 'Castilla', 'Royce', 'Norem', 'Sam-Bodden', 'Ford']Ruby
res4 = r.zrange('racer_scores', 0, -1)
puts res4.inspect # ["Ford", "Sam-Bodden", "Norem", "Royce", "Castilla", "Prickett"]
res5 = r.zrevrange('racer_scores', 0, -1)
puts res5.inspect # ["Prickett", "Castilla", "Royce", "Norem", "Sam-Bodden", "Ford"]Rust (异步)
if let Ok(res) = r.zrange("racer_scores", 0, -1).await {
let res: Vec<String> = res;
println!("{res:?}");
// >>> ["Ford", "Sam-Bodden", "Norem", "Royce", "Castilla", "Prickett"]
}
if let Ok(res) = r.zrevrange("racer_scores", 0, -1).await {
let res: Vec<String> = res;
println!("{res:?}");
// >>> ["Prickett", "Castilla", "Royce", "Norem", "Sam-Bodden", "Ford"]
}Rust (同步)
if let Ok(res) = r.zrange("racer_scores", 0, -1) {
let res: Vec<String> = res;
println!("{res:?}");
// >>> ["Ford", "Sam-Bodden", "Norem", "Royce", "Castilla", "Prickett"]
}
if let Ok(res) = r.zrevrange("racer_scores", 0, -1) {
let res: Vec<String> = res;
println!("{res:?}");
// >>> ["Prickett", "Castilla", "Royce", "Norem", "Sam-Bodden", "Ford"]
}注意:0 和 -1 表示从元素索引 0 到最后一个元素(-1 在这里的作用与 LRANGE 命令中的一样)。
也可以使用 WITHSCORES 参数返回分值:
当需要同时获取成员及其分值时,使用 WITHSCORES 选项检索成员及其分值
基于: zrange
命令: ZRANGE
复杂度:
- ZRANGE:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZRANGE racer_scores 0 -1 withscores
1) "Ford"
2) "6"
3) "Sam-Bodden"
4) "8"
5) "Norem"
6) "10"
7) "Royce"
8) "10"
9) "Castilla"
10) "12"
11) "Prickett"
12) "14"C#
SortedSetEntry[] res6 = db.SortedSetRangeByRankWithScores("racer_scores", 0, -1);
Console.WriteLine(string.Join(", ", res6)); // >>> Ford: 6, Sam-Bodden: 8, Norem: 10, Royce: 10, Castilla: 12, Prickett: 14Go
res6, err := rdb.ZRangeWithScores(ctx, "racer_scores", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res6)
// >>> [{6 Ford} {8 Sam-Bodden} {10 Norem} {10 Royce} {12 Castilla} {14 Prickett}]Java (异步 - Lettuce)
CompletableFuture<Void> zrangeWithScores = asyncCommands.zrangeWithScores("racer_scores", 0, -1)
.thenAccept(System.out::println)
// >>> [ScoredValue[6.000000, Ford], ScoredValue[8.000000, Sam-Bodden]...
.toCompletableFuture();Java (同步 - Jedis)
List<Tuple> res6 = jedis.zrangeWithScores("racer_scores", 0, -1);
System.out.println(res6); // >>> [[Ford,6.0], [Sam-Bodden,8.0], [Norem,10.0], [Royce,10.0], [Castil,12.0], [Castilla,12.0], [Prickett,14.0]]JavaScript (Node.js)
const res6 = await client.zRangeWithScores('racer_scores', 0, -1);
console.log(res6);
// >>> [
// { value: 'Ford', score: 6 }, { value: 'Sam-Bodden', score: 8 },
// { value: 'Norem', score: 10 }, { value: 'Royce', score: 10 },
// { value: 'Castilla', score: 12 }, { value: 'Prickett', score: 14 }
// ]PHP
$res6 = $r->zrange('racer_scores', 0, -1,[
'withscores' => true,
]);
echo json_encode($res6) . PHP_EOL;
// >>> {"Ford":"6","Sam-Bodden":"8","Norem":"10","Royce":"10","Castilla":"12","Prickett":"14"}Python
res6 = r.zrange("racer_scores", 0, -1, withscores=True)
print(
res6
)
# >>> [
# ('Ford', 6.0), ('Sam-Bodden', 8.0), ('Norem', 10.0), ('Royce', 10.0),
# ('Castilla', 12.0), ('Prickett', 14.0)
# ]Ruby
res6 = r.zrange('racer_scores', 0, -1, with_scores: true)
puts res6.inspect
# [["Ford", 6.0], ["Sam-Bodden", 8.0], ["Norem", 10.0], ["Royce", 10.0],
# ["Castilla", 12.0], ["Prickett", 14.0]]Rust (异步)
if let Ok(res) = r.zrange_withscores("racer_scores", 0, -1).await {
let res: Vec<(String, f64)> = res;
println!("{res:?}");
// >>> [("Ford", 6.0), ("Sam-Bodden", 8.0), ("Norem", 10.0), ("Royce", 10.0), ("Castilla", 12.0), ("Prickett", 14.0)]
}Rust (同步)
if let Ok(res) = r.zrange_withscores("racer_scores", 0, -1) {
let res: Vec<(String, f64)> = res;
println!("{res:?}");
// >>> [("Ford", 6.0), ("Sam-Bodden", 8.0), ("Norem", 10.0), ("Royce", 10.0), ("Castilla", 12.0), ("Prickett", 14.0)]
}范围操作
有序集合比这更强大。它们可以进行范围操作。 让我们获取所有得分小于等于 10 的赛车手。我们使用 ZRANGEBYSCORE 命令来实现:
按分值范围查询:当需要按数值过滤时,使用 ZRANGEBYSCORE 检索分值范围内的成员
难度: 中级
基于: zadd
命令: ZRANGEBYSCORE
复杂度:
- ZRANGEBYSCORE:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZRANGEBYSCORE racer_scores -inf 10
1) "Ford"
2) "Sam-Bodden"
3) "Norem"
4) "Royce"C#
RedisValue[] res7 = db.SortedSetRangeByScore("racer_scores", double.NegativeInfinity, 10);
Console.WriteLine(string.Join(", ", res7)); // >>> Ford, Sam-Bodden, Norem, RoyceGo
res7, err := rdb.ZRangeByScore(ctx, "racer_scores",
&redis.ZRangeBy{Min: "-inf", Max: "10"},
).Result()
if err != nil {
panic(err)
}
fmt.Println(res7)
// >>> [Ford Sam-Bodden Norem Royce]Java (异步 - Lettuce)
CompletableFuture<Void> zrangebyscore = asyncCommands
.zrangebyscore("racer_scores", Range.create(Double.MIN_VALUE, 10))
.thenAccept(System.out::println)
// >>> [Ford, Sam-Bodden, Norem, Royce]
.toCompletableFuture();Java (同步 - Jedis)
List<String> res7 = jedis.zrangeByScore("racer_scores", Double.MIN_VALUE, 10d);
System.out.println(res7); // >>> [Ford, Sam-Bodden, Norem, Royce]JavaScript (Node.js)
const res7 = await client.zRangeByScore('racer_scores', '-inf', 10);
console.log(res7); // >>> ['Ford', 'Sam-Bodden', 'Norem', 'Royce']PHP
$res7 = $r->zrangebyscore('racer_scores', '-inf', 10);
echo json_encode($res7) . PHP_EOL;
// >>> ["Ford","Sam-Bodden","Norem","Royce"]Python
res7 = r.zrangebyscore("racer_scores", "-inf", 10)
print(res7) # >>> ['Ford', 'Sam-Bodden', 'Norem', 'Royce']Ruby
res7 = r.zrangebyscore('racer_scores', '-inf', 10)
puts res7.inspect # ["Ford", "Sam-Bodden", "Norem", "Royce"]Rust (异步)
if let Ok(res) = r.zrangebyscore("racer_scores", "-inf", 10).await {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Ford", "Sam-Bodden", "Norem", "Royce"]
}Rust (同步)
if let Ok(res) = r.zrangebyscore("racer_scores", "-inf", 10) {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Ford", "Sam-Bodden", "Norem", "Royce"]
}我们要求 Redis 返回所有分值在负无穷到 10 之间的元素(包含两端)。
要删除一个元素,只需调用 ZREM 并传入赛车手名字。 也可以删除范围内的元素。让我们删除赛车手 Castilla 以及所有得分严格小于 10 的赛车手:
使用 ZREM 删除单个成员,或使用 ZREMRANGEBYSCORE 按分值范围删除成员(当需要删除数据时)
难度: 中级
基于: zadd
命令: ZREM, ZREMRANGEBYSCORE, ZRANGE
复杂度:
- ZREM:O(M*log(N))
- ZREMRANGEBYSCORE:O(log(N))
- ZRANGE:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZREM racer_scores "Castilla"
(integer) 1
> ZREMRANGEBYSCORE racer_scores -inf 9
(integer) 2
> ZRANGE racer_scores 0 -1
1) "Norem"
2) "Royce"
3) "Prickett"C#
bool res8 = db.SortedSetRemove("racer_scores", "Castilla");
Console.WriteLine(res8); // >>> True
long res9 = db.SortedSetRemoveRangeByScore("racer_scores", double.NegativeInfinity, 9);
Console.WriteLine(res9); // >>> 2
RedisValue[] res10 = db.SortedSetRangeByRank("racer_scores", 0, -1);
Console.WriteLine(string.Join(", ", res10)); // >>> Norem, Royce, PrickettGo
res8, err := rdb.ZRem(ctx, "racer_scores", "Castilla").Result()
if err != nil {
panic(err)
}
fmt.Println(res8) // >>> 1
res9, err := rdb.ZRemRangeByScore(ctx, "racer_scores", "-inf", "9").Result()
if err != nil {
panic(err)
}
fmt.Println(res9) // >>> 2
res10, err := rdb.ZRange(ctx, "racer_scores", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res10)
// >>> [Norem Royce Prickett]Java (异步 - Lettuce)
CompletableFuture<Void> zremrangebyscore = asyncCommands.zrem("racer_scores", "Castilla").thenCompose(res4 -> {
System.out.println(res4); // >>> 1
return asyncCommands.zremrangebyscore("racer_scores", Range.create(Double.MIN_VALUE, 9));
}).thenCompose(res5 -> {
System.out.println(res5); // >>> 2
return asyncCommands.zrange("racer_scores", 0, -1);
})
.thenAccept(System.out::println)
// >>> [Norem, Royce, Prickett]
.toCompletableFuture();Java (同步 - Jedis)
long res8 = jedis.zrem("racer_scores", "Castilla");
System.out.println(res8); // >>> 1
long res9 = jedis.zremrangeByScore("racer_scores", Double.MIN_VALUE, 9d);
System.out.println(res9); // >>> 2
List<String> res10 = jedis.zrange("racer_scores", 0, -1);
System.out.println(res10); // >>> [Norem, Royce, Prickett]JavaScript (Node.js)
const res8 = await client.zRem('racer_scores', 'Castilla');
console.log(res8); // >>> 1
const res9 = await client.zRemRangeByScore('racer_scores', '-inf', 9);
console.log(res9); // >>> 2
const res10 = await client.zRange('racer_scores', 0, -1);
console.log(res10); // >>> ['Norem', 'Royce', 'Prickett']PHP
$res8 = $r->zrem('racer_scores', 'Castilla');
echo $res8 . PHP_EOL;
// >>> 1
$res9 = $r->zremrangebyscore('racer_scores', '-inf', 9);
echo $res9 . PHP_EOL;
// >>> 2
$res10 = $r->zrange('racer_scores', 0, -1);
echo json_encode($res10) . PHP_EOL;
// >>> ["Norem","Royce","Prickett"]Python
res8 = r.zrem("racer_scores", "Castilla")
print(res8) # >>> 1
res9 = r.zremrangebyscore("racer_scores", "-inf", 9)
print(res9) # >>> 2
res10 = r.zrange("racer_scores", 0, -1)
print(res10) # >>> ['Norem', 'Royce', 'Prickett']Ruby
res8 = r.zrem('racer_scores', ['Castilla'])
puts res8 # 1
res9 = r.zremrangebyscore('racer_scores', '-inf', 9)
puts res9 # 2
res10 = r.zrange('racer_scores', 0, -1)
puts res10.inspect # ["Norem", "Royce", "Prickett"]Rust (异步)
if let Ok(res) = r.zrem("racer_scores", "Castilla").await {
let res: usize = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.zrembyscore("racer_scores", "-inf", 9).await {
let res: usize = res;
println!("{res}"); // >>> 2
}
if let Ok(res) = r.zrange("racer_scores", 0, -1).await {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Norem", "Royce", "Prickett"]
}Rust (同步)
if let Ok(res) = r.zrem("racer_scores", "Castilla") {
let res: usize = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.zrembyscore("racer_scores", "-inf", 9) {
let res: usize = res;
println!("{res}"); // >>> 2
}
if let Ok(res) = r.zrange("racer_scores", 0, -1) {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Norem", "Royce", "Prickett"]
}ZREMRANGEBYSCORE 这个命令名称也许不是最好的,但它非常有用,并返回移除的元素数量。
另一个为有序集合定义的非常有用的操作是获取排名操作。可以询问某个元素在有序集合中的位置。 ZREVRANK 命令也可用,用于按降序排列时获取排名。
获取成员排名:使用 ZRANK 和 ZREVRANK 查找成员在有序集合中的位置(对排行榜很有用)
难度: 中级
基于: zadd
命令: ZRANK, ZREVRANK
复杂度:
- ZRANK:O(log(N))
- ZREVRANK:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZRANK racer_scores "Norem"
(integer) 0
> ZREVRANK racer_scores "Norem"
(integer) 2C#
long? res11 = db.SortedSetRank("racer_scores", "Norem");
Console.WriteLine(res11); // >>> 0
long? res12 = db.SortedSetRank("racer_scores", "Norem", Order.Descending);
Console.WriteLine(res12); // >>> 2Go
res11, err := rdb.ZRank(ctx, "racer_scores", "Norem").Result()
if err != nil {
panic(err)
}
fmt.Println(res11) // >>> 0
res12, err := rdb.ZRevRank(ctx, "racer_scores", "Norem").Result()
if err != nil {
panic(err)
}
fmt.Println(res12) // >>> 2Java (异步 - Lettuce)
CompletableFuture<Void> zrank = asyncCommands.zrank("racer_scores", "Norem").thenCompose(res6 -> {
System.out.println(res6); // >>> 0
return asyncCommands.zrevrank("racer_scores", "Norem");
})
.thenAccept(System.out::println) // >>> 2
.toCompletableFuture();Java (同步 - Jedis)
long res11 = jedis.zrank("racer_scores", "Norem");
System.out.println(res11); // >>> 0
long res12 = jedis.zrevrank("racer_scores", "Norem");
System.out.println(res12); // >>> 2JavaScript (Node.js)
const res11 = await client.zRank('racer_scores', 'Norem');
console.log(res11); // >>> 0
const res12 = await client.zRevRank('racer_scores', 'Norem');
console.log(res12); // >>> 2PHP
$res11 = $r->zrank('racer_scores', 'Norem');
echo $res11 . PHP_EOL;
// >>> 0
$res12 = $r->zrevrank('racer_scores', 'Norem');
echo $res12 . PHP_EOL;
// >>> 2Python
res11 = r.zrank("racer_scores", "Norem")
print(res11) # >>> 0
res12 = r.zrevrank("racer_scores", "Norem")
print(res12) # >>> 2Ruby
res11 = r.zrank('racer_scores', 'Norem')
puts res11 # 0
res12 = r.zrevrank('racer_scores', 'Norem')
puts res12 # 2Rust (异步)
if let Ok(res) = r.zrank("racer_scores", "Norem").await {
let res: Option<usize> = res;
if let Some(res) = res {
println!("{res}"); // >>> 0
}
}
if let Ok(res) = r.zrevrank("racer_scores", "Norem").await {
let res: Option<usize> = res;
if let Some(res) = res {
println!("{res}"); // >>> 2
}
}Rust (同步)
if let Ok(res) = r.zrank("racer_scores", "Norem") {
let res: Option<usize> = res;
if let Some(res) = res {
println!("{res}"); // >>> 0
}
}
if let Ok(res) = r.zrevrank("racer_scores", "Norem") {
let res: Option<usize> = res;
if let Some(res) = res {
println!("{res}"); // >>> 2
}
}字典序分值
在 Redis 2.8 版本中,引入了一个新特性,允许按字典序获取范围,假设有序集合中的所有元素都插入了相同的分值(元素通过 C 的 memcmp 函数比较,因此保证没有排序规则,每个 Redis 实例都会返回相同的输出)。
操作字典序范围的主要命令有 ZRANGEBYLEX、ZREVRANGEBYLEX、ZREMRANGEBYLEX 和 ZLEXCOUNT。
例如,我们再次添加我们的著名赛车手列表,但这次所有元素的分值都设为 0。我们会发现,由于有序集合的排序规则,它们已经按字典序排序了。使用 ZRANGEBYLEX 我们可以请求字典序范围:
字典序查询:添加具有相同分值的成员,并使用 ZRANGEBYLEX 按字符串范围查询(实现通用索引)
难度: 中级
基于: zadd
命令: ZADD, ZRANGE, ZRANGEBYLEX
复杂度:
- ZADD:O(log(N))
- ZRANGE:O(log(N))
- ZRANGEBYLEX:O(log(N))
可用环境: Redis CLI、C#、Go、Java (异步 - Lettuce)、Java (同步 - Jedis)、JavaScript (Node.js)、PHP、Python、Ruby、Rust (异步)、Rust (同步)
Redis CLI
> ZADD racer_scores 0 "Norem" 0 "Sam-Bodden" 0 "Royce" 0 "Castilla" 0 "Prickett" 0 "Ford"
(integer) 3
> ZRANGE racer_scores 0 -1
1) "Castilla"
2) "Ford"
3) "Norem"
4) "Prickett"
5) "Royce"
6) "Sam-Bodden"
> ZRANGEBYLEX racer_scores [A [L
1) "Castilla"
2) "Ford"C#
long res13 = db.SortedSetAdd("racer_scores", [
new("Norem", 0),
new("Sam-Bodden", 0),
new("Royce", 0),
new("Ford", 0),
new("Prickett", 0),
new("Castilla", 0)
]);
Console.WriteLine(res13); // >>> 3
RedisValue[] res14 = db.SortedSetRangeByRank("racer_scores", 0, -1);
Console.WriteLine(string.Join(", ", res14)); // >>> Castilla, Ford, Norem, Pricket, Royce, Sam-Bodden
RedisValue[] res15 = db.SortedSetRangeByValue("racer_scores", "A", "L", Exclude.None);
Console.WriteLine(string.Join(", ", res15)); // >>> Castilla, FordGo
res13, err := rdb.ZAdd(ctx, "racer_scores",
redis.Z{Member: "Norem", Score: 0},
redis.Z{Member: "Sam-Bodden", Score: 0},
redis.Z{Member: "Royce", Score: 0},
redis.Z{Member: "Ford", Score: 0},
redis.Z{Member: "Prickett", Score: 0},
redis.Z{Member: "Castilla", Score: 0},
).Result()
if err != nil {
panic(err)
}
fmt.Println(res13) // >>> 3
res14, err := rdb.ZRange(ctx, "racer_scores", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println(res14)
// >>> [Castilla Ford Norem Prickett Royce Sam-Bodden]
res15, err := rdb.ZRangeByLex(ctx, "racer_scores", &redis.ZRangeBy{
Min: "[A", Max: "[L",
}).Result()
if err != nil {
panic(err)
}
fmt.Println(res15) // >>> [Castilla Ford]Java (异步 - Lettuce)
CompletableFuture<Void> zaddLex = asyncCommands.zadd("racer_scores", ScoredValue.just(0d, "Norem"),
ScoredValue.just(0d, "Sam-Bodden"), ScoredValue.just(0d, "Royce"), ScoredValue.just(0d, "Castilla"),
ScoredValue.just(0d, "Prickett"), ScoredValue.just(0d, "Ford")).thenCompose(res7 -> {
System.out.println(res7); // >>> 3
return asyncCommands.zrange("racer_scores", 0, -1);
}).thenCompose(res8 -> {
System.out.println(res8);
// >>> [Castilla, Ford, Norem, Prickett, Royce, Sam-Bodden]
return asyncCommands.zrangebylex("racer_scores", Range.create("A", "L"));
})
.thenAccept(System.out::println)
// >>> [Castilla, Ford]
.toCompletableFuture();Java (同步 - Jedis)
long res13 = jedis.zadd("racer_scores", new HashMap<String,Double>() {{
put("Norem", 0d);
put("Sam-Bodden", 0d);
put("Royce", 0d);
put("Ford", 0d);
put("Prickett", 0d);
put("Castilla", 0d);
}});
System.out.println(res13); // >>> 3
List<String> res14 = jedis.zrange("racer_scores", 0, -1);
System.out.println(res14); // >>> [Castilla, Ford, Norem, Prickett, Royce, Sam-Bodden]
List<String> res15 = jedis.zrangeByLex("racer_scores", "[A", "[L");
System.out.println(res15); // >>> [Castilla, Ford]JavaScript (Node.js)
const res13 = await client.zAdd('racer_scores', [
{ score: 0, value: 'Norem' },
{ score: 0, value: 'Sam-Bodden' },
{ score: 0, value: 'Royce' },
{ score: 0, value: 'Ford' },
{ score: 0, value: 'Prickett' },
{ score: 0, value: 'Castilla' }
]);
console.log(res13); // >>> 3
const res14 = await client.zRange('racer_scores', 0, -1);
console.log(res14); // >>> ['Castilla', 'Ford', 'Norem', 'Prickett', 'Royce', 'Sam-Bodden']
const res15 = await client.zRangeByLex('racer_scores', '[A', '[L');
console.log(res15); // >>> ['Castilla', 'Ford']PHP
$res13 = $r->zadd('racer_scores', [
'Norem' => 0,
'Sam-Bodden' => 0,
'Royce' => 0,
'Ford' => 0,
'Prickett' => 0,
'Castilla' => 0,
]);
echo $res13 . PHP_EOL;
// >>> 3
$res14 = $r->zrange('racer_scores', 0, -1);
echo json_encode($res14) . PHP_EOL;
// >>> ["Castilla","Ford","Norem","Prickett","Royce","Sam-Bodden"]
$res15 = $r->zrangebylex('racer_scores', '[A', '[L');
echo json_encode($res15) . PHP_EOL;
// >>> ["Castilla","Ford"]Python
res13 = r.zadd(
"racer_scores",
{
"Norem": 0,
"Sam-Bodden": 0,
"Royce": 0,
"Ford": 0,
"Prickett": 0,
"Castilla": 0,
},
)
print(res13) # >>> 3
res14 = r.zrange("racer_scores", 0, -1)
print(res14) # >>> ['Castilla', 'Ford', 'Norem', 'Prickett', 'Royce', 'Sam-Bodden']
res15 = r.zrangebylex("racer_scores", "[A", "[L")
print(res15) # >>> ['Castilla', 'Ford']Ruby
res13 = r.zadd('racer_scores', [
[0, 'Norem'],
[0, 'Sam-Bodden'],
[0, 'Royce'],
[0, 'Ford'],
[0, 'Prickett'],
[0, 'Castilla']
])
puts res13 # 3
res14 = r.zrange('racer_scores', 0, -1)
puts res14.inspect # ["Castilla", "Ford", "Norem", "Prickett", "Royce", "Sam-Bodden"]
res15 = r.zrangebylex('racer_scores', '[A', '[L')
puts res15.inspect # ["Castilla", "Ford"]Rust (异步)
if let Ok(res) = r
.zadd_multiple(
"racer_scores",
&[
(0, "Norem"),
(0, "Sam-Bodden"),
(0, "Royce"),
(0, "Ford"),
(0, "Prickett"),
(0, "Castilla"),
],
)
.await
{
let res: usize = res;
println!("{res}"); // >>> 3
}
if let Ok(res) = r.zrange("racer_scores", 0, -1).await {
let res: Vec<String> = res;
println!("{res:?}");
// >>> ["Castilla", "Ford", "Norem", "Prickett", "Royce", "Sam-Bodden"]
}
if let Ok(res) = r.zrangebylex("racer_scores", "[A", "[L").await {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Castilla", "Ford"]
}Rust (同步)
if let Ok(res) = r.zadd_multiple(
"racer_scores",
&[
(0, "Norem"),
(0, "Sam-Bodden"),
(0, "Royce"),
(0, "Ford"),
(0, "Prickett"),
(0, "Castilla"),
],
) {
let res: usize = res;
println!("{res}"); // >>> 3
}
if let Ok(res) = r.zrange("racer_scores", 0, -1) {
let res: Vec<String> = res;
println!("{res:?}");
// >>> ["Castilla", "Ford", "Norem", "Prickett", "Royce", "Sam-Bodden"]
}
if let Ok(res) = r.zrangebylex("racer_scores", "[A", "[L") {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Castilla", "Ford"]
}范围可以包含或排除(取决于第一个字符),字符串正无穷和负无穷分别用 + 和 - 指定。更多信息请参阅文档。
这个特性很重要,因为它允许我们将有序集合用作通用索引。例如,如果你想按 128 位无符号整数参数索引元素,你只需将所有元素以相同的分值(例如 0)添加到一个有序集合中,但带有一个 16 字节的前缀,即 采用大端序的 128 位数字。由于大端序的数字在按字典序(按原始字节顺序)排列时,实际上也是按数值顺序排列的,你可以在 128 位空间中请求范围,并在去掉前缀后获取元素值。
更新分值:排行榜
在进入下一个主题之前,关于有序集合最后补充一点。有序集合的分值可以随时更新。只需对已存在于有序集合中的元素调用 ZADD,就会以 O(log(N)) 的时间复杂度更新其分值(和位置)。因此,有序集合适用于大量更新的场景。
由于这一特性,一个常见的用例是排行榜。典型的应用是 Facebook 游戏,你可以结合按高分排序用户的能力,以及获取排名操作,来显示前 N 名用户以及用户在排行榜中的排名(例如,“你是这里排名第 4932 的最佳得分”)。