【遇见青山】基于Redis的Feed流实现案例
- 1.关注推送
- 2.具体代码实现
1.关注推送
关注推送也叫做Feed流,直译为投喂。为用户持续的提供"沉浸式”的体验,通过无限下拉刷新获取新的信息。
Feed流产品有两种常见模式:
这里我们实现基本的TimeLine
Feed流模式:
的TimeLine
Feed流模式有三种基本的实现方案:
拉模式:也叫做读扩散🦿
推模式:也叫做写扩散👣
推拉结合模式:也叫做读写混合,兼具推和拉两种模式的优点🤺
Feed流的实现方案分析:
点评类网站或App,宜使用推模式,短视频类网站或App,宜使用推拉结合模式⛷️
2.具体代码实现
需求分析:
- 修改新增探店笔记的业务,在保存blog到数据库的同时,推送到粉丝的收件箱
- 收件箱满足可以根据时间戳排序,必须用Redis的数据结构实现
- 查询收件箱数据时,可以实现分页查询
分析:实现滚动分页的方式
Feed流中的数据会不断更新,所以数据的角标也在变化,因此不能采用传统的分页模式,而要采用滚动分页,Redis数据结构中终于List和SortedSet支持分页,但List不支持滚动分页功能,所以Redis数据结构我们宜采用SortedSet!
首先,在保存博客的时候要推送给所有的粉丝(收件箱):
/**
* 保存博客
*
* @param blog 博客对象
* @return 博客的ID
*/
@Override
public Result saveBlog(Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
boolean isSave = save(blog);
if (!isSave) {
return Result.fail("新增笔记失败!");
}
// 查询笔记作者的所有粉丝
List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
for (Follow follow : follows) {
Long userId = follow.getUserId();
// 开始推送
stringRedisTemplate.opsForZSet().add(FEED_KEY + userId, blog.getId().toString(), System.currentTimeMillis());
}
// 返回id
return Result.ok(blog.getId());
}
实现具体的滚动分页查询:
/**
* 滚动分页查询Feed流推送的博客
*
* @param max 上一次查询的最小值,用于实现滚动查询
* @param offset 偏移量,防止查询到重复数据
* @return Result
*/
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// 获取当前用户
Long userId = UserHolder.getUser().getId();
// 查询当前用户的收件箱
Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet().
reverseRangeByScoreWithScores(FEED_KEY + userId, 0, max, offset, DEFAULT_PAGE_SIZE);
if (tuples == null || tuples.isEmpty()) {
return Result.ok();
}
// 创建集合,保存id
ArrayList<Long> ids = new ArrayList<>(tuples.size());
// 保存最小时间
long minTime = 0;
// 保存偏移量
int os = 1;
// 开始解析数据,得到最终的ids,minTime,offset(os)值
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
// 获取id并保存
ids.add(Long.valueOf(Objects.requireNonNull(tuple.getValue())));
// 获取分数(时间戳)
long time = Objects.requireNonNull(tuple.getScore()).longValue();
if (time == minTime) {
os++;
} else {
// 最后一个元组的时间一定是最小时间
minTime = time;
// 重置偏移量
os = 1;
}
}
String idStr = StrUtil.join(",", ids);
// 根据id查询blog
List<Blog> blogs = query().in("id", ids)
.last("ORDER BY FIELD(id," + idStr + ")").list();
// 给每个blog封装点赞,作者等信息
for (Blog blog : blogs) {
// 查询blog有关的用户
queryBlogUser(blog);
// 查询blog的点赞信息,当前用户是否点过赞?
isBlogLiked(blog);
}
// 返回博客集合给前端
ScrollResult scrollResult = new ScrollResult();
scrollResult.setList(blogs);
scrollResult.setMinTime(minTime);
scrollResult.setOffset(os);
return Result.ok(scrollResult);
}