使用Redis的zset集合实现小程序的滚动分页

这篇具有很好参考价值的文章主要介绍了使用Redis的zset集合实现小程序的滚动分页。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、 Redis中,使用有序集合(sorted set)实现滚动分页的原理如下:

  1. 将每个文档的 score 值设置为时间戳(或根据其他规则计算的分数),将文档的 ID 作为 value,然后将其添加到有序集合中。
  2. 获取当前时间戳,作为查询时间点。
  3. 使用 ZRANGEBYSCORE 命令根据 score 值范围查询出 score 值在当前时间戳之前的所有文档 ID。
  4. 返回查询结果作为当前页的结果集。
  5. 将当前页的最后一个文档 ID 作为新的查询起点,重复以上步骤,直到遍历所有文档。

二、Redis中,(sorted set)命令详细说明

Redis中的sorted set(有序集合)是一个数据结构,它允许你存储一组有序的元素(成员),每个元素可以有一个分数(score),分数可以用于排序、限制范围或聚合操作等。sorted set是自动排序的,并且所有的成员都是唯一的,不允许重复。

以下是在Redis中操作sorted set的命令:

1 ZADD key score member:

向有序集合中添加一个元素。

ZADD myset 7.5 "apple"  
ZADD myset 9.0 "orange"  
ZADD myset 8.2 "banana"

2 ZRANGE key start stop [WITHSCORES]:

返回有序集合中指定范围内的元素。

ZRANGE myset 0 -1 WITHSCORES  
ZRANGE myset 1 2 WITHSCORES

结果:

127.0.0.1:6379> ZRANGE myset 0 -1 WITHSCORES  
1) "apple"
2) "7.5"
3) "banna"
4) "8.1999999999999993"
5) "orange"
6) "9"
127.0.0.1:6379> ZRANGE myset 1 2 WITHSCORES  
1) "banna"
2) "8.1999999999999993"
3) "orange"
4) "9"

3 ZREVRANGE key start stop [WITHSCORES]:

返回有序集合中指定范围内的元素,按照分数从大到小排序,与 ZRANGE 返回的结果相反。

ZREVRANGE myset 0 -1 WITHSCORES  
ZREVRANGE myset 1 2 WITHSCORES

结果:

127.0.0.1:6379> ZREVRANGE myset 0 -1 withscores
1) "orange"
2) "9"
3) "banna"
4) "8.1999999999999993"
5) "apple"
6) "7.5"
127.0.0.1:6379> ZREVRANGE myset 1 2 withscores
1) "banna"
2) "8.1999999999999993"
3) "apple"
4) "7.5"

4 ZREM key member:

从有序集合中删除指定元素。

ZREM myset "apple"  
ZREM myset "banana"

5 ZCARD key:

返回有序集合中元素的数量。

ZCARD myset
127.0.0.1:6379> ZCARD myset 
(integer) 2

6 ZSCORE key member:

返回指定元素在有序集合中的分数。

ZSCORE myset "banana"
127.0.0.1:6379> ZSCORE myset "banana"
"7.9000000000000004"

7 ZREMRANGEBYSCORE key min max:

删除有序集合中分数在指定范围内的所有元素

127.0.0.1:6379> ZREMRANGEBYSCORE myset 6.5 8.5
(integer) 2

8 ZREMRANGE BY PRIORITY key min max:

删除有序集合中优先级在指定范围内的所有元素。与ZREMRANGE BY SCORE命令类似。

三、具体实现步骤如下:

1 向有序集合中添加文档:

在文档添加时,为每个文档添加一个时间戳作为score值,并将其文档ID作为value值。例如,使用以下Java代码向有序集合中添加文档:

ZAddArgs zAddArgs = new ZAddArgs(score, value);  
redis.zAdd("docs", zAddArgs);

2 获取当前时间戳:

使用Java的System.currentTimeMillis()方法获取当前时间戳。

3 查询score值在当前时间戳之前的文档ID

使用以下Java代码查询有序集合中score值在当前时间戳之前的文档ID:

ZRangeArgs zRangeArgs = new ZRangeArgs(0, -1, score -> score < System.currentTimeMillis());  
List<String> docIds = redis.zRangeByScore("docs", zRangeArgs);

其中,ZRangeArgs构造函数中的参数0表示起始位置为0,-1表示结束位置为集合末尾,score -> score < System.currentTimeMillis()表示只返回score值小于当前时间戳的文档ID。

4 返回查询结果作为当前页的结果集:

将查到的文档ID作为当前页的结果集返回。

5 将当前页的最后一个文档ID作为新的查询起点,重复以上步骤,直到遍历所有文档。

例如,使用以下Java代码将当前页的最后一个文档ID作为新的查询起点:

String lastDocId = docIds.get(docIds.size() - 1);  
docIds = redis.zRangeByScore("docs", new ZRangeArgs(0, -1, score -> score < System.currentTimeMillis() - 86400000L), lastDocId, "LIMIT");

其中,ZRangeArgs构造函数中的参数表示从集合的起始位置开始返回文档ID,lastDocId表示只返回大于lastDocId且score值小于当前时间戳的文档ID,86400000L表示一天的毫秒数,表示向前滚动一页。

四、实例

1 回顾传统分页

传统的分页 前端参数一般传入当前页数curpage和页面长度paegsize 最终通过数据库limit curpage*(pageszie-1),pageszie 实现分页 假设两参数分别为1,5 即 limit 0,5 也就是查询序号0到4的5条数据 这时如果数据库新增了一条数据其序号为1。

如果查询下一页即limit 5,5 查询序号为5 到9的数据
redis zset分页,# redis,Java 2023面试大全,redis,数据库,缓存
如图所示,很显然值为四的数据被重复查了。 查了下比较流行的做法就是新增一个字段,记录数据插入的时间。 然后查寻第一页的时候记录当前时间,之后每次分页查询都需要带上这个时间 把比这个时间大的数据排除。该方案挺不错,但是要修改数据库,费事.

2 使用Redis的zset分页

1 分页存在的问题

每次插入数据库成功时,额外保存一份<数据id,插入时间>到redis的有序集合里 。这时就可以通过插入时间分页了。第一次查询返回按时间排序前1到5的数据, 然后记录当前的时间6 。 之后的查询带上这个时间。 返回从 比这个时间小的第一个数据(即为5)和其后的四条数据。
redis zset分页,# redis,Java 2023面试大全,redis,数据库,缓存
但是这也有个问题 假如同一时间新增很多条相同数据怎么办。
redis zset分页,# redis,Java 2023面试大全,redis,数据库,缓存
引入个新的变量offset 记录返回的数据中有几个和 他们最后一个数据时间相同 上图
第一次查询返回 10 9 8 7 6 6即为2。 然后下一次查询的参数即为最后一个数据的时间戳6 ,偏移量2 ,就能确定是从第三个6开始了

2 分页方法说明
ZREVRANGEBYSCORE key Max Min LIMIT offset count
ZREVRANGEBYSCORE z1 5 0 withscores limit 1 3

分页参数:
max: 当前时间戳 | 上一次查询人最小时间戳
min:0 最小值,不变
offset: 0 | 取决于上一次的结果,与最小元素的个数
count: 3 分页的页面大小,相当于pageSize

3 在点评小程序的应用

在本小程序中,我们要将粉丝关注我的博客数排序

  • 保存博客时,将我的博客发送到粉丝的信箱,也就是按粉丝的id与博客id对保存到redis中
 @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        blog.setUserId(userId);
        // 保存探店博客
        if (blogService.save(blog)) {
            Long currTime = System.currentTimeMillis();
//        博客推送给关注作者的人
//        1获得关注该作者的用户列表
            List<Follow> follows = followService.query().select("user_id").eq("follow_user_id", userId).list();
            for (Follow follow : follows
            ) {
                Long followId = follow.getUserId();
//
                String key = RedisConstants.FEED_KEY + followId;
                stringRedisTemplate.opsForZSet().add(key,String.valueOf(blog.getId()),currTime);
            }
            // 返回id
            return Result.ok(blog.getId());
        }
        return Result.fail("发布笔记失败");
    }
  • 读取信箱,按评分进行分页
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
     //1 获取当前用户
     Long userId = UserHolder.getUser().getId();
     //2 查询收件箱
     String key = RedisKey.FEED_KEY + userId;
     //3 解析数据 blogId、minTime时间戳、offset
     Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);
     //4 非空判断
     if (tuples == null || tuples.isEmpty()) {
         return Result.ok();
     }
     //5 遍历
     long minTime = 0; //获取时间错最小值,遍历完最后一个值
     int offsetResutl = 1; //偏移量,为最小值的个数
     List<Long> ids = new ArrayList<>(tuples.size());
     for (ZSetOperations.TypedTuple<String> tuple : tuples) {
         long time = tuple.getScore().longValue();
         if(time == minTime){
             offsetResutl ++ ;
         }else {
             //如果与最小时间不同,则最小时间重新赋值,将 offsetFirst重新赋值为1
             minTime = time;
             offsetResutl = 1;
         }
         String blogId = tuple.getValue();
         ids.add(Long.valueOf(blogId));
     }
     //6 根据id 查询blog
     String idsStr = StrUtil.join(",", ids);
     List<Blog> blogs = this.lambdaQuery().eq(Blog::getId, ids).last("order by field(id," + idsStr + ")").list();
     //7 封装结果
     ScrollResult scrollResult = new ScrollResult();
     scrollResult.setList(blogs);
     scrollResult.setOffset(offsetResutl);
     scrollResult.setMinTime(minTime);
     return Result.ok(scrollResult);
 }

UserHolder 实体类

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

UserDTO 实体类

@Data
public class UserDTO {
    private Long id;
    private String nickName;
    private String icon;
}

Blog 实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_blog")
public class Blog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 商户id
     */
    private Long shopId;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 用户图标
     */
    @TableField(exist = false)
    private String icon;
    /**
     * 用户姓名
     */
    @TableField(exist = false)
    private String name;
    /**
     * 是否点赞过了
     */
    @TableField(exist = false)
    private Boolean isLike;

    /**
     * 标题
     */
    private String title;

    /**
     * 探店的照片,最多9张,多张以","隔开
     */
    private String images;

    /**
     * 探店的文字描述
     */
    private String content;

    /**
     * 点赞数量
     */
    private Integer liked;

    /**
     * 评论数量
     */
    private Integer comments;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;


}

Result实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Boolean success;
    private String errorMsg;
    private Object data;
    private Long total;

    public static Result ok(){
        return new Result(true, null, null, null);
    }
    public static Result ok(Object data){
        return new Result(true, null, data, null);
    }
    public static Result ok(List<?> data, Long total){
        return new Result(true, null, data, total);
    }
    public static Result fail(String errorMsg){
        return new Result(false, errorMsg, null, null);
    }
}

Follow实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_follow")
public class Follow implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 关联的用户id
     */
    private Long followUserId;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;


}

五、源码下载

https://gitee.com/charlinchenlin/koo-erp文章来源地址https://www.toymoban.com/news/detail-769487.html

到了这里,关于使用Redis的zset集合实现小程序的滚动分页的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Redis--Zset使用场景举例(滑动窗口实现限流)

    前言 在Redis–Zset的语法和使用场景举例(朋友圈点赞,排行榜)一文中,提及了redis数据结构zset的指令语法和一些使用场景,今天我们使用zset来实现滑动窗口限流,详见下文。 什么是滑动窗口 滑动窗口是一种流量控制策略,用于控制一定时间内请求的访问数量。 其原理是

    2024年01月19日
    浏览(54)
  • 微服务—Redis实用篇-黑马头条项目-达人探店功能(使用set与zset实现)

    1.1、达人探店-发布探店笔记 发布探店笔记 探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个: tb_blog:探店笔记表,包含笔记中的标题、文字、图片等 tb_blog_comments:其他用户对探店笔记的评价 具体发布流程 上传接口 注意:同学们在操作时,需要修改Syste

    2024年02月05日
    浏览(47)
  • 使用Python读写Redis——Zsets

    之前详细介绍了 Redis命令 - Zsets命令组常用命令,同样的命令,本文将用python调用redis库封装好的方法。 1、ZADD key score member [score] [member] 2、ZRANGE key start stop [WITHSCORES] 3、ZCARD key 4、ZRANK key member 5、ZSCORE key member 6、ZCOUNT key min max 7、ZINCRBY key increment member 8、ZREVRANGE key start stop

    2024年01月23日
    浏览(31)
  • [Redis] 数据结构zset压缩列表实现和跳表实现讲解

    😚一个不甘平凡的普通人,致力于为Golang社区和算法学习做出贡献,期待您的关注和认可,陪您一起学习打卡!!!😘😘😘 🤗专栏:算法学习 🤗专栏:Go实战 💬个人主页:个人主页 跳表问题 redis 有五种数据结构:string,hash,list,set,zset 压缩列表 或者 跳表 实现 压缩

    2024年02月05日
    浏览(52)
  • Java使用Redis实现分页功能

    分页功能实现应该是比较常见的,对于redis来说,近期刷题就发现了lrange、zrange这些指令,这个指令怎么使用呢? 我们接下来就来讲解下。 lrange 是 Redis 中的一个命令,用于从 列表中 获取指定范围内的元素。 语法:lrange key start end start 和 end是两个整数,表示要从列表中获取

    2024年02月21日
    浏览(47)
  • 风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法

    个人博客:无奈何杨(wnhyang) 个人语雀:wnhyang 共享语雀:在线知识共享 Github:wnhyang - Overview 引用 AI 对于风控系统的介绍 风控系统是一种用于在线业务的安全管理系统,它帮助企业和平台防范潜在的欺诈、信用风险以及不合规行为。简单来说,它的核心作用就是“保安全

    2024年03月14日
    浏览(47)
  • Redis实战 | 使用Redis 的有序集合(Sorted Set)实现排行榜功能,和Spring Boot集成

    专栏集锦,大佬们可以收藏以备不时之需 Spring Cloud实战专栏:https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏:https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏:https://blog.csdn.net/superdangbo/category_9271502.html tensorflow专栏:https://blog.csdn.net/superdangbo/category_869

    2024年02月05日
    浏览(77)
  • Redis Zset类型

    Zset(有序集合)它是集合的一种,不仅可以保存元素,还可以为每个元素关联一个 double 类型的分数(score),Redis 正是通过分数来为集合中的元素进行从小到大的排序。在 Zset 中,元素的值是唯一的,但分数(score)却可以重复。 有序集合 :该有序集合显示了三国中的武将

    2024年01月19日
    浏览(39)
  • Redis——zset类型详解

    zset是有序集合,将zset中的members引入一个属性score,根据这个属性值来进行排序,其中members不可以重复,score可以重复(按照字典序排序),默认按照升序排序 有序集合中提供指定分数和元素范围查找,计算成员排名功能,一般用于排行榜系统 zset中的member和score并不是键值对

    2024年02月09日
    浏览(38)
  • redis的zset跳表

    redis用了哪些算法来实现他的数据结构? 读懂才会用 : 带你见识 Redis 的 zset 读懂才会用:Redis ZSet 的几种使用场景 跳表中每一层节点数是其下一层的一半(或其他固定比例),是为了在保证查找效率的同时,尽可能地减少额外的空间消耗。当我们在跳表中查找一个元素时,

    2024年02月14日
    浏览(29)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包