Redis实战案例24-关注推送

1. Feed流实现方案

Redis实战案例24-关注推送_第1张图片
Redis实战案例24-关注推送_第2张图片

拉模式主要缺点,延迟问题,极端情况某个用户关注了成千上万的up主,每位up主又发布了十几条博客,此时拉模式的延迟就会很高;

Redis实战案例24-关注推送_第3张图片

推模式缺点也很明显,内存消耗太大,假设up主是千万级别的,此时往每个粉丝的收件箱都要发送推文,收件箱要保存上亿份,消耗的内存太大;

Redis实战案例24-关注推送_第4张图片

对于普通的up主采用的是推模式,也就是直接发送给粉丝的收件箱,而自己没有发件箱;
而对于大V,有两种情况,如果是活跃度高的粉丝,采用的就是推模式,会直接获取信息,延时低;而对于那种普通不活跃或者僵尸粉,则采用的是拉模式,用户需要从发件箱中获取,延时会高一点;

Redis实战案例24-关注推送_第5张图片
一般达不到千万级别以上都是采用推模式,所以案例采用推模式,难度简单;

2. 基于推模式实现关注推送功能

采用SortedSet数据结构
选择SortedSet原因:

  1. 对于变化的排名进行分页时采用SortedSet;
  2. 数据动态变化;
  3. 排行榜;

Redis实战案例24-关注推送_第6张图片

传统分页模式缺点:读取到重复数据

Redis实战案例24-关注推送_第7张图片

滚动分页实现:记录每一次查询的最后一条

Redis实战案例24-关注推送_第8张图片

/**
 * 保存博客,并且推送到粉丝的收件箱中
 * @param blog
 * @return
 */
@Override
public Result saveBlog(Blog blog) {
    // 1.获取当前用户
    UserDTO user = UserHolder.getUser();
    blog.setUserId(user.getId());
    // 2.保存探店笔记
    boolean isSuccess = save(blog);
    if(!isSuccess){
        return Result.fail("保存失败");
    }
    // 3.推送笔记给粉丝,所以首先需要获取当前用户的粉丝列表
    // user_id:用户id  follow_user_id:被关注的博主的id
    List<Follow> followUserIds = followService.query().eq("follow_user_id", user.getId()).list();
    // 4.遍历整个粉丝列表,推送给粉丝
    for (Follow follow:followUserIds) {
        // 4.1 获取粉丝id
        Long userId = follow.getUserId();
        // 4.2 推送笔记id给所有粉丝,key值为用户自身的id
        String key = "feed:" + userId;
        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
    }
    //5. 返回id
    return Result.ok(blog.getId());
}

3. 实现关注推送页面的分页查询

重点是如何解决滚动分页查询收件箱

Redis实战案例24-关注推送_第9张图片

如果按角标(value)查,角标变化会出现重复查询;

Redis实战案例24-关注推送_第10张图片

按照score值查,记录查询最小score;

在这里插入图片描述
测试时max取1000,实际业务时取当前时间为最大值;
限制为查询条数,offset表示偏移量,意思是上一条查询中,和最小值一样的条数,避免重复(一般取值为1,因为也不需要查询上一条查询中最小值);
count表示查询的条数;

在这里插入图片描述

Redis实战案例24-关注推送_第11张图片

偏移量继续设为0则会重复查询;

Redis实战案例24-关注推送_第12张图片

Redis实战案例24-关注推送_第13张图片
代码实现

第一次查询给默认值,时间戳为当前时间,偏移量为0
之后的值有后续代码给出;

返回值类:

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

实现类,见注释

/**
 * 滚动查询,展示博主推送的笔记,新发布的滚动查询查不到,但是往上滚,前端做了处理,就是刷新重新查询,开始位置在当前最新位置
 * @param max
 * @param offset
 * @return
 */
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
    //1.找到用户
    UserDTO user = UserHolder.getUser();
    //2.查询用户收件箱
    String key = FEED_KEY + user.getId();
    Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
            .rangeByScoreWithScores(key, 0, max, offset, 2);
    //判空操作
    if(typedTuples == null || typedTuples.isEmpty()) {
        return Result.ok();
    }
    //3.解析收件箱数据:blogId、minTime(时间戳)、offset(偏移量)
    ArrayList<Object> ids = new ArrayList<>(typedTuples.size());
    long minTime = 0;  //这个minTime是上次查询的最小时间戳,作为当次查询的最大时间戳来开始查
    int os = 1; //计算分数值等于最小值的个数
    for (ZSetOperations.TypedTuple<String> typedTuple:typedTuples) {
        //获取博客id转换为Long型并存入ids数组中
        //获取id
        String id = typedTuple.getValue();
        ids.add(Long.valueOf(id));
        //获取分数(时间戳)
        long time = typedTuple.getScore().longValue();
        if(time == minTime){
            os += 1;
        }else {
            minTime = time;
            os = 1;
        }
    }
    //4.根据id查询blog,采用mp的批量查询
    //但是由于用mp提供的listByIds是用in方法查,不能保证顺序
    //需要加orderby语句,需要id字符串
    //使用last方法添加查询条件,按照给定的ID顺序进行排序。idStr是一个字符串,用于指定ID的顺序。
    String idStr = StrUtil.join(",", ids);
    List<Blog> blogList = query().in("id", ids).last("order by field(id," + idStr + ")").list();
    //其中每个blog都要做查询该blog博主信息和点赞信息的操作
	for (Blog blog:blogList) {
    	//查询blog有关的用户
    	queryBlogUser(blog);
    	//查询blog是否被点赞
    	isBlogLiked(blog);
	}
    //5.封装并返回
    ScrollResult r = new ScrollResult();
    r.setList(blogList);
    r.setOffset(os);
    r.setMinTime(minTime);
    return Result.ok(r);
}

你可能感兴趣的:(Redis,redis,数据库,缓存)