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

 更新时间:2025年02月05日 17:14:49   作者:记得开心一点嘛  
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(有序集合) 是一个非常适合用于处理 排行榜计数问题 的数据结构。

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

ZSET 数据结构

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

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

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

优化高并发的点赞操作

高并发情况下,当多个用户同时对某个内容进行点赞时,我们需要高效地更新该内容的点赞数,并保证数据一致性。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: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 方法,可能会导致结果顺序不一致。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Redis存储断点续传文件状态的最佳实践

    Redis存储断点续传文件状态的最佳实践

    在断点续传系统中,如何高效地存储和更新文件上传状态是关键,得益于 Redis 高效的内存操作和多种数据结构的支持,它非常适合用于存储上传过程中的临时状态信息,下面,我们将探讨如何利用 Redis 实现文件上传状态的存储,需要的朋友可以参考下
    2024-12-12
  • redis锁机制介绍与实例

    redis锁机制介绍与实例

    今天小编就为大家分享一篇关于redis锁机制介绍与实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Redis的主从复制机制以及主从复制的实现方法

    Redis的主从复制机制以及主从复制的实现方法

    本文介绍了Redis的主从复制机制,包括异步同步、快照同步、增量同步以及如何配置主从服务器,主从同步包括增量同步和快照同步两种方式,增量同步通过指令流实现,快照同步通过快照文件传输数据,主从复制可以提高系统的稳定性和性能,但也需要合理配置资源以避免资源浪费
    2026-02-02
  • redis key过期监听的实现示例

    redis key过期监听的实现示例

    在Redis中,我们可以为Key设置过期时间,当Key的过期时间到达后,Redis会自动将该Key标记为已失效,本文就来介绍一下redis key过期监听的实现示例,感兴趣的可以了解一下
    2024-03-03
  • redis使用不当导致应用卡死bug的过程解析

    redis使用不当导致应用卡死bug的过程解析

    本文主要记一次找因redis使用不当导致应用卡死bug的过程,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • Redis 高可用与集群原理深度解析

    Redis 高可用与集群原理深度解析

    Redis高可用机制涵盖主从复制、哨兵自动故障转移及Cluster集群分片与高可用,依赖Gossip协议实现节点通信与状态同步,源码解析了故障转移流程,为后续持久化内容奠定基础,本文给大家介绍Redis高可用与集群原理,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • 基于Redis实现抽奖功能及问题小结

    基于Redis实现抽奖功能及问题小结

    这篇文章主要介绍了基于Redis实现抽奖功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • Redis之十大数据类型解读

    Redis之十大数据类型解读

    文章主要介绍了Redis的基本操作命令,包括key管理、string、list、hash、set、zset、bitmap、HyperLogLog、GEO、stream、bitfields等数据类型的操作方法和应用场景
    2026-04-04
  • Redis设置键的生存时间或过期时间的方法详解

    Redis设置键的生存时间或过期时间的方法详解

    这篇文章主要介绍了Redis如何设置键的生存时间或过期时间,通过EXPIRE命令或者PEXIPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间,文中有详细的代码供供大家参考,需要的朋友可以参考下
    2024-03-03
  • Redis Stat的安装指南

    Redis Stat的安装指南

    这篇文章主要介绍了Redis Stat的安装指南的相关资料,需要的朋友可以参考下
    2016-04-04

最新评论