如何使用 Redis 快速实现排行榜?

Sherwin.Wei Lv7

如何使用 Redis 快速实现排行榜?

回答重点

使用 Redis 实现排行榜的方式主要利用 Sorted Set(有序集合),它可以高效地存储、更新、以及获取排名数据。

实现排行榜的主要步骤:

1)使用 Sorted Set 存储分数和成员

  • 使用 Redis 的 ZADD 命令,将用户和对应的分数添加到有序集合中。例如:ZADD leaderboard 1000 user1,将用户 user1 的分数设置为 1000。

2)获取排名

  • 使用 ZRANK 命令获取某个用户的排名。例如:ZRANK leaderboard user1,返回用户 user1 的排名(从 0 开始)。

3)获取前 N 名

  • 使用 ZREVRANGE 命令获取分数最高的前 N 名。例如:ZREVRANGE leaderboard 0 9 WITHSCORES,获取排行榜前 10 名用户及其分数。

4)更新分数

  • 如果用户的分数需要更新,可以使用 ZINCRBY 命令对其分数进行加减操作。例如:ZINCRBY leaderboard 500 user1,将用户 user1 的分数增加 500。

扩展知识

Sorted Set 的特点

  • Sorted Set 是 Redis 中的一个数据结构,内部使用 跳表(Skip List) 来实现,提供按分数排序的功能。每个成员有一个唯一的 score(分数),根据分数进行排序。
  • Redis 的 Sorted Set 通过 O(logN) 的时间复杂度进行插入、更新和删除操作,且可以通过范围查找快速获取指定区间的数据。
  • 使用 Sorted Set 可以确保成员唯一性,因为 Redis 的 Sorted Set 中每个成员都是唯一的。如果添加相同的成员,ZADD 将更新其分数而不是重复插入。

Sorted Set 相关 Redis 命令

  • ZADD:向有序集合中添加成员并设置分数。如果成员已经存在,则更新其分数。
  • ZRANK / ZREVRANK:返回指定成员的排名。ZRANK 是按从小到大的排名,ZREVRANK 是从大到小(即逆序)。
  • ZREVRANGE:按分数从高到低返回指定区间内的成员列表,常用于获取排行榜的前 N 名。
  • ZINCRBY:对有序集合中指定成员的分数进行增减操作。

Java 示例代码

以下是一个完整的 Java 示例代码,供大家参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import redis.clients.jedis.Jedis;

import java.util.Set;
import java.util.Map;
import java.util.HashMap;

public class RedisLeaderboard {

private static final String LEADERBOARD_KEY = "leaderboard";
private Jedis jedis;

public RedisLeaderboard() {
// 连接到 Redis
this.jedis = new Jedis("localhost", 6379);
}

// 添加或更新用户分数
public void addOrUpdateUserScore(String user, double score) {
jedis.zadd(LEADERBOARD_KEY, score, user);
}

// 获取排行榜前 N 名用户
public Set<String> getTopUsers(int topN) {
return jedis.zrevrange(LEADERBOARD_KEY, 0, topN - 1);
}

// 获取用户的排名
public Long getUserRank(String user) {
return jedis.zrevrank(LEADERBOARD_KEY, user);
}

// 获取用户的分数
public Double getUserScore(String user) {
return jedis.zscore(LEADERBOARD_KEY, user);
}

// 删除用户
public void removeUser(String user) {
jedis.zrem(LEADERBOARD_KEY, user);
}

// 获取排行榜分页查询
public Set<String> getUsersByPage(int page, int pageSize) {
int start = (page - 1) * pageSize;
int end = start + pageSize - 1;
return jedis.zrevrange(LEADERBOARD_KEY, start, end);
}

// 关闭连接
public void close() {
jedis.close();
}

public static void main(String[] args) {
RedisLeaderboard leaderboard = new RedisLeaderboard();

// 添加用户及其分数
leaderboard.addOrUpdateUserScore("user1", 100);
leaderboard.addOrUpdateUserScore("user2", 200);
leaderboard.addOrUpdateUserScore("user3", 150);

// 更新用户分数
leaderboard.addOrUpdateUserScore("user1", 250);

// 获取排行榜前 3 名用户
Set<String> topUsers = leaderboard.getTopUsers(3);
System.out.println("Top 3 users: " + topUsers);

// 获取用户的排名
Long rank = leaderboard.getUserRank("user1");
System.out.println("User1 rank: " + rank);

// 获取用户的分数
Double score = leaderboard.getUserScore("user1");
System.out.println("User1 score: " + score);

// 获取排行榜第 2 页,每页 2 个用户
Set<String> pageUsers = leaderboard.getUsersByPage(2, 2);
System.out.println("Page 2 users: " + pageUsers);

// 删除用户
leaderboard.removeUser("user1");

// 关闭连接
leaderboard.close();
}
}

Comments