redis实战——feed流实现收件箱滚动查询

73 阅读3分钟

思路

分页查询会重复查询

需求:在个人主页的“关注”卡片中,查询并展示推送的blog信息

image.png

这里是一个SortedSet集合当做收件箱,首先把这个score当成时间戳,value当成笔记的id

image.png

使用Zrevrange进行倒序排序

image.png

新增一个最大score的值,然后进行角标分页查询,因为上一个查了三个元素出来,所以这次查询的偏移量为3,此时我们看到,m4被重复查询了

image.png

按照Score值进行查询

使用revrangebyscore进行根据score的排序查询

image.png

上面三个结果查出来之后,去查找下面的结果,limit变成了1 3 是因为5已经被查询过了,所以要查找不等于5的数目

image.png

假如有2个一样的时间戳

image.png

按照原来的方法进行查询会有重复查询的情况,因为只偏移了第一个最小值,但是还有一个最小值。

image.png

所以,我们每一次查询后,假如最后x个查询结果是一样的,就要在下次查找中把之前查找到的x个结果给跳过去。

image.png

所以滚动查询有四个参数 max:当前时间戳(第一次插叙) | 上次查询的最小时间戳 min: 0 (无关心) offset:在上一次查询结果中,与最小值元素一样的个数 count: 每页几个元素

实现

具体操作如下:

1、每次查询完成后,我们要分析出查询出数据的最小时间戳,这个值会作为下一次查询的条件

2、我们需要找到与上一次查询相同的查询个数作为偏移量,下次查询时,跳过这些查询过的数据,拿到我们需要的数据

这两个参数第一次会由前端来指定,以后的查询就根据后台结果作为条件,再次传递到后台。

一、定义出来具体的返回值实体类

@Data
public class ScrollResult {
    private List<?> list;
    private Long minTime;
    private Integer offset;
}

BlogController

注意:RequestParam 表示接受url地址栏传参的注解,当方法上参数的名称和url地址栏不相同时,可以通过RequestParam 来进行指定

@GetMapping("/of/follow")
public Result queryBlogOfFollow(
    @RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){
    return blogService.queryBlogOfFollow(max, offset);
}

BlogServiceImpl

@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
    // 1.获取当前用户
    Long userId = UserHolder.getUser().getId();
    // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
    String key = FEED_KEY + userId;
    Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
        .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
    // 3.非空判断
    if (typedTuples == null || typedTuples.isEmpty()) {
        return Result.ok();
    }
    // 4.解析数据:blogId、minTime(时间戳)、offset
    List<Long> ids = new ArrayList<>(typedTuples.size());
    long minTime = 0; // 2
    int os = 1; // 2
    for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
        // 4.1.获取id
        ids.add(Long.valueOf(tuple.getValue()));
        // 4.2.获取分数(时间戳)
        long time = tuple.getScore().longValue();
        if(time == minTime){
            os++;
        }else{
            minTime = time;
            os = 1;
        }
    }
	os = minTime == max ? os : os + offset;
    // 5.根据id查询blog
    String idStr = StrUtil.join(",", ids);
    List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

    for (Blog blog : blogs) {
        // 5.1.查询blog有关的用户
        queryBlogUser(blog);
        // 5.2.查询blog是否被点赞
        isBlogLiked(blog);
    }

    // 6.封装并返回
    ScrollResult r = new ScrollResult();
    r.setList(blogs);
    r.setOffset(os);
    r.setMinTime(minTime);

    return Result.ok(r);
}

原作者:黑日之潮 本文复制自:juejin.cn/post/726854…