Redis积分排行榜设计与实现第二篇

前言

在上一篇,讨论了积分排行榜基于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

本篇到此结束,最后感谢观看!

猜你喜欢

转载自blog.csdn.net/zhangcongyi420/article/details/114227365