前言

排行榜作为现代互联网应用中的重要功能,广泛应用于游戏、电商、社交、教育等各个领域。Redis作为高性能的内存数据库,提供了丰富的数据结构和命令,是实现排行榜功能的理想选择。本文从Redis排行榜设计到排序算法,从基础实现到企业级方案,系统梳理基于Redis的排行榜应用完整解决方案。

一、Redis排行榜架构设计

1.1 Redis排行榜整体架构

1.2 排行榜类型架构

二、Redis数据结构选择

2.1 Sorted Set数据结构

2.1.1 Sorted Set基础操作

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* Redis Sorted Set排行榜服务
*/
@Service
public class RedisLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String LEADERBOARD_PREFIX = "leaderboard:";

/**
* 添加用户分数
*/
public void addUserScore(String leaderboardKey, String userId, double score) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
redisTemplate.opsForZSet().add(key, userId, score);
}

/**
* 更新用户分数
*/
public void updateUserScore(String leaderboardKey, String userId, double score) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
redisTemplate.opsForZSet().add(key, userId, score);
}

/**
* 增加用户分数
*/
public void incrementUserScore(String leaderboardKey, String userId, double increment) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
redisTemplate.opsForZSet().incrementScore(key, userId, increment);
}

/**
* 获取用户分数
*/
public Double getUserScore(String leaderboardKey, String userId) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
return redisTemplate.opsForZSet().score(key, userId);
}

/**
* 获取用户排名
*/
public Long getUserRank(String leaderboardKey, String userId, boolean reverse) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
if (reverse) {
return redisTemplate.opsForZSet().reverseRank(key, userId);
} else {
return redisTemplate.opsForZSet().rank(key, userId);
}
}

/**
* 获取排行榜前N名
*/
public Set<ZSetOperations.TypedTuple<Object>> getTopN(String leaderboardKey, long n, boolean reverse) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
if (reverse) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, n - 1);
} else {
return redisTemplate.opsForZSet().rangeWithScores(key, 0, n - 1);
}
}

/**
* 获取排行榜范围
*/
public Set<ZSetOperations.TypedTuple<Object>> getRange(String leaderboardKey, long start, long end, boolean reverse) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
if (reverse) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
} else {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
}

/**
* 获取排行榜总数
*/
public Long getLeaderboardSize(String leaderboardKey) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
return redisTemplate.opsForZSet().zCard(key);
}

/**
* 删除用户
*/
public void removeUser(String leaderboardKey, String userId) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
redisTemplate.opsForZSet().remove(key, userId);
}

/**
* 清空排行榜
*/
public void clearLeaderboard(String leaderboardKey) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
redisTemplate.delete(key);
}
}

2.1.2 高级排行榜操作

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* 高级排行榜服务
*/
@Service
public class AdvancedLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String LEADERBOARD_PREFIX = "leaderboard:";

/**
* 获取用户周围排名
*/
public List<LeaderboardUser> getUserAroundRank(String leaderboardKey, String userId, int range) {
String key = LEADERBOARD_PREFIX + leaderboardKey;

// 获取用户排名
Long userRank = redisTemplate.opsForZSet().rank(key, userId);
if (userRank == null) {
return Collections.emptyList();
}

// 计算范围
long start = Math.max(0, userRank - range);
long end = userRank + range;

// 获取范围内的用户
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.rangeWithScores(key, start, end);

// 转换为LeaderboardUser列表
List<LeaderboardUser> users = new ArrayList<>();
long currentRank = start;
for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
LeaderboardUser user = new LeaderboardUser();
user.setUserId((String) tuple.getValue());
user.setScore(tuple.getScore());
user.setRank(currentRank);
users.add(user);
currentRank++;
}

return users;
}

/**
* 获取分数范围内的用户
*/
public Set<ZSetOperations.TypedTuple<Object>> getUsersByScoreRange(String leaderboardKey,
double minScore, double maxScore) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
return redisTemplate.opsForZSet().rangeWithScores(key, minScore, maxScore);
}

/**
* 获取分数范围内的用户数量
*/
public Long getUsersCountByScoreRange(String leaderboardKey, double minScore, double maxScore) {
String key = LEADERBOARD_PREFIX + leaderboardKey;
return redisTemplate.opsForZSet().count(key, minScore, maxScore);
}

/**
* 批量更新用户分数
*/
public void batchUpdateUserScores(String leaderboardKey, Map<String, Double> userScores) {
String key = LEADERBOARD_PREFIX + leaderboardKey;

// 使用Pipeline批量操作
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (Map.Entry<String, Double> entry : userScores.entrySet()) {
connection.zAdd(key.getBytes(), entry.getValue(), entry.getKey().getBytes());
}
return null;
}
});
}

/**
* 获取排行榜统计信息
*/
public LeaderboardStats getLeaderboardStats(String leaderboardKey) {
String key = LEADERBOARD_PREFIX + leaderboardKey;

LeaderboardStats stats = new LeaderboardStats();

// 总用户数
stats.setTotalUsers(redisTemplate.opsForZSet().zCard(key));

// 最高分
Set<ZSetOperations.TypedTuple<Object>> topUsers = redisTemplate.opsForZSet()
.reverseRangeWithScores(key, 0, 0);
if (!topUsers.isEmpty()) {
stats.setHighestScore(topUsers.iterator().next().getScore());
}

// 最低分
Set<ZSetOperations.TypedTuple<Object>> bottomUsers = redisTemplate.opsForZSet()
.rangeWithScores(key, 0, 0);
if (!bottomUsers.isEmpty()) {
stats.setLowestScore(bottomUsers.iterator().next().getScore());
}

// 平均分
Double totalScore = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1)
.stream()
.mapToDouble(ZSetOperations.TypedTuple::getScore)
.sum();
if (stats.getTotalUsers() > 0) {
stats.setAverageScore(totalScore / stats.getTotalUsers());
}

return stats;
}

/**
* 合并多个排行榜
*/
public void mergeLeaderboards(String targetKey, List<String> sourceKeys, double[] weights) {
String targetRedisKey = LEADERBOARD_PREFIX + targetKey;

// 清空目标排行榜
redisTemplate.delete(targetRedisKey);

// 合并排行榜
for (int i = 0; i < sourceKeys.size(); i++) {
String sourceKey = LEADERBOARD_PREFIX + sourceKeys.get(i);
double weight = weights[i];

// 获取源排行榜的所有用户
Set<ZSetOperations.TypedTuple<Object>> users = redisTemplate.opsForZSet()
.rangeWithScores(sourceKey, 0, -1);

// 添加到目标排行榜
for (ZSetOperations.TypedTuple<Object> user : users) {
String userId = (String) user.getValue();
double score = user.getScore() * weight;

// 累加分数
redisTemplate.opsForZSet().incrementScore(targetRedisKey, userId, score);
}
}
}
}

2.2 多数据结构组合

2.2.1 Hash + Sorted Set组合

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
89
90
91
92
/**
* 多数据结构排行榜服务
*/
@Service
public class MultiDataStructureLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String LEADERBOARD_PREFIX = "leaderboard:";
private final String USER_INFO_PREFIX = "user_info:";

/**
* 添加用户到排行榜(包含详细信息)
*/
public void addUserToLeaderboard(String leaderboardKey, String userId, double score, Map<String, Object> userInfo) {
String leaderboardRedisKey = LEADERBOARD_PREFIX + leaderboardKey;
String userInfoRedisKey = USER_INFO_PREFIX + leaderboardKey + ":" + userId;

// 添加到排行榜
redisTemplate.opsForZSet().add(leaderboardRedisKey, userId, score);

// 保存用户详细信息
redisTemplate.opsForHash().putAll(userInfoRedisKey, userInfo);

// 设置过期时间
redisTemplate.expire(userInfoRedisKey, Duration.ofDays(30));
}

/**
* 获取排行榜用户详细信息
*/
public List<LeaderboardUserDetail> getLeaderboardWithDetails(String leaderboardKey, long start, long end) {
String leaderboardRedisKey = LEADERBOARD_PREFIX + leaderboardKey;

// 获取排行榜用户
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(leaderboardRedisKey, start, end);

List<LeaderboardUserDetail> users = new ArrayList<>();
long currentRank = start;

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();
String userInfoRedisKey = USER_INFO_PREFIX + leaderboardKey + ":" + userId;

// 获取用户详细信息
Map<Object, Object> userInfo = redisTemplate.opsForHash().entries(userInfoRedisKey);

LeaderboardUserDetail user = new LeaderboardUserDetail();
user.setUserId(userId);
user.setScore(tuple.getScore());
user.setRank(currentRank);
user.setUserInfo(userInfo);

users.add(user);
currentRank++;
}

return users;
}

/**
* 更新用户信息
*/
public void updateUserInfo(String leaderboardKey, String userId, Map<String, Object> userInfo) {
String userInfoRedisKey = USER_INFO_PREFIX + leaderboardKey + ":" + userId;
redisTemplate.opsForHash().putAll(userInfoRedisKey, userInfo);
}

/**
* 获取用户详细信息
*/
public Map<Object, Object> getUserInfo(String leaderboardKey, String userId) {
String userInfoRedisKey = USER_INFO_PREFIX + leaderboardKey + ":" + userId;
return redisTemplate.opsForHash().entries(userInfoRedisKey);
}

/**
* 删除用户
*/
public void removeUser(String leaderboardKey, String userId) {
String leaderboardRedisKey = LEADERBOARD_PREFIX + leaderboardKey;
String userInfoRedisKey = USER_INFO_PREFIX + leaderboardKey + ":" + userId;

// 从排行榜删除
redisTemplate.opsForZSet().remove(leaderboardRedisKey, userId);

// 删除用户信息
redisTemplate.delete(userInfoRedisKey);
}
}

2.2.2 List + Sorted Set组合

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
89
90
91
92
93
/**
* 历史排行榜服务
*/
@Service
public class HistoryLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String LEADERBOARD_PREFIX = "leaderboard:";
private final String HISTORY_PREFIX = "history:";

/**
* 保存历史排行榜
*/
public void saveHistoryLeaderboard(String leaderboardKey, String period) {
String currentKey = LEADERBOARD_PREFIX + leaderboardKey;
String historyKey = HISTORY_PREFIX + leaderboardKey + ":" + period;

// 获取当前排行榜
Set<ZSetOperations.TypedTuple<Object>> currentLeaderboard = redisTemplate.opsForZSet()
.rangeWithScores(currentKey, 0, -1);

// 清空历史排行榜
redisTemplate.delete(historyKey);

// 保存历史排行榜
for (ZSetOperations.TypedTuple<Object> tuple : currentLeaderboard) {
redisTemplate.opsForZSet().add(historyKey, tuple.getValue(), tuple.getScore());
}

// 设置过期时间
redisTemplate.expire(historyKey, Duration.ofDays(365));
}

/**
* 获取历史排行榜
*/
public Set<ZSetOperations.TypedTuple<Object>> getHistoryLeaderboard(String leaderboardKey, String period, long start, long end) {
String historyKey = HISTORY_PREFIX + leaderboardKey + ":" + period;
return redisTemplate.opsForZSet().reverseRangeWithScores(historyKey, start, end);
}

/**
* 比较历史排行榜
*/
public List<LeaderboardComparison> compareHistoryLeaderboards(String leaderboardKey, String period1, String period2) {
String historyKey1 = HISTORY_PREFIX + leaderboardKey + ":" + period1;
String historyKey2 = HISTORY_PREFIX + leaderboardKey + ":" + period2;

// 获取两个时期的排行榜
Set<ZSetOperations.TypedTuple<Object>> leaderboard1 = redisTemplate.opsForZSet()
.rangeWithScores(historyKey1, 0, -1);
Set<ZSetOperations.TypedTuple<Object>> leaderboard2 = redisTemplate.opsForZSet()
.rangeWithScores(historyKey2, 0, -1);

// 构建比较结果
List<LeaderboardComparison> comparisons = new ArrayList<>();

// 创建分数映射
Map<String, Double> scores1 = leaderboard1.stream()
.collect(Collectors.toMap(
tuple -> (String) tuple.getValue(),
ZSetOperations.TypedTuple::getScore
));

Map<String, Double> scores2 = leaderboard2.stream()
.collect(Collectors.toMap(
tuple -> (String) tuple.getValue(),
ZSetOperations.TypedTuple::getScore
));

// 比较所有用户
Set<String> allUsers = new HashSet<>();
allUsers.addAll(scores1.keySet());
allUsers.addAll(scores2.keySet());

for (String userId : allUsers) {
Double score1 = scores1.get(userId);
Double score2 = scores2.get(userId);

LeaderboardComparison comparison = new LeaderboardComparison();
comparison.setUserId(userId);
comparison.setScore1(score1 != null ? score1 : 0.0);
comparison.setScore2(score2 != null ? score2 : 0.0);
comparison.setScoreDiff(comparison.getScore2() - comparison.getScore1());

comparisons.add(comparison);
}

return comparisons;
}
}

三、排行榜排序算法

3.1 基础排序算法

3.1.1 分数排序

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
/**
* 分数排序服务
*/
@Service
public class ScoreSortingService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 按分数降序排列
*/
public List<LeaderboardUser> getLeaderboardByScoreDesc(String leaderboardKey, long start, long end) {
String key = "leaderboard:" + leaderboardKey;

Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(key, start, end);

return convertToLeaderboardUsers(tuples, start);
}

/**
* 按分数升序排列
*/
public List<LeaderboardUser> getLeaderboardByScoreAsc(String leaderboardKey, long start, long end) {
String key = "leaderboard:" + leaderboardKey;

Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.rangeWithScores(key, start, end);

return convertToLeaderboardUsers(tuples, start);
}

/**
* 按分数范围排序
*/
public List<LeaderboardUser> getLeaderboardByScoreRange(String leaderboardKey, double minScore, double maxScore) {
String key = "leaderboard:" + leaderboardKey;

Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.rangeWithScores(key, minScore, maxScore);

return convertToLeaderboardUsers(tuples, 0);
}

/**
* 转换为LeaderboardUser列表
*/
private List<LeaderboardUser> convertToLeaderboardUsers(Set<ZSetOperations.TypedTuple<Object>> tuples, long startRank) {
List<LeaderboardUser> users = new ArrayList<>();
long currentRank = startRank;

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
LeaderboardUser user = new LeaderboardUser();
user.setUserId((String) tuple.getValue());
user.setScore(tuple.getScore());
user.setRank(currentRank);
users.add(user);
currentRank++;
}

return users;
}
}

3.1.2 时间排序

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
/**
* 时间排序服务
*/
@Service
public class TimeSortingService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 按最后更新时间排序
*/
public List<LeaderboardUser> getLeaderboardByLastUpdate(String leaderboardKey, long start, long end) {
String key = "leaderboard:" + leaderboardKey;
String timeKey = "leaderboard_time:" + leaderboardKey;

// 获取排行榜用户
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(key, 0, -1);

// 按时间排序
List<LeaderboardUser> users = new ArrayList<>();
for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();
Double lastUpdateTime = redisTemplate.opsForZSet().score(timeKey, userId);

LeaderboardUser user = new LeaderboardUser();
user.setUserId(userId);
user.setScore(tuple.getScore());
user.setLastUpdateTime(lastUpdateTime != null ? lastUpdateTime.longValue() : 0);
users.add(user);
}

// 按时间排序
users.sort((u1, u2) -> Long.compare(u2.getLastUpdateTime(), u1.getLastUpdateTime()));

// 设置排名
for (int i = 0; i < users.size(); i++) {
users.get(i).setRank(start + i);
}

return users.subList((int) start, Math.min((int) end + 1, users.size()));
}

/**
* 更新用户最后更新时间
*/
public void updateUserLastUpdateTime(String leaderboardKey, String userId) {
String timeKey = "leaderboard_time:" + leaderboardKey;
redisTemplate.opsForZSet().add(timeKey, userId, System.currentTimeMillis());
}
}

3.2 复合排序算法

3.2.1 多维度排序

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
/**
* 多维度排序服务
*/
@Service
public class MultiDimensionSortingService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 多维度综合排序
*/
public List<LeaderboardUser> getMultiDimensionLeaderboard(String leaderboardKey,
Map<String, Double> weights, long start, long end) {

String key = "leaderboard:" + leaderboardKey;

// 获取所有用户
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.rangeWithScores(key, 0, -1);

List<LeaderboardUser> users = new ArrayList<>();

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();

// 计算综合分数
double compositeScore = calculateCompositeScore(userId, weights);

LeaderboardUser user = new LeaderboardUser();
user.setUserId(userId);
user.setScore(compositeScore);
user.setOriginalScore(tuple.getScore());
users.add(user);
}

// 按综合分数排序
users.sort((u1, u2) -> Double.compare(u2.getScore(), u1.getScore()));

// 设置排名
for (int i = 0; i < users.size(); i++) {
users.get(i).setRank(start + i);
}

return users.subList((int) start, Math.min((int) end + 1, users.size()));
}

/**
* 计算综合分数
*/
private double calculateCompositeScore(String userId, Map<String, Double> weights) {
double compositeScore = 0.0;

for (Map.Entry<String, Double> entry : weights.entrySet()) {
String dimension = entry.getKey();
Double weight = entry.getValue();

// 获取维度分数
Double dimensionScore = getDimensionScore(userId, dimension);
if (dimensionScore != null) {
compositeScore += dimensionScore * weight;
}
}

return compositeScore;
}

/**
* 获取维度分数
*/
private Double getDimensionScore(String userId, String dimension) {
String key = "leaderboard_" + dimension + ":" + userId;
return redisTemplate.opsForValue().get(key) != null ?
Double.valueOf(redisTemplate.opsForValue().get(key).toString()) : 0.0;
}

/**
* 设置维度分数
*/
public void setDimensionScore(String userId, String dimension, double score) {
String key = "leaderboard_" + dimension + ":" + userId;
redisTemplate.opsForValue().set(key, score, Duration.ofDays(30));
}
}

3.2.2 动态权重排序

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* 动态权重排序服务
*/
@Service
public class DynamicWeightSortingService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 动态权重排序
*/
public List<LeaderboardUser> getDynamicWeightLeaderboard(String leaderboardKey,
String weightType, long start, long end) {

String key = "leaderboard:" + leaderboardKey;

// 获取权重配置
Map<String, Double> weights = getDynamicWeights(weightType);

// 获取所有用户
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.rangeWithScores(key, 0, -1);

List<LeaderboardUser> users = new ArrayList<>();

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();

// 计算动态权重分数
double dynamicScore = calculateDynamicScore(userId, weights);

LeaderboardUser user = new LeaderboardUser();
user.setUserId(userId);
user.setScore(dynamicScore);
user.setOriginalScore(tuple.getScore());
users.add(user);
}

// 按动态分数排序
users.sort((u1, u2) -> Double.compare(u2.getScore(), u1.getScore()));

// 设置排名
for (int i = 0; i < users.size(); i++) {
users.get(i).setRank(start + i);
}

return users.subList((int) start, Math.min((int) end + 1, users.size()));
}

/**
* 获取动态权重
*/
private Map<String, Double> getDynamicWeights(String weightType) {
String key = "weight_config:" + weightType;
Map<Object, Object> weightMap = redisTemplate.opsForHash().entries(key);

Map<String, Double> weights = new HashMap<>();
for (Map.Entry<Object, Object> entry : weightMap.entrySet()) {
weights.put((String) entry.getKey(), Double.valueOf(entry.getValue().toString()));
}

return weights;
}

/**
* 计算动态分数
*/
private double calculateDynamicScore(String userId, Map<String, Double> weights) {
double dynamicScore = 0.0;

for (Map.Entry<String, Double> entry : weights.entrySet()) {
String dimension = entry.getKey();
Double weight = entry.getValue();

// 获取维度分数
Double dimensionScore = getDimensionScore(userId, dimension);
if (dimensionScore != null) {
// 应用动态权重
double adjustedWeight = applyDynamicWeight(weight, dimension, userId);
dynamicScore += dimensionScore * adjustedWeight;
}
}

return dynamicScore;
}

/**
* 应用动态权重
*/
private double applyDynamicWeight(double baseWeight, String dimension, String userId) {
// 根据用户特征调整权重
double adjustmentFactor = getUserAdjustmentFactor(userId, dimension);
return baseWeight * adjustmentFactor;
}

/**
* 获取用户调整因子
*/
private double getUserAdjustmentFactor(String userId, String dimension) {
// 实现用户特征分析逻辑
// 例如:新用户、活跃用户、VIP用户等
return 1.0; // 默认不调整
}
}

四、企业级排行榜应用

4.1 游戏排行榜应用

4.1.1 游戏积分排行榜

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* 游戏积分排行榜服务
*/
@Service
public class GameScoreLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private GameUserService gameUserService;

private final String GAME_SCORE_PREFIX = "game_score:";
private final String GAME_RANK_PREFIX = "game_rank:";

/**
* 更新游戏积分
*/
public void updateGameScore(String gameId, String userId, int score) {
String scoreKey = GAME_SCORE_PREFIX + gameId;
String rankKey = GAME_RANK_PREFIX + gameId;

// 更新积分
redisTemplate.opsForZSet().add(scoreKey, userId, score);

// 更新排名
redisTemplate.opsForZSet().add(rankKey, userId, score);

// 更新用户信息
updateUserGameInfo(gameId, userId, score);
}

/**
* 获取游戏排行榜
*/
public List<GameLeaderboardUser> getGameLeaderboard(String gameId, long start, long end) {
String scoreKey = GAME_SCORE_PREFIX + gameId;

// 获取排行榜
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(scoreKey, start, end);

List<GameLeaderboardUser> users = new ArrayList<>();
long currentRank = start;

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();

// 获取用户信息
GameUser userInfo = gameUserService.getUserById(userId);

GameLeaderboardUser leaderboardUser = new GameLeaderboardUser();
leaderboardUser.setUserId(userId);
leaderboardUser.setScore(tuple.getScore().intValue());
leaderboardUser.setRank(currentRank);
leaderboardUser.setNickname(userInfo.getNickname());
leaderboardUser.setAvatar(userInfo.getAvatar());
leaderboardUser.setLevel(userInfo.getLevel());

users.add(leaderboardUser);
currentRank++;
}

return users;
}

/**
* 获取用户游戏排名
*/
public GameUserRank getUserGameRank(String gameId, String userId) {
String scoreKey = GAME_SCORE_PREFIX + gameId;

// 获取用户分数
Double score = redisTemplate.opsForZSet().score(scoreKey, userId);
if (score == null) {
return null;
}

// 获取用户排名
Long rank = redisTemplate.opsForZSet().reverseRank(scoreKey, userId);

// 获取总用户数
Long totalUsers = redisTemplate.opsForZSet().zCard(scoreKey);

GameUserRank userRank = new GameUserRank();
userRank.setUserId(userId);
userRank.setScore(score.intValue());
userRank.setRank(rank != null ? rank + 1 : 0);
userRank.setTotalUsers(totalUsers);
userRank.setPercentile(calculatePercentile(rank, totalUsers));

return userRank;
}

/**
* 获取用户周围排名
*/
public List<GameLeaderboardUser> getUserAroundRank(String gameId, String userId, int range) {
String scoreKey = GAME_SCORE_PREFIX + gameId;

// 获取用户排名
Long userRank = redisTemplate.opsForZSet().reverseRank(scoreKey, userId);
if (userRank == null) {
return Collections.emptyList();
}

// 计算范围
long start = Math.max(0, userRank - range);
long end = userRank + range;

return getGameLeaderboard(gameId, start, end);
}

/**
* 更新用户游戏信息
*/
private void updateUserGameInfo(String gameId, String userId, int score) {
String userInfoKey = "game_user_info:" + gameId + ":" + userId;

Map<String, Object> userInfo = new HashMap<>();
userInfo.put("lastScore", score);
userInfo.put("lastUpdateTime", System.currentTimeMillis());
userInfo.put("gameId", gameId);

redisTemplate.opsForHash().putAll(userInfoKey, userInfo);
redisTemplate.expire(userInfoKey, Duration.ofDays(30));
}

/**
* 计算百分位
*/
private double calculatePercentile(Long rank, Long totalUsers) {
if (rank == null || totalUsers == null || totalUsers == 0) {
return 0.0;
}

return (double) (totalUsers - rank) / totalUsers * 100;
}
}

4.1.2 游戏成就排行榜

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
89
90
91
/**
* 游戏成就排行榜服务
*/
@Service
public class GameAchievementLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private GameAchievementService achievementService;

private final String ACHIEVEMENT_PREFIX = "achievement:";

/**
* 更新用户成就
*/
public void updateUserAchievement(String gameId, String userId, String achievementId, int points) {
String achievementKey = ACHIEVEMENT_PREFIX + gameId;

// 更新成就分数
redisTemplate.opsForZSet().incrementScore(achievementKey, userId, points);

// 记录成就详情
recordAchievementDetail(gameId, userId, achievementId, points);
}

/**
* 获取成就排行榜
*/
public List<AchievementLeaderboardUser> getAchievementLeaderboard(String gameId, long start, long end) {
String achievementKey = ACHIEVEMENT_PREFIX + gameId;

// 获取排行榜
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(achievementKey, start, end);

List<AchievementLeaderboardUser> users = new ArrayList<>();
long currentRank = start;

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();

// 获取用户成就详情
List<GameAchievement> achievements = getUserAchievements(gameId, userId);

AchievementLeaderboardUser leaderboardUser = new AchievementLeaderboardUser();
leaderboardUser.setUserId(userId);
leaderboardUser.setTotalPoints(tuple.getScore().intValue());
leaderboardUser.setRank(currentRank);
leaderboardUser.setAchievements(achievements);
leaderboardUser.setAchievementCount(achievements.size());

users.add(leaderboardUser);
currentRank++;
}

return users;
}

/**
* 获取用户成就详情
*/
private List<GameAchievement> getUserAchievements(String gameId, String userId) {
String detailKey = "achievement_detail:" + gameId + ":" + userId;
Map<Object, Object> achievementMap = redisTemplate.opsForHash().entries(detailKey);

List<GameAchievement> achievements = new ArrayList<>();
for (Map.Entry<Object, Object> entry : achievementMap.entrySet()) {
String achievementId = (String) entry.getKey();
int points = Integer.parseInt(entry.getValue().toString());

GameAchievement achievement = achievementService.getAchievementById(achievementId);
if (achievement != null) {
achievement.setPoints(points);
achievements.add(achievement);
}
}

return achievements;
}

/**
* 记录成就详情
*/
private void recordAchievementDetail(String gameId, String userId, String achievementId, int points) {
String detailKey = "achievement_detail:" + gameId + ":" + userId;
redisTemplate.opsForHash().put(detailKey, achievementId, points);
redisTemplate.expire(detailKey, Duration.ofDays(30));
}
}

4.2 电商排行榜应用

4.2.1 商品销量排行榜

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
* 商品销量排行榜服务
*/
@Service
public class ProductSalesLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private ProductService productService;

private final String SALES_PREFIX = "sales:";
private final String CATEGORY_SALES_PREFIX = "category_sales:";

/**
* 更新商品销量
*/
public void updateProductSales(String productId, int quantity) {
String salesKey = SALES_PREFIX + "total";

// 更新总销量
redisTemplate.opsForZSet().incrementScore(salesKey, productId, quantity);

// 更新分类销量
Product product = productService.getProductById(productId);
if (product != null) {
String categoryKey = CATEGORY_SALES_PREFIX + product.getCategoryId();
redisTemplate.opsForZSet().incrementScore(categoryKey, productId, quantity);
}

// 更新商品信息
updateProductInfo(productId, quantity);
}

/**
* 获取商品销量排行榜
*/
public List<ProductSalesLeaderboard> getProductSalesLeaderboard(String categoryId, long start, long end) {
String salesKey = categoryId != null ?
CATEGORY_SALES_PREFIX + categoryId : SALES_PREFIX + "total";

// 获取排行榜
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(salesKey, start, end);

List<ProductSalesLeaderboard> products = new ArrayList<>();
long currentRank = start;

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String productId = (String) tuple.getValue();

// 获取商品信息
Product product = productService.getProductById(productId);
if (product != null) {
ProductSalesLeaderboard leaderboardProduct = new ProductSalesLeaderboard();
leaderboardProduct.setProductId(productId);
leaderboardProduct.setProductName(product.getName());
leaderboardProduct.setProductImage(product.getImage());
leaderboardProduct.setPrice(product.getPrice());
leaderboardProduct.setSales(tuple.getScore().intValue());
leaderboardProduct.setRank(currentRank);
leaderboardProduct.setCategoryId(product.getCategoryId());

products.add(leaderboardProduct);
}

currentRank++;
}

return products;
}

/**
* 获取商品销量趋势
*/
public List<ProductSalesTrend> getProductSalesTrend(String productId, String period) {
String trendKey = "sales_trend:" + productId + ":" + period;

// 获取趋势数据
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.rangeWithScores(trendKey, 0, -1);

List<ProductSalesTrend> trends = new ArrayList<>();

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String timeKey = (String) tuple.getValue();
int sales = tuple.getScore().intValue();

ProductSalesTrend trend = new ProductSalesTrend();
trend.setTimeKey(timeKey);
trend.setSales(sales);
trend.setTimestamp(parseTimeKey(timeKey));

trends.add(trend);
}

return trends;
}

/**
* 更新商品信息
*/
private void updateProductInfo(String productId, int quantity) {
String productInfoKey = "product_info:" + productId;

Map<String, Object> productInfo = new HashMap<>();
productInfo.put("lastSalesUpdate", System.currentTimeMillis());
productInfo.put("lastSalesQuantity", quantity);

redisTemplate.opsForHash().putAll(productInfoKey, productInfo);
redisTemplate.expire(productInfoKey, Duration.ofDays(7));
}

/**
* 解析时间键
*/
private long parseTimeKey(String timeKey) {
try {
return Long.parseLong(timeKey);
} catch (NumberFormatException e) {
return 0;
}
}
}

4.2.2 用户消费排行榜

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* 用户消费排行榜服务
*/
@Service
public class UserConsumptionLeaderboardService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private UserService userService;

private final String CONSUMPTION_PREFIX = "consumption:";
private final String MONTHLY_CONSUMPTION_PREFIX = "monthly_consumption:";

/**
* 更新用户消费
*/
public void updateUserConsumption(String userId, double amount) {
String consumptionKey = CONSUMPTION_PREFIX + "total";
String monthlyKey = MONTHLY_CONSUMPTION_PREFIX + getCurrentMonth();

// 更新总消费
redisTemplate.opsForZSet().incrementScore(consumptionKey, userId, amount);

// 更新月度消费
redisTemplate.opsForZSet().incrementScore(monthlyKey, userId, amount);

// 更新用户消费信息
updateUserConsumptionInfo(userId, amount);
}

/**
* 获取用户消费排行榜
*/
public List<UserConsumptionLeaderboard> getUserConsumptionLeaderboard(String period, long start, long end) {
String consumptionKey = period.equals("monthly") ?
MONTHLY_CONSUMPTION_PREFIX + getCurrentMonth() : CONSUMPTION_PREFIX + "total";

// 获取排行榜
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(consumptionKey, start, end);

List<UserConsumptionLeaderboard> users = new ArrayList<>();
long currentRank = start;

for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
String userId = (String) tuple.getValue();

// 获取用户信息
User user = userService.getUserById(userId);
if (user != null) {
UserConsumptionLeaderboard leaderboardUser = new UserConsumptionLeaderboard();
leaderboardUser.setUserId(userId);
leaderboardUser.setUsername(user.getUsername());
leaderboardUser.setNickname(user.getNickname());
leaderboardUser.setAvatar(user.getAvatar());
leaderboardUser.setConsumption(tuple.getScore());
leaderboardUser.setRank(currentRank);
leaderboardUser.setUserLevel(user.getLevel());

users.add(leaderboardUser);
}

currentRank++;
}

return users;
}

/**
* 获取用户消费统计
*/
public UserConsumptionStats getUserConsumptionStats(String userId) {
String totalKey = CONSUMPTION_PREFIX + "total";
String monthlyKey = MONTHLY_CONSUMPTION_PREFIX + getCurrentMonth();

// 获取总消费
Double totalConsumption = redisTemplate.opsForZSet().score(totalKey, userId);

// 获取月度消费
Double monthlyConsumption = redisTemplate.opsForZSet().score(monthlyKey, userId);

// 获取总排名
Long totalRank = redisTemplate.opsForZSet().reverseRank(totalKey, userId);

// 获取月度排名
Long monthlyRank = redisTemplate.opsForZSet().reverseRank(monthlyKey, userId);

UserConsumptionStats stats = new UserConsumptionStats();
stats.setUserId(userId);
stats.setTotalConsumption(totalConsumption != null ? totalConsumption : 0.0);
stats.setMonthlyConsumption(monthlyConsumption != null ? monthlyConsumption : 0.0);
stats.setTotalRank(totalRank != null ? totalRank + 1 : 0);
stats.setMonthlyRank(monthlyRank != null ? monthlyRank + 1 : 0);

return stats;
}

/**
* 更新用户消费信息
*/
private void updateUserConsumptionInfo(String userId, double amount) {
String userInfoKey = "user_consumption_info:" + userId;

Map<String, Object> userInfo = new HashMap<>();
userInfo.put("lastConsumption", amount);
userInfo.put("lastConsumptionTime", System.currentTimeMillis());
userInfo.put("month", getCurrentMonth());

redisTemplate.opsForHash().putAll(userInfoKey, userInfo);
redisTemplate.expire(userInfoKey, Duration.ofDays(30));
}

/**
* 获取当前月份
*/
private String getCurrentMonth() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
}

五、性能优化与监控

5.1 性能优化

5.1.1 缓存优化

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* 排行榜缓存优化服务
*/
@Service
public class LeaderboardCacheOptimizationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private CaffeineCache localCache;

private final String CACHE_PREFIX = "leaderboard_cache:";

/**
* 获取排行榜缓存
*/
public List<LeaderboardUser> getLeaderboardWithCache(String leaderboardKey, long start, long end) {
String cacheKey = CACHE_PREFIX + leaderboardKey + ":" + start + ":" + end;

// 1. 尝试从本地缓存获取
@SuppressWarnings("unchecked")
List<LeaderboardUser> cachedResult = (List<LeaderboardUser>) localCache.getIfPresent(cacheKey);
if (cachedResult != null) {
return cachedResult;
}

// 2. 从Redis获取
String redisCacheKey = "redis_cache:" + cacheKey;
@SuppressWarnings("unchecked")
List<LeaderboardUser> redisResult = (List<LeaderboardUser>) redisTemplate.opsForValue().get(redisCacheKey);
if (redisResult != null) {
localCache.put(cacheKey, redisResult);
return redisResult;
}

// 3. 从数据库获取
List<LeaderboardUser> result = getLeaderboardFromDatabase(leaderboardKey, start, end);

// 4. 写入缓存
localCache.put(cacheKey, result);
redisTemplate.opsForValue().set(redisCacheKey, result, Duration.ofMinutes(5));

return result;
}

/**
* 预热排行榜缓存
*/
@PostConstruct
public void warmupCache() {
// 预热热门排行榜
List<String> hotLeaderboards = getHotLeaderboards();

for (String leaderboardKey : hotLeaderboards) {
// 预热前100名
getLeaderboardWithCache(leaderboardKey, 0, 99);
}
}

/**
* 清理过期缓存
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void cleanupExpiredCache() {
// 清理本地缓存
localCache.cleanUp();

// 清理Redis过期缓存
cleanupRedisExpiredCache();
}

/**
* 清理Redis过期缓存
*/
private void cleanupRedisExpiredCache() {
try {
Set<String> cacheKeys = redisTemplate.keys("redis_cache:" + CACHE_PREFIX + "*");

for (String key : cacheKeys) {
Long ttl = redisTemplate.getExpire(key);
if (ttl != null && ttl <= 0) {
redisTemplate.delete(key);
}
}
} catch (Exception e) {
log.error("清理Redis过期缓存失败", e);
}
}

/**
* 获取热门排行榜
*/
private List<String> getHotLeaderboards() {
// 实现获取热门排行榜的逻辑
return Arrays.asList("game_score", "product_sales", "user_consumption");
}

/**
* 从数据库获取排行榜
*/
private List<LeaderboardUser> getLeaderboardFromDatabase(String leaderboardKey, long start, long end) {
// 实现从数据库获取排行榜的逻辑
return new ArrayList<>();
}
}

5.1.2 批量操作优化

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
/**
* 批量操作优化服务
*/
@Service
public class BatchOperationOptimizationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 批量更新用户分数
*/
public void batchUpdateUserScores(String leaderboardKey, Map<String, Double> userScores) {
String key = "leaderboard:" + leaderboardKey;

// 使用Pipeline批量操作
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (Map.Entry<String, Double> entry : userScores.entrySet()) {
connection.zAdd(key.getBytes(), entry.getValue(), entry.getKey().getBytes());
}
return null;
}
});
}

/**
* 批量获取用户分数
*/
public Map<String, Double> batchGetUserScores(String leaderboardKey, List<String> userIds) {
String key = "leaderboard:" + leaderboardKey;

// 使用Pipeline批量操作
List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (String userId : userIds) {
connection.zScore(key.getBytes(), userId.getBytes());
}
return null;
}
});

Map<String, Double> userScores = new HashMap<>();
for (int i = 0; i < userIds.size(); i++) {
String userId = userIds.get(i);
Double score = (Double) results.get(i);
userScores.put(userId, score != null ? score : 0.0);
}

return userScores;
}

/**
* 批量获取用户排名
*/
public Map<String, Long> batchGetUserRanks(String leaderboardKey, List<String> userIds) {
String key = "leaderboard:" + leaderboardKey;

// 使用Pipeline批量操作
List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (String userId : userIds) {
connection.zRevRank(key.getBytes(), userId.getBytes());
}
return null;
}
});

Map<String, Long> userRanks = new HashMap<>();
for (int i = 0; i < userIds.size(); i++) {
String userId = userIds.get(i);
Long rank = (Long) results.get(i);
userRanks.put(userId, rank != null ? rank + 1 : 0);
}

return userRanks;
}
}

5.2 监控告警

5.2.1 监控指标

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
/**
* 排行榜监控指标
*/
@Component
public class LeaderboardMetrics {

private final MeterRegistry meterRegistry;

public LeaderboardMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

/**
* 记录排行榜查询
*/
public void recordLeaderboardQuery(String leaderboardType) {
Counter.builder("leaderboard.query.count")
.description("排行榜查询次数")
.tag("type", leaderboardType)
.register(meterRegistry)
.increment();
}

/**
* 记录排行榜更新
*/
public void recordLeaderboardUpdate(String leaderboardType) {
Counter.builder("leaderboard.update.count")
.description("排行榜更新次数")
.tag("type", leaderboardType)
.register(meterRegistry)
.increment();
}

/**
* 记录排行榜查询响应时间
*/
public void recordQueryResponseTime(String leaderboardType, long duration) {
Timer.builder("leaderboard.query.response.time")
.description("排行榜查询响应时间")
.tag("type", leaderboardType)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}

/**
* 记录排行榜更新响应时间
*/
public void recordUpdateResponseTime(String leaderboardType, long duration) {
Timer.builder("leaderboard.update.response.time")
.description("排行榜更新响应时间")
.tag("type", leaderboardType)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}

/**
* 记录排行榜大小
*/
public void recordLeaderboardSize(String leaderboardType, long size) {
Gauge.builder("leaderboard.size")
.description("排行榜大小")
.tag("type", leaderboardType)
.register(meterRegistry, size);
}
}

5.2.2 告警规则

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
# prometheus-rules.yml
groups:
- name: leaderboard_alerts
rules:
- alert: HighLeaderboardQueryResponseTime
expr: leaderboard_query_response_time{quantile="0.95"} > 1000
for: 2m
labels:
severity: warning
annotations:
summary: "排行榜查询响应时间过长"
description: "排行榜查询响应时间P95超过1秒,当前值: {{ $value }}ms"

- alert: HighLeaderboardUpdateResponseTime
expr: leaderboard_update_response_time{quantile="0.95"} > 500
for: 2m
labels:
severity: warning
annotations:
summary: "排行榜更新响应时间过长"
description: "排行榜更新响应时间P95超过500ms,当前值: {{ $value }}ms"

- alert: LeaderboardSizeTooLarge
expr: leaderboard_size > 1000000
for: 5m
labels:
severity: warning
annotations:
summary: "排行榜大小过大"
description: "排行榜大小超过100万,当前值: {{ $value }}"

六、总结

Redis中实现排行榜,通过合理的数据结构选择和排序算法设计,能够为企业提供高效、稳定的排行榜服务。本文从Redis数据结构选择到排序算法实现,从基础应用到企业级方案,系统梳理了基于Redis的排行榜应用完整解决方案。

6.1 关键要点

  1. 数据结构选择:Sorted Set是Redis实现排行榜的核心数据结构
  2. 排序算法:支持分数排序、时间排序、多维度排序等多种排序方式
  3. 应用场景:适用于游戏、电商、社交等多种业务场景
  4. 性能优化:通过缓存、批量操作等手段提高系统性能
  5. 监控告警:建立完善的监控体系,及时发现和处理问题

6.2 最佳实践

  1. 数据结构设计:合理选择Redis数据结构,支持复杂查询需求
  2. 排序算法优化:根据业务需求选择合适的排序算法
  3. 缓存策略:使用多级缓存提高查询性能
  4. 批量操作:使用Pipeline进行批量操作,提高更新效率
  5. 监控告警:建立完善的监控体系,确保系统稳定运行

通过以上措施,可以构建一个高效、稳定、可扩展的排行榜系统,为企业的各种业务场景提供支持。