Redis --- 使用zset处理排行榜和计数问题

news2025/3/13 23:59:56

在处理计数业务时,我们一般会使用一个数据结构,既是集合又可以保证唯一性,所以我们会选择Redis中的set集合:

业务逻辑:

用户点击点赞按钮,需要再set集合内判断是否已点赞,未点赞则需要将点赞数+1并保存用户信息到集合中,已点赞则需要将数据库点赞数-1并移除set集合中的用户。

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Autowired
    private IUserService userService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result likeBlog(Long id) {
        // 获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 判断当前登录用户是否已经点赞
        String key = "blog:like:" + id;
        Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
        if(BooleanUtil.isFalse(isMember)){
            // 未点赞
            // 数据库点赞数+1
            boolean isSuccess = update().setSql("like = like + 1").eq("id",id).update();
            // 保存用户到Redis集合中
            if(isSuccess){
                stringRedisTemplate.opsForSet().add(key, userId.toString());
            }
        } else {
            // 已点赞,取消点赞
            // 数据库点赞数-1
            boolean isSuccess = update().setSql("like = like - 1").eq("id",id).update();
            // 移除set集合中的用户
            stringRedisTemplate.opsForSet().remove(key, userId.toString());
        }
        return Result.ok();
    }
}

那么我们想要实现按照点赞时间的先后顺序排序,返回Top5的用户,这个时候set无法保证数据有序,所以我们需要换一个数据结构满足业务需求:

Redis 的 ZSET(有序集合) 是一个非常适合用于处理 排行榜计数问题 的数据结构。在高并发的点赞业务中,使用 ZSET 可以帮助我们高效地管理点赞的排名,并且由于 ZSET 的排序特性,我们可以轻松实现根据点赞数实时排序的功能。


ZSET 数据结构


Redis 的 ZSET 是一个集合,它的每个元素都会关联一个 分数(score),这个分数决定了元素在集合中的排序。ZSET 保证集合中的元素是按分数排序的,并且可以在 O(log(N)) 的时间复杂度内进行添加、删除和查找操作

在高并发的点赞业务中,ZSET 可以帮助我们轻松地进行以下几项操作:

  • 记录每个用户对某个内容(如文章、评论等)的点赞数
  • 通过分数进行实时排序,获取点赞数最多的内容

优化高并发的点赞操作


高并发情况下,当多个用户同时对某个内容进行点赞时,我们需要高效地更新该内容的点赞数,并保证数据一致性。ZSET 提供了很好的支持,具体步骤如下:

  1. 用户点赞操作:使用 ZINCRBY 命令来对某个元素的分数进行增量操作,表示对该内容的点赞数增加。
  2. 查看点赞数:可以通过 ZSCORE 命令获取某个内容的当前点赞数。
  3. 查看排行榜:使用 ZRANGEZREVRANGE 命令来获取点赞数排名前 N 的内容,按分数进行排序。

ZSET 结构设计


key:表示某个内容的点赞的 id。

value:表示点赞用户的 id。

score:根据点赞时间排序。

下面是修改后的点赞逻辑:

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Autowired
    private IUserService userService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result likeBlog(Long id) {
        // 获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 判断当前登录用户是否已经点赞
        String key = "blog:like:" + id;
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        if(score == null){
            // 未点赞
            // 数据库点赞数+1
            boolean isSuccess = update().setSql("like = like + 1").eq("id",id).update();
            // 保存用户到Redis集合中
            if(isSuccess){
                stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
            }
        } else {
            // 已点赞,取消点赞
            // 数据库点赞数-1
            boolean isSuccess = update().setSql("like = like - 1").eq("id",id).update();
            // 移除set集合中的用户
            stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        }
        return Result.ok();
    }
}

而点赞排行榜代码如下:

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Autowired
    private IUserService userService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryBlogLikes(Long id) {
        String key = "blog:like:" + id;
        // 查询top5的点赞用户 zrange key 0 4
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if (top5 == null || top5.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        // 解析出集合中的用户的id
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        // 根据id查询用户,并将类型由User转为UserDTO,随后转换为List集合
        String idStr = StrUtil.join(",",ids);
//        List<UserDTO> userDTOs = userService.listByIds(ids).stream()
//                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
//                .collect(Collectors.toList());
        List<UserDTO> userDTOs = userService.query()
                .in("id",ids).last("order by field(id," + idStr +")").list()
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(userDTOs);
    }
}

使用 userService.query().in("id", ids).last("order by field(id," + idStr + ")") 来查询用户信息,并且使用 order by field(id, ...) 语句来保证查询结果的顺序与 top5 中的用户顺序一致。

这里的 order by field(id, ...) 是关键,它确保了从数据库返回的数据顺序和 Redis 返回的 top5 用户顺序完全匹配。因为 Redis 中的 ZSet 是有顺序的,top5 会按照点赞数量进行排序。如果直接使用 listByIds 方法,可能会导致结果顺序不一致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

响应式编程_04Spring 5 中的响应式编程技术栈_WebFlux 和 Spring Data Reactive

文章目录 概述响应式Web框架Spring WebFlux响应式数据访问Spring Data Reactive 概述 https://spring.io/reactive 2017 年&#xff0c;Spring 发布了新版本 Spring 5&#xff0c; Spring 5 引入了很多核心功能&#xff0c;这其中重要的就是全面拥抱了响应式编程的设计思想和实…

C++ Primer 算术运算符

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

中位数定理:小试牛刀> _ <2025牛客寒假1

给定数轴上的n个点&#xff0c;找出一个到它们的距离之和尽量小的点&#xff08;即使我们可以选择不是这些点里的点&#xff0c;我们还是选择中位数的那个点最优&#xff09; 结论:这些点的中位数就是目标点。可以自己枚举推导&#xff08;很好想&#xff09; (对于 点的数量为…

安全实验作业

一 拓扑图 二 要求 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff1b;R4与其他所有直连设备间均使用共有IP 2、R3-R5-R6-R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b; 4、所有设备均可访问R4的环回&#x…

《Python预训练视觉和大语言模型》:从DeepSeek到大模型实战的全栈指南

就是当代AI工程师的日常&#xff1a;* - 砸钱买算力&#xff0c;却卡在分布式训练的“隐形坑”里&#xff1b; - 跟着论文复现模型&#xff0c;结果连1/10的性能都达不到&#xff1b; - 好不容易上线应用&#xff0c;却因伦理问题被用户投诉…… 当所有人都在教你怎么调用…

血压计OCR文字检测数据集VOC+YOLO格式2147张11类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2147 标注数量(xml文件个数)&#xff1a;2147 标注数量(txt文件个数)&#xff1a;2147 …

Java 面试合集(2024版)

种自己的花&#xff0c;爱自己的宇宙 目录 第一章-Java基础篇 1、你是怎样理解OOP面向对象??? 难度系数&#xff1a;? 2、重载与重写区别??? 难度系数&#xff1a;? 3、接口与抽象类的区别??? 难度系数&#xff1a;? 4、深拷贝与浅拷贝的理解??? 难度系数&…

Typora免费使用

一.下载地址 https://typoraio.cn/ 二.修改配置文件 1.找到安装路径下的LicenseIndex.180dd4c7.4da8909c.chunk.js文件 文件路径为:安装路径\resources\page-dist\static\js\LicenseIndex.180dd4c7.4da8909c.chunk.js 将js中的 e.hasActivated"true"e.hasActiva…

第一性原理:游戏开发成本的思考

利润 营收-成本 营收定价x销量x分成比例 销量 曝光量x 点击率x &#xff08;购买率- 退款率&#xff09; 分成比例 100%- 平台抽成- 税- 引擎费- 发行抽成 成本开发成本运营成本 开发成本 人工外包办公地点租金水电设备折旧 人工成本设计成本开发成本迭代修改成本后续内容…

裁员潮血洗硅谷,普通人惨遭裁员的血泪教训——要随时做好失业的准备

我大学室友21年暑假在meta的某AI组实习过&#xff0c;压力巨大&#xff01;组里大群天天消息99&#xff0c;年底实习结束直接就进到Google去了&#xff0c;听说eng组的intern十有八九都拿到了return offer&#xff0c;但都利用空余时间跳到了别的大厂。 离谱的时候&#xff0c…

MacBook Pro(M1芯片)Qt环境配置

MacBook Pro&#xff08;M1芯片&#xff09;Qt环境配置 1、准备 试图写一个跨平台的桌面应用&#xff0c;此时想到了使用Qt&#xff0c;于是开始了搭建开发环境&#xff5e; 在M1芯片的电脑上安装&#xff0c;使用brew工具比较方便 Apple Silicon&#xff08;ARM/M1&#xf…

智能编码在前端研发的创新应用

一、前端开发实例 今天主要想分享一些关于大模型如何协助我们进行前端编码的实践。首先&#xff0c;让我们以一个前端开发的实例开始。通常&#xff0c;当需要实现一个新的前端功能时&#xff0c;我们会收到相关的背景和需求描述。我的期望是&#xff0c;大模型能够直接使用这…

基于RK3588/RK3576+MCU STM32+AI的储能电站电池簇管理系统设计与实现

伴随近年来新型储能技术的高质量规模化发展&#xff0c;储能电站作为新能源领域的重要载体&#xff0c; 旨在配合逐步迈进智能电网时代&#xff0c;满足电力系统能源结构与分布的创新升级&#xff0c;给予相应规模 电池管理系统的设计与实现以新的挑战。同时&#xff0c;电子系…

Django框架丨从零开始的Django入门学习

Django 是一个用于构建 Web 应用程序的高级 Python Web 框架&#xff0c;Django是一个高度模块化的框架&#xff0c;使用 Django&#xff0c;只要很少的代码&#xff0c;Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容&#xff0c;并进一步开发出全功能…

稀疏混合专家架构语言模型(MoE)

注&#xff1a;本文为 “稀疏混合专家架构语言模型&#xff08;MoE&#xff09;” 相关文章合辑。 手把手教你&#xff0c;从零开始实现一个稀疏混合专家架构语言模型&#xff08;MoE&#xff09; 机器之心 2024年02月11日 12:21 河南 选自huggingface 机器之心编译 机器之心…

spring基础总结

先修知识&#xff1a;依赖注入&#xff0c;反转控制&#xff0c;生命周期 IDEA快捷键 Ctrl Altm:提取方法&#xff0c;设置trycatch 通用快捷键&#xff1a; Ctrl F&#xff1a;在当前文件中查找文本。Ctrl R&#xff1a;在当前文件中替换文本。Ctrl Z&#xff1a;撤销…

openRv1126 AI算法部署实战之——TensorFlow TFLite Pytorch ONNX等模型转换实战

Conda简介 查看当前系统的环境列表 conda env list base为基础环境 py3.6-rknn-1.7.3为模型转换环境&#xff0c;rknn-toolkit版本V1.7.3&#xff0c;python版本3.6 py3.6-tensorflow-2.5.0为tensorflow模型训练环境&#xff0c;tensorflow版本2.5.0&#xff0c;python版本…

java进阶1——JVM

java进阶——JVM 1、JVM概述 作用 Java 虚拟机就是二进制字节码的运行环境&#xff0c;负责装载字节码到其内部&#xff0c;解释/编译为对 应平台上的机器码指令行&#xff0c;每一条 java 指令&#xff0c;java 虚拟机中都有详细定义&#xff0c;如怎么取操 作数&#xff0c…

基于深度学习的视觉检测小项目(十六) 用户管理界面的组态

分组和权限&#xff1a; 用户分为三个组&#xff0c;管理员、普通用户、访客。 • 管理员的权限和作业范围&#xff1a; 添加和删除用户、更改所有用户的信息&#xff08;用户名、登录密码、所在分组等&#xff09;、查看和备份以及复制数据库&#xff1b; • 普通用户的权限和…

Docker使用指南(一)——镜像相关操作详解(实战案例教学,适合小白跟学)

目录 1.镜像名的组成 2.镜像操作相关命令 镜像常用命令总结&#xff1a; 1. docker images 2. docker rmi 3. docker pull 4. docker push 5. docker save 6. docker load 7. docker tag 8. docker build 9. docker history 10. docker inspect 11. docker prune…