文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Redis 实现用户积分和积分排行榜微服务优化

2023-08-22 09:59

关注

在之前的博客中我通过 MySQL数据库实现了积分和积分排行榜功能,在数据量大和并发量高的情况下会有以下缺点:

使用 Sorted Sets 保存用户的积分总数,因为 Sorted Sets 有 score 属性,能够方便保存与读取,使用指令:

# 添加元素的分数,如果member不存在就会自动创建ZINCRBY key increment member # 按分数从大到小进行读取zrevrange key# 根据分数从大到小获取member排名zrevrank key member

修改添加积分方法

当将用户积分记录插入数据库后,同时利用ZINCRBY指令,将数据存入Redis中,这里不使用ZADD的原因是当用户不存在记录要插入,而且存在时需要将分数累加。
在这里插入图片描述

积分排行控制层redis实现

        @GetMapping("redis")    public ResultInfo findDinerPointsRankFromRedis(String access_token) {        List<UserPointsRankVO> ranks = userPointsService.findUserPointRankFromRedis(access_token);        return ResultInfoUtil.buildSuccess(request.getServletPath(), ranks);    }

积分排行业务逻辑层

        public List<UserPointsRankVO> findUserPointRankFromRedis(String accessToken) {        // 获取登录用户信息        SignInUserInfo signInUserInfo = loadSignInUserInfo(accessToken);        // 统计积分排行榜        Set<ZSetOperations.TypedTuple<Integer>> rangeWithScores = redisTemplate.opsForZSet().reverseRangeWithScores(                RedisKeyConstant.user_points.getKey(), 0, 19);        if (rangeWithScores == null || rangeWithScores.isEmpty()) {            return Lists.newArrayList();        }        // 初始化用户 ID 集合        List<Integer> rankuserIds = Lists.newArrayList();        // 根据 key:用户 ID value:积分信息 构建一个 Map        Map<Integer, UserPointsRankVO> ranksMap = new LinkedHashMap<>();        // 初始化排名        int rank = 1;        // 循环处理排行榜,添加排名信息        for (ZSetOperations.TypedTuple<Integer> rangeWithScore : rangeWithScores) {            // 用户ID            Integer userId = rangeWithScore.getValue();            // 积分            int points = rangeWithScore.getScore().intValue();            // 将用户 ID 添加至用户 ID 集合            rankuserIds.add(userId);            UserPointsRankVO userPointsRankVO = new UserPointsRankVO();            userPointsRankVO.setId(userId);            userPointsRankVO.setRanks(rank);            userPointsRankVO.setTotal(points);            // 将 VO 对象添加至 Map 中            ranksMap.put(userId, userPointsRankVO);            // 排名 +1            rank++;        }        // 获取 users 用户信息        ResultInfo resultInfo = restTemplate.getForObject(usersServerName +                        "findByIds?access_token=${accessToken}&ids={ids}",                ResultInfo.class, accessToken, StrUtil.join(",", rankuserIds));        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());        }        List<LinkedHashMap> dinerInfoMaps = (List<LinkedHashMap>) resultInfo.getData();        // 完善用户昵称和头像        for (LinkedHashMap dinerInfoMap : dinerInfoMaps) {            ShortUserInfo shortDinerInfo = BeanUtil.fillBeanWithMap(dinerInfoMap,                    new ShortUserInfo(), false);            UserPointsRankVO rankVO = ranksMap.get(shortDinerInfo.getId());            rankVO.setNickname(shortDinerInfo.getNickname());            rankVO.setAvatarUrl(shortDinerInfo.getAvatarUrl());        }        // 判断个人是否在 ranks 中,如果在,添加标记直接返回        if (ranksMap.containsKey(signInUserInfo.getId())) {            UserPointsRankVO rankVO = ranksMap.get(signInUserInfo.getId());            rankVO.setIsMe(1);            return Lists.newArrayList(ranksMap.values());        }        // 如果不在 ranks 中,获取个人排名追加在最后        // 获取排名        Long myRank = redisTemplate.opsForZSet().reverseRank(                RedisKeyConstant.user_points.getKey(), signInUserInfo.getId());        if (myRank != null) {            UserPointsRankVO me = new UserPointsRankVO();            BeanUtils.copyProperties(signInUserInfo, me);            me.setRanks(myRank.intValue() + 1);// 排名从 0 开始            me.setIsMe(1);            // 获取积分            Double points = redisTemplate.opsForZSet().score(RedisKeyConstant.user_points.getKey(),                    signInUserInfo.getId());            me.setTotal(points.intValue());            ranksMap.put(signInUserInfo.getId(), me);        }        return Lists.newArrayList(ranksMap.values());    }

Redis排行榜测试

查询结果如下:
在这里插入图片描述

{    "code": 1,    "message": "Successful.",    "path": "/redis",    "data": [        {            "id": 1171,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 773,            "ranks": 1,            "isMe": null        },        {            "id": 482,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 772,            "ranks": 2,            "isMe": null        },        {            "id": 161,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 762,            "ranks": 3,            "isMe": null        },        {            "id": 740,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 757,            "ranks": 4,            "isMe": null        },        {            "id": 1629,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 754,            "ranks": 5,            "isMe": null        },        {            "id": 912,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 747,            "ranks": 6,            "isMe": null        },        {            "id": 213,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 744,            "ranks": 7,            "isMe": null        },        {            "id": 1477,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 742,            "ranks": 8,            "isMe": null        },        {            "id": 771,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 737,            "ranks": 9,            "isMe": null        },        {            "id": 791,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 736,            "ranks": 10,            "isMe": null        },        {            "id": 1989,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 735,            "ranks": 11,            "isMe": null        },        {            "id": 1027,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 735,            "ranks": 12,            "isMe": null        },        {            "id": 492,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 734,            "ranks": 13,            "isMe": null        },        {            "id": 1743,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 733,            "ranks": 14,            "isMe": null        },        {            "id": 1529,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 729,            "ranks": 15,            "isMe": null        },        {            "id": 242,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 727,            "ranks": 16,            "isMe": null        },        {            "id": 1126,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 725,            "ranks": 17,            "isMe": null        },        {            "id": 796,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 719,            "ranks": 18,            "isMe": null        },        {            "id": 418,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 718,            "ranks": 19,            "isMe": null        },        {            "id": 1435,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 717,            "ranks": 20,            "isMe": null        },        {            "id": 6,            "nickname": "共饮一杯无",            "avatarUrl": null,            "total": 627,            "ranks": 172,            "isMe": 1        }    ]}

可以看到id为6的用户排名172,同时展示排名前20名的数据。

使用 JMeter 压测对比

通过JMeter分别对数据库和Redis两种方式实现的积分排行榜进行压力测试(5000并发),可以发现Redis在响应速度,吞吐量上面都提升明显,同时异常率更低。

使用Sorted Sets优势

Redis Sorted Sets是类似Redis Sets数据结构,不允许重复项的String集合。不同的是Sorted Sets中的每个成员都分配了一个分数值(score),它用于在Sorted Sets中进行成员排序,从最小值到最大值。Sorted Sets中所有的成员都是唯一的,其分数(score)是可以重复的,即是说一个分数可能会对应多个值。
用Sorted Sets可以非常快的进行添加、删除、或更新成员,其复杂度是O(m*log(n)),m是添加或查询的成员数量。因为成员是按照顺序添加的,所以可以非常快的通过score或者索引进行范围查询。访问Sorted Sets中间的元素也是非常快的,因此可以用sort sets作为一个不重复的小型有序列表。 通过Sorted Sets可以快速操作任何你想做的事情:排序成员,判断成员是否在集合中,快速访问集合中间的成员。
总的来说,在其他数据库比较难完成的任务,用Sorted Sets可以更快更优性能的完成。
更多Sorted Sets的用法可以查看官方文档

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

来源地址:https://blog.csdn.net/qq_35427589/article/details/128700848

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯