• 设为首页
  • 收藏本站
  • 积分充值
  • VIP赞助
  • 手机版
  • 微博
  • 微信
    微信公众号 添加方式:
    1:搜索微信号(888888
    2:扫描左侧二维码
  • 快捷导航
    福建二哥 门户 查看主题

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

    发布者: 福建二哥 | 发布时间: 2025-6-19 12:45| 查看数: 100| 评论数: 0|帖子模式

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

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

    业务逻辑

    用户点击点赞按钮,需要再set集合内判断是否已点赞,未点赞则需要将点赞数+1并保存用户信息到集合中,已点赞则需要将数据库点赞数-1并移除set集合中的用户。
    1. @Service
    2. public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    3.     @Autowired
    4.     private IUserService userService;
    5.     @Resource
    6.     private StringRedisTemplate stringRedisTemplate;

    7.     @Override
    8.     public Result likeBlog(Long id) {
    9.         // 获取登录用户
    10.         Long userId = UserHolder.getUser().getId();
    11.         // 判断当前登录用户是否已经点赞
    12.         String key = "blog:like:" + id;
    13.         Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
    14.         if(BooleanUtil.isFalse(isMember)){
    15.             // 未点赞
    16.             // 数据库点赞数+1
    17.             boolean isSuccess = update().setSql("like = like + 1").eq("id",id).update();
    18.             // 保存用户到Redis集合中
    19.             if(isSuccess){
    20.                 stringRedisTemplate.opsForSet().add(key, userId.toString());
    21.             }
    22.         } else {
    23.             // 已点赞,取消点赞
    24.             // 数据库点赞数-1
    25.             boolean isSuccess = update().setSql("like = like - 1").eq("id",id).update();
    26.             // 移除set集合中的用户
    27.             stringRedisTemplate.opsForSet().remove(key, userId.toString());
    28.         }
    29.         return Result.ok();
    30.     }
    31. }
    复制代码
    那么我们想要实现按照点赞时间的先后顺序排序,返回Top5的用户,这个时候set无法保证数据有序,所以我们需要换一个数据结构满足业务需求:

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

    ZSET 数据结构

    Redis 的 ZSET 是一个集合,它的每个元素都会关联一个 分数(score),这个分数决定了元素在集合中的排序。ZSET 保证集合中的元素是按分数排序的,并且可以在 O(log(N)) 的时间复杂度内进行添加、删除和查找操作
    在高并发的点赞业务中,ZSET 可以帮助我们轻松地进行以下几项操作:

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

    优化高并发的点赞操作

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

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

    ZSET 结构设计


      1. key
      复制代码
      :表示某个内容的点赞的 id。
      1. value
      复制代码
      :表示点赞用户的 id。
      1. score
      复制代码
      :根据点赞时间排序。
    下面是修改后的点赞逻辑:
    1. @Service
    2. public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    3.     @Autowired
    4.     private IUserService userService;
    5.     @Resource
    6.     private StringRedisTemplate stringRedisTemplate;

    7.     @Override
    8.     public Result likeBlog(Long id) {
    9.         // 获取登录用户
    10.         Long userId = UserHolder.getUser().getId();
    11.         // 判断当前登录用户是否已经点赞
    12.         String key = "blog:like:" + id;
    13.         Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
    14.         if(score == null){
    15.             // 未点赞
    16.             // 数据库点赞数+1
    17.             boolean isSuccess = update().setSql("like = like + 1").eq("id",id).update();
    18.             // 保存用户到Redis集合中
    19.             if(isSuccess){
    20.                 stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
    21.             }
    22.         } else {
    23.             // 已点赞,取消点赞
    24.             // 数据库点赞数-1
    25.             boolean isSuccess = update().setSql("like = like - 1").eq("id",id).update();
    26.             // 移除set集合中的用户
    27.             stringRedisTemplate.opsForZSet().remove(key, userId.toString());
    28.         }
    29.         return Result.ok();
    30.     }
    31. }
    复制代码
    而点赞排行榜代码如下:
    1. @Service
    2. public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    3.     @Autowired
    4.     private IUserService userService;
    5.     @Resource
    6.     private StringRedisTemplate stringRedisTemplate;

    7.     @Override
    8.     public Result queryBlogLikes(Long id) {
    9.         String key = "blog:like:" + id;
    10.         // 查询top5的点赞用户 zrange key 0 4
    11.         Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
    12.         if (top5 == null || top5.isEmpty()) {
    13.             return Result.ok(Collections.emptyList());
    14.         }
    15.         // 解析出集合中的用户的id
    16.         List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
    17.         // 根据id查询用户,并将类型由User转为UserDTO,随后转换为List集合
    18.         String idStr = StrUtil.join(",",ids);
    19. //        List<UserDTO> userDTOs = userService.listByIds(ids).stream()
    20. //                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
    21. //                .collect(Collectors.toList());
    22.         List<UserDTO> userDTOs = userService.query()
    23.                 .in("id",ids).last("order by field(id," + idStr +")").list()
    24.                 .stream()
    25.                 .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
    26.                 .collect(Collectors.toList());
    27.         return Result.ok(userDTOs);
    28.     }
    29. }
    复制代码
    使用
    1. userService.query().in("id", ids).last("order by field(id," + idStr + ")")
    复制代码
    来查询用户信息,并且使用
    1. order by field(id, ...)
    复制代码
    语句来保证查询结果的顺序与
    1. top5
    复制代码
    中的用户顺序一致。
    这里的
    1. order by field(id, ...)
    复制代码
    是关键,它确保了从数据库返回的数据顺序和 Redis 返回的
    1. top5
    复制代码
    用户顺序完全匹配。因为 Redis 中的 ZSet 是有顺序的,
    1. top5
    复制代码
    会按照点赞数量进行排序。
    如果直接使用
    1. listByIds
    复制代码
    方法,可能会导致结果顺序不一致。

    总结

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

    来源:https://www.jb51.net/database/33515069u.htm
    免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    ×

    最新评论

    浏览过的版块

    QQ Archiver 手机版 小黑屋 福建二哥 ( 闽ICP备2022004717号|闽公网安备35052402000345号 )

    Powered by Discuz! X3.5 © 2001-2023

    快速回复 返回顶部 返回列表