前言
大家好,我又回来了。已经好久没有写博客了,今天要写的排行榜的需求:排名前50,然后标记自己的位置,如果超过就补到后面去。
其实实现很简单,就是redis zset数据结构实现。主要是自己一些思考。
实现
前num名次(倒序)
public List<ZSetOperations.TypedTuple> rangeTopLeaderboard(Long activityId, Integer num) {
if (num < 0) {
return Collections.emptyList();
}
Set<ZSetOperations.TypedTuple<String>> set = stringRedisTemplate.opsForZSet().reverseRangeWithScores(TwinkleCacheKey.getActivityLeaderboardAppidKey(activityId), 0, num);
return new ArrayList<>(set);
}
正序就是把reverse去掉。
加分数
Double incrementScore(K key, V value, double delta);
初始化
Boolean add(K key, V value, double score);
个人总结
附加信息
我们的需求里面是有包含头像,个人信息什么的。如果说这些数据是不会变的,其实是可以序列化到 V value里面,可惜他们会变。
so 我只能单独再把这些冗余的信息放到缓存再封装返回给前端咯~
缓存问题
同事考虑到用户频繁刷新会导致性能问题,决定缓存一哈~那么问题来了,我只能缓存前面固定的例如50条+自己的数据在排行榜的名次。5分钟重新刷新,其实刷的只是前50条,西西······
更正:上面这么做的有坑的,比如我前50条缓存了,我排名第一,然后我实时排名第二,如果说要标出自己的位置就出现bug了。
删除问题
考虑到内存问题,可能也是有点杞人忧天吧。活动结束后一段时间会删除这个排行榜,不要再去占位置了,哈哈。
排序问题
由于我们的需求是这样的,要不就并列名次,这个有点难,因为redis排序出来的哪有并列的,要不就按照用户先达到的特定数量的时间进行排序。
我们使用第二种方法来实现需求。经过我和同事的激烈讨论之后得出结论:网上大多是在key或者score上加上时间戳,那就这样试下。
由于这个key我们是不能变的,那么就改造score!
由于redis zset返回的是double类型,考虑到精度,so加上时间戳(太长了,我们把前1位和后3位截取掉,前一位基本不会变,后3位代表秒)然后再跟真正的score拼接起来,格式:score.截取之后的时间戳
public static Double getScore(Double score) {
long currentTime = System.currentTimeMillis();
String time = String.valueOf(currentTime);
//截取后3位
time = time.substring(0, time.length() - 3);
time = time.substring(1);
//时间越早达到,排在越前
time = String.valueOf(999999999L - Long.valueOf(time));
String scoreValue = String.valueOf(score.intValue());
return Double.valueOf(scoreValue + "." + time);
}
这里就使用不了zset的自增了,只能每次add去更新值。因为score是不断变化的。
测试demo
template.opsForZSet().add("dajitui","小红",5);
template.opsForZSet().add("dajitui","小名",7);
template.opsForZSet().add("dajitui","小白",2);
template.opsForZSet().add("dajitui","小黑",8);
template.opsForZSet().add("dajitui","小积",9);
System.out.println(template.opsForZSet().reverseRank("dajitui","123"));
System.out.println(template.opsForZSet().range("dajitui",0,5));
System.out.println(template.opsForZSet().score("dajitui","小白"));
Set<ZSetOperations.TypedTuple<String>> set=template.opsForZSet().reverseRangeWithScores("dajitui",0,5);
for (ZSetOperations.TypedTuple<String> typedTuple : set) {
System.out.println(typedTuple.getValue() + " " + typedTuple.getScore());
}
System.out.println("--------------------");
template.opsForZSet().incrementScore("dajitui","小白",5);
//System.out.println(template.opsForZSet().range("dajitui",0,5));
Set<ZSetOperations.TypedTuple<String>> set1=template.opsForZSet().reverseRangeWithScores("dajitui",0,5);
for (ZSetOperations.TypedTuple<String> typedTuple : set1) {
System.out.println(typedTuple.getValue() + " " + typedTuple.getScore());
}
template.delete("dajitui");
基本够用了,that all,bye~