前言
在上一篇,讨论了积分排行榜基于mysql的实现方案,并且在文章的末尾我们抛出了一个问题,就是当用户量越来越大的时候,性能的问题将会成为此功能的瓶颈,因此需要寻找更好的解决方案
在Redis中,提供了一个叫做SortedSet的数据结构,该数据结构有2个特性,有序性和数据的可压缩性,利用这2个特点,结合上篇分析到的积分排行的业务场景,下面来尝试下如何利用Redis的这种数据结构来实现topN的积分排行榜吧
实现思路
- 用户签到插入积分时,同时插入到zset中
- 获取topN的排行信息时候,直接从zset中取出用户的userId和points的列表信息
- 拿到上面的用户列表,查询数据库用户的完整信息返回给页面展示
1、数据准备
考虑到要将用户的积分信息同时插入到mysql和redis,t_user表数据不需动,t_order表重新初始化下数据,简单对单元测试做一下修改
/**
* 为30个用户随机增加积分
*/
@Test
public void testAddPoints(){
for(int i=1;i<=30;i++){
Random rd1 = new Random();
int timeCount = rd1.nextInt(10);;
for(int j=1;j<=timeCount;j++){
Random rd = new Random();
int points = rd.nextInt(100) * 10;
signService.addPoints(i,1,points);
}
}
}
//积分插入数据库
public String addPoints(int userId,int type,int points){
if(ObjectUtil.isNull(userId) || ObjectUtil.isNull(type) || ObjectUtil.isNull(points)){
throw new RuntimeException("参数为空");
}
Points pointsSave = new Points();
pointsSave.setId(atomicInteger.incrementAndGet());
pointsSave.setUserId(userId);
pointsSave.setPoints(points);
pointsSave.setIsValid(1);
pointsSave.setCreateDate(new Date());
pointsMapper.save(pointsSave);
log.info("积分添加成功");
//插入一份到redis中
redisTemplate.opsForZSet().incrementScore(RedisKeyConstants.user_points.getKey(),userId,points);
return "add points success";
}
运行当前单元测试,观察数据库和redis中是否成功初始化数据
按照上一篇获取排名前10的数据,
同时利用zset的基本命令,也来查询下排名前10的数据
zrevrange user:points 0 9
两边的数据正好可以对应上,当然也可以通过这个命令将积分信息也一并查询出来
zrevrange user:points 0 9 withscores
查询某个用户的积分信息
zscore user:points 27
查询某个用户在整个积分中的排名
zrevrank user:points 9
有了上面的基础之后,下面来通过程序来实现下top10的积分排行榜信息展示吧
编码实现
业务实现逻辑
/**
* 获取前10的积分排行榜信息
* @param userId
* @param start
* @param end
* @return
*/
public Map<Integer,UserPointVO> getTop10Tanks(int userId,int start,int end){
Set<ZSetOperations.TypedTuple<Integer>> rangeScoreSet = redisTemplate.opsForZSet().reverseRangeWithScores(
RedisKeyConstants.user_points.getKey(), start, end
);
if(rangeScoreSet == null || rangeScoreSet.isEmpty()){
return new HashMap<>();
}
//set集合中保存了从第一到第十的用户ID和对应的积分信息,然后再代入查询用户信息即可
List<Integer> userIds = new LinkedList<>();
Map<Integer,UserPointVO> userRankMap = new LinkedHashMap<>();
int rank =1;
for(ZSetOperations.TypedTuple<Integer> item : rangeScoreSet){
Integer uid = item.getValue();
Integer points = item.getScore().intValue();
userIds.add(uid);
UserPointVO userPointVO = new UserPointVO();
userPointVO.setUserId(uid);
userPointVO.setScores(points);
userPointVO.setRanks(rank);
userRankMap.put(uid,userPointVO);
rank++;
}
List<User> userInfos = userMapper.selectUserByIds(userIds);
//最后再将用户的相关字段信息填充进去,不能打乱排名的顺序
userRankMap.forEach((key,value) ->{
User user1 = userInfos.stream().filter(user -> user.getId() == key).findFirst().get();
value.setUsername(user1.getUsername());
});
return userRankMap;
}
接口
//localhost:8089/getTop10Tanks?userId=27&start=0&end=9
@GetMapping("/getTop10Tanks")
public Object getTop10Tanks(int userId,int start,int end){
return signService.getTop10Tanks(userId,start,end);
}
最后启动工程,调一下接口:localhost:8089/getTop10Tanks?userId=27&start=0&end=9
得出的就是一个有序的排名从第一到第10的排行榜信息,依照上面的思路,有兴趣的同学还可以获取某个用户的积分信息,或者在整个积分排行榜中的位置,这里就不再继续演示了
需要源码的同学可自行下载:https://download.csdn.net/download/zhangcongyi420/15499799
本篇到此结束,最后感谢观看!