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

2025-02-06 16:50

本文主要是介绍Redis如何使用zset处理排行榜和计数问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Redis如何使用zset处理排行榜和计数问题》Redis的ZSET数据结构非常适合处理排行榜和计数问题,它可以在高并发的点赞业务中高效地管理点赞的排名,并且由于ZSET的排序特性,可以轻松实现根据...

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

在处理计数业务时,我们一般会使用一个数据结构,既是集合又可以保证唯一性,所以我们会选择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处理排行榜和计数问题

Redis 的 ZSEphpT(有序集合) 是一个非常适合用于处理 排行榜计数问题 的数据结构。

在高并发的点赞业务中,使用 ZSET 可以帮助我们高效地管理点赞的排名,并且由于 ZSET 的排序特性,我们可以轻松实现根据点赞数实时排序的功能。

ZSET 数据结构

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

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

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

优化高并发的点赞操作

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

  • 用户点赞操作:使用 ZINCRBY 命令来对某个元素的分数进行增量操作,表示对该内容的点赞数增加。
  • 查看点赞数:可以通过 ZSCORE 命令获取某个内容的当前点赞数。
  • 查看排行榜:使用 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:likerOrIEcWUa:" +android 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("orChina编程der by field(id," + idStr + ")") 

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

这里的 order by field(id, ...) 是关键,它确保了从数据库返回的数据顺序和 Redis 返回的 top5 用户顺序完全匹配。因为 Redis 中的 ZSet 是有顺序的,top5 会按照点赞数量进行排序。

如果直接使用 listByIds 方法,可能会导致结果顺序不一致。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Redis如何使用zset处理排行榜和计数问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153314

相关文章

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

使用PyQt实现简易文本编辑器

《使用PyQt实现简易文本编辑器》这篇文章主要为大家详细介绍了如何使用PyQt5框架构建一个简单的文本编辑器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录分析主窗口类 (MyWindow)菜单操作语法高亮 (SyntaxHighlighter)运行程序主要组件代码图示分析实现

deepseek本地部署使用步骤详解

《deepseek本地部署使用步骤详解》DeepSeek是一个开源的深度学习模型,支持自然语言处理和推荐系统,本地部署步骤包括克隆仓库、创建虚拟环境、安装依赖、配置模型和数据、启动服务、调试与优化以及... 目录环境要求部署步骤1. 克隆 DeepSeek 仓库2. 创建虚拟环境3. 安装依赖4. 配置模型

微服务架构之使用RabbitMQ进行异步处理方式

《微服务架构之使用RabbitMQ进行异步处理方式》本文介绍了RabbitMQ的基本概念、异步调用处理逻辑、RabbitMQ的基本使用方法以及在SpringBoot项目中使用RabbitMQ解决高并发... 目录一.什么是RabbitMQ?二.异步调用处理逻辑:三.RabbitMQ的基本使用1.安装2.架构

在idea中使用mysql数据库超详细教程

《在idea中使用mysql数据库超详细教程》:本文主要介绍如何在IntelliJIDEA中连接MySQL数据库,并使用控制台执行SQL语句,还详细讲解了如何使用MyBatisGenerator快... 目录一、连接mysql二、使用mysql三、快速生成实体、接口、sql文件总结一、连接mysql在ID

Sentinel 断路器在Spring Cloud使用详解

《Sentinel断路器在SpringCloud使用详解》Sentinel是阿里巴巴开源的一款微服务流量控制组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、... 目录Sentinel 介绍同类对比Hystrix:Sentinel:微服务雪崩问题问题原因问题解决方案请

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

使用TomCat,service输出台出现乱码的解决

《使用TomCat,service输出台出现乱码的解决》本文介绍了解决Tomcat服务输出台中文乱码问题的两种方法,第一种方法是修改`logging.properties`文件中的`prefix`和`... 目录使用TomCat,service输出台出现乱码问题1解决方案问题2解决方案总结使用TomCat,