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

这篇具有很好参考价值的文章主要介绍了Redis 实现用户积分和积分排行榜微服务优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


在之前的博客中我通过 MySQL数据库实现了积分和积分排行榜功能,在数据量大和并发量高的情况下会有以下缺点:
  • SQL编写复杂;
  • 数据量大,执行统计SQL慢;
  • 高并发下会拖累其他业务表的操作,导致系统变慢;

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

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

修改添加积分方法

当将用户积分记录插入数据库后,同时利用ZINCRBY指令,将数据存入Redis中,这里不使用ZADD的原因是当用户不存在记录要插入,而且存在时需要将分数累加。
关于用户积分有一个问题用户信息表里的当前积分current_integral和总积分total_in,# Redis,🐁微服务,redis,java,数据库

积分排行控制层redis实现

    /**
     * 查询前 20 积分排行榜,同时显示用户排名 -- Redis
     *
     * @param access_token
     * @return
     */
    @GetMapping("redis")
    public ResultInfo findDinerPointsRankFromRedis(String access_token) {
        List<UserPointsRankVO> ranks = userPointsService.findUserPointRankFromRedis(access_token);
        return ResultInfoUtil.buildSuccess(request.getServletPath(), ranks);
    }

积分排行业务逻辑层

  • 排行榜:从Redis中根据user:points的key按照score的排序进行读取,这里使用Redis的ZREVRANGE指令,但在ZREVRANGE指令只返回member,不返回score,在RedisTemplate的ZSetOperations中有一个一个API方法叫reverseRangeWithScores(key, start, end)其中start从0开始,返回的是member和score,底层是将ZREVRANGEZSCORE指令进行组装。
  • 个人排名:使用REVRANKZSCORE操作进行读取;
    /**
     * 查询前 20 积分排行榜,并显示个人排名 -- Redis
     *
     * @param accessToken
     * @return
     */
    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排行榜测试

查询结果如下:
关于用户积分有一个问题用户信息表里的当前积分current_integral和总积分total_in,# Redis,🐁微服务,redis,java,数据库

{
    "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底层是SkipList + ZipList既能保证有序又能对数据进行压缩存储;
  • 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://www.toymoban.com/news/detail-769594.html

到了这里,关于Redis 实现用户积分和积分排行榜微服务优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【lettuce-排行榜】

    背景: 这次游戏中台采用lettuce的zset完成游戏内的本服和跨服排行榜,因此写一下案例。 pom.xml RedisManager.java RankManager.java RankItem.java RankInfo.java Main.java redis中查看下

    2024年01月21日
    浏览(41)
  • 爬虫:中国大学排行榜

            访问网址https://www.shanghairanking.cn/rankings,爬取排行榜数据,分析按区域的大学数量排行,得出有效结论。 爬取主榜数据并保存在文件中。 分析每个地区上榜大学的数量,保存在文件中。 分析前十名的地区的大学数量,绘制柱状图。 (4)说明爬虫爬取过程中可能

    2024年01月17日
    浏览(33)
  • 采购管理系统排行榜

    在这个数字化改变业务的年代,有哪些采购管理系统居于排行榜单的前列呢?作为一个自诩对该行业了如执掌的软件行业人员,给各位分享分享采购管理系统排行榜。 1、甄云数字化采购管理平台 国内做的一流的采购管理软件,算是行业内的老大哥,功能全面,也比较实用。

    2024年02月04日
    浏览(28)
  • 全球热门 AI 排行榜出炉!

    以下文章来源于无敌信息差 ,作者无敌 刚刚过去的 2023 年,可以说是 AI 元年。 随着技术的飞速发展,AI 行业在这一年再次迎来了爆炸式的增长。 近日,Writerbuddy 研究了  3000 多个 AI  的数据,出具了一份行业报告:该报告细致地盘点了 全球 50 大热门 AI 工具。 让无敌来带

    2024年02月22日
    浏览(39)
  • 开放 LLM 排行榜: 深入研究 DROP

    最近,开放 LLM 排行榜 迎来了 3 个新成员: Winogrande、GSM8k 以及 DROP,它们都使用了 EleutherAI Harness 的原始实现。一眼望去,我们就会发现 DROP 的分数有点古怪: 绝大多数模型的 F1 分数都低于 10 分 (满分 100 分)!我们对此进行了深入调查以一探究竟,请随我们一起踏上发现之旅吧

    2024年01月16日
    浏览(32)
  • unity微信小游戏——排行榜

    设置时记住排行榜唯一标识 此处建议使用官方案例的UI进行修改 minigame-unity-webgl-transform: Unity WebGL 微信小游戏适配方案 (gitee.com) DemoRanking这个项目就是 1.新建Canvas 此处要选择Overlay 否则排行榜会无法拖动 此处RankingBox默认状态为SetActive(false) bg:排行榜背景 也可以把背景让在

    2024年02月02日
    浏览(43)
  • 排序7-2 奥运排行榜 PTA 数据结构

    7-2 奥运排行榜 分数 25 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 每年奥运会各大媒体都会公布一个排行榜,但是细心的读者发现,不同国家的排行榜略有不同。比如中国金牌总数列第一的时候,中国媒体就公布“金牌榜”;而美国的奖牌总数第一,于是美国媒体就

    2024年02月02日
    浏览(35)
  • SRM的主要功能是什么?国内SRM主流排行榜

    SRM的主要功能是什么?国内SRM主流排行榜 现在国内很多中大型企业都在用SRM来优化企业采购业务,作为SRM行业从业者,我今天和大家聊聊主流SRM的功能,以及国内做SRM比较好的服务商,给大家参考。 SRM的主流功能,主要包括以下6点: 1、优化供应商的管理 供应商管理是企业

    2024年02月04日
    浏览(27)
  • 【python】爬取酷狗音乐Top500排行榜【附源码】

    英杰社区 https://bbs.csdn.net/topics/617804998     这篇博客将介绍如何使用Python编写一个爬虫程序,从斗鱼直播网站上获取图片信息并保存到本地。我们将使用 request s 模块发送HTTP请求和接收响应,以及 os 模块处理文件和目录操作。         如果出现模块报错         进入控

    2024年02月05日
    浏览(33)
  • 贵金属实时行情看盘软件排行榜(top 10)

    贵金属实时行情看盘软件哪个好,还是得看MT4软件,MT4是俄罗斯软件公司MetaQuotes生产的一款以外汇和贵金属交易为主的软件,其功能十分全面,目前全球有超过100家贵金属公司和30个国家的银行选择MT4软件作为网络交易平台。MT4综合行情图表、技术分析、下单交易四大功能于

    2024年02月03日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包