RedisTemplate 使用 Redis zset 实现排行榜(日榜、总榜)

1,542 阅读2分钟

需求

1、比赛记录分数,统计日榜和总榜

2、排行榜保留前3名

实现

一、引入redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

二、配置yml

spring:
  redis:
    host: localhost
    port: 6379

三、初始化RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        return redisTemplate;
    }

}

四、创建redis工具类

@Component
public class RedisUtils {

    @Resource
    private RedisTemplate<String, String> redisTemplate;
    
    // 相关方法会在后面说明
    ....
}

五、编写业务逻辑

1、每日新增排名

每日排名用日期作为key分割开

@Test
void addDayData() {
    // 添加第一天数据
    String key1 = "score:rank:20220620";
    redisUtils.zAdd(key1, "jack", 100);
    redisUtils.zAdd(key1, "david", 150);
    redisUtils.zAdd(key1, "chris", 76);
    redisUtils.zAdd(key1, "soho", 200);

    // 添加第二天数据,其中david两天都有参赛,并且第二天成绩比第一天好
    String key2 = "score:rank:20220621";
    redisUtils.zAdd(key2, "justin", 30);
    redisUtils.zAdd(key2, "david", 173);
    redisUtils.zAdd(key2, "luke", 89);
    redisUtils.zAdd(key2, "gk", 190);
}

redisUtils.zAdd()方法的实现如下:

/**
 * 向zset中添加分数
 * @param key   key
 * @param value 存放唯一标识
 * @param score 分数
 * @return boolean
 */
public Boolean zAdd(String key, String value, double score) {
    return redisTemplate.opsForZSet().add(key, value, score);
}

2、获取日榜前三数据

@Test
void getDayRank() {
    // 获取日榜前三,假如今天是20220621
    String key = "score:rank:20220621";
    redisUtils.zReverseRangeWithScores(key, 0, 2)
            .forEach(item ->
                    System.out.printf("user: %s, score: %s%n", item.getValue(), item.getScore()));
}

redisUtils.zReverseRangeWithScores()方法的实现如下:

/**
 * 从大到小截取数据
 * @param key   key
 * @param start 从哪里开始(0表示第一名)
 * @param end   截取到哪(9表示截取10名)
 * @return Set
 */
public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {
    return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}

2022年6月21日的排名如下:

user: gk, score: 190.0
user: david, score: 173.0
user: luke, score: 89.0

3、获取总榜前三数据

@Test
void getTotalRank() {
    // 获取所有日榜的keys
    Set<String> listKey = redisUtils.getListKey("score:rank");
    String key = "score:rank:20220621";
    String destKey = "score:rank:total";
    // 先查缓存
    Set<ZSetOperations.TypedTuple<String>> typedTuples = redisUtils.zReverseRangeWithScores(destKey, 0, 2);
    if (typedTuples.isEmpty()) {
        // 没有缓存的话将日榜并集,并将结果存储到新key,并设置有效期为15分钟
        redisUtils.zUnionAndStore(key, listKey, destKey);
        redisUtils.expire(destKey, 15L, TimeUnit.MINUTES);
        typedTuples = redisUtils.zReverseRangeWithScores(destKey, 0, 2);
    }
    // 获取总榜前10
    typedTuples.forEach(item ->
            System.out.printf("user: %s, score: %s%n", item.getValue(), item.getScore()));
}

2022年6月21日的总排名如下:

user: soho, score: 200.0
user: gk, score: 190.0
user: david, score: 173.0

redisUtils.zUnionAndStore()redisUtils.expire() 的实现如下:

/**
 * 并集所有keys
 *
 * RedisZSetCommands.Aggregate.MAX -> 保留最大
 * RedisZSetCommands.Aggregate.MIN -> 保留最小
 * RedisZSetCommands.Aggregate.SUM -> 相加(默认)
 *
 * @param key       要被并集的key
 * @param otherKeys 所有要并集的key
 * @param destKey   新key
 * @return          新set的长度
 */
public Long zUnionAndStore(String key, Collection<String> otherKeys, String destKey) {
    return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey, RedisZSetCommands.Aggregate.MAX);
}

/**
 * 设置key的过期时间
 * @param key       目标key
 * @param timeout   时间
 * @param timeUnit  单位
 * @return  boolean
 */ 
public Boolean expire(String key, long timeout, TimeUnit timeUnit) {
    return redisTemplate.expire(key, timeout, timeUnit);
}

参考

【开发经验】redis排行榜功能(日榜、周榜、月榜)

Redis工具类RedisUtils