关注推送关注推送也叫做Feed流直译为投喂。为用户持续的提供“沉浸式”的体验通过无限下拉刷新获取新的信息。Feed流的模式Feed流产品有两种常见模式Timeline不做内容筛选简单的按照内容发布时间排序常用于好友或关注。例如朋友圈。优点信息全面不会有缺失。并且实现也相对简单。缺点信息噪音较多用户不一定感兴趣内容获取效率低。智能排序利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户。优点投喂用户感兴趣信息用户粘度很高容易沉迷。缺点如果算法不精准可能起到反作用。本例中的个人页面是基于关注的好友来做Feed流因此采用Timeline的模式。该模式的实现方案有三种1、拉模式2、推模式3、推拉结合Feed流的实现方案1拉模式也叫做读扩散。优点比较节省内存空间。一般收件人的收件箱是空的消息只在发件人的发件箱里保存一份只有在收件人读取时才拉取发件人发件箱的信息拷贝一份到收件人的收件箱并按照时间做排序读完之后不用了就会清理掉。缺点读取延迟较高。收件人每次来读消息时都得重新拉取发件箱的消息然后再做排序这些操作一般耗时较久所以延迟较高。Feed流的实现方案2推模式也叫做写扩散。优点读取延迟较低。发件人自身没有发件箱当发件人有新消息时直接将信息推送至收件人的收件箱进行存储并做好排序收件人在读取信息时不需要再做拉取信息的动作直接读取收件箱即可。缺点内存空间占用较多。由于发件人没有发件箱所以当发件人有新消息时需要将消息在每个收件人的收件箱中都保存一份因此内存占用较高。Feed流的实现方案3推拉结合模式也叫做读写混合兼具推和拉两种模式的优点。模式说明将发件人分为大V粉丝较多和普通人粉丝较少将收件人分为活跃粉丝读取消息频繁和普通粉丝读取消息频率不高。对于大V由于粉丝众多若直接给所有粉丝的收件箱推送消息内存消耗过大且由于大多粉丝读取消息的频率不高只有少部分活跃粉丝所以可以采取“高活跃度粉丝直接推送低活跃度粉丝在读取时主动拉取”的策略在发送消息时将消息在发件箱里保存一份同时推送给活跃粉丝对于普通人由于粉丝较少直接将消息推送至所有粉丝的收件箱即可。Feed流的三种实现方案对比基于推模式实现关注推送功能需求1、修改新增探店笔记的业务在保存blog到数据库的同时推送到粉丝的收件箱。2、收件箱满足可以根据时间戳排序必须用Redis的数据结构实现。思考在Redis中List和Set都可以实现排序和分页那么当前场景应该选择谁呢先自己思考后面会有分析3、查询收件箱数据时可以实现分页查询。Feed流的分页问题Feed流中的数据会不断更新所以数据的角标也在变化因此不能采用传统的分页模式。Feed流的滚动分页总结根据以上分析因为不断会有新的数据插入角标是不断变化的传统的分页模式会导致数据重复查询问题。但由于SortedSet可以通过ZRANGEBYSCORE命令来获取指定score范围内的元素所以SortedSet胜出最终选择使用SortedSet。实现关注推送页面的分页查询滚动分页查询需求在个人主页的“关注”卡片中查询并展示推送的Blog信息滚动分页查询功能分析在Redis命令行窗口可以通过以下命令实现滚动分页查询ZREVRANGEBYSCORE key Max Min WITHSCORES LIMIT offset count滚动分页查询参数max最大分数当前时间戳第一次查询 或 上一次查询的最小时间戳除了第一次以外的查询。min最小分数0固定不变。offset偏移量0第一次查询 或 在上一次的结果中与最小值一样的元素的个数除了第一次以外的查询。count分页数据个数3 。关键代码片段// 查询收件箱 ZREVRANGEBYSCORE key Max Min WITHSCORES LIMIT offset countStringkeyFEED_KEYuserId;SetZSetOperations.TypedTupleStringtypedTuplesstringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,0,max,offset,2);// 解析数据: blogId、minTime(时间戳)、offsetListLongidsnewArrayList(typedTuples.size());longminTime0;// 2intos1;// 2for(ZSetOperations.TypedTupleStringtuple:typedTuples){// 5 4 4 2 2// 4.1.获取idids.add(Long.valueOf(tuple.getValue()));// 4.2.获取分数(时间戳)longtimetuple.getScore().longValue();if(timeminTime){os;}else{minTimetime;os1;}}// 根据id查询blogStringidStrStrUtil.join(conjunction:,,ids);ListBlogblogsquery().in(column:id,ids).last(lastSql:ORDER BY FIELD(id,idStr)).list();// 这部分容易遗漏for(Blogblog:blogs){// 5.1.查询blog有关的用户queryBlogUser(blog);// 5.2.查询blog是否被点赞isBlogLiked(blog);}
【Redis实战篇】Feed流(关注推送)与滚动分页查询
发布时间:2026/5/31 17:52:11
关注推送关注推送也叫做Feed流直译为投喂。为用户持续的提供“沉浸式”的体验通过无限下拉刷新获取新的信息。Feed流的模式Feed流产品有两种常见模式Timeline不做内容筛选简单的按照内容发布时间排序常用于好友或关注。例如朋友圈。优点信息全面不会有缺失。并且实现也相对简单。缺点信息噪音较多用户不一定感兴趣内容获取效率低。智能排序利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户。优点投喂用户感兴趣信息用户粘度很高容易沉迷。缺点如果算法不精准可能起到反作用。本例中的个人页面是基于关注的好友来做Feed流因此采用Timeline的模式。该模式的实现方案有三种1、拉模式2、推模式3、推拉结合Feed流的实现方案1拉模式也叫做读扩散。优点比较节省内存空间。一般收件人的收件箱是空的消息只在发件人的发件箱里保存一份只有在收件人读取时才拉取发件人发件箱的信息拷贝一份到收件人的收件箱并按照时间做排序读完之后不用了就会清理掉。缺点读取延迟较高。收件人每次来读消息时都得重新拉取发件箱的消息然后再做排序这些操作一般耗时较久所以延迟较高。Feed流的实现方案2推模式也叫做写扩散。优点读取延迟较低。发件人自身没有发件箱当发件人有新消息时直接将信息推送至收件人的收件箱进行存储并做好排序收件人在读取信息时不需要再做拉取信息的动作直接读取收件箱即可。缺点内存空间占用较多。由于发件人没有发件箱所以当发件人有新消息时需要将消息在每个收件人的收件箱中都保存一份因此内存占用较高。Feed流的实现方案3推拉结合模式也叫做读写混合兼具推和拉两种模式的优点。模式说明将发件人分为大V粉丝较多和普通人粉丝较少将收件人分为活跃粉丝读取消息频繁和普通粉丝读取消息频率不高。对于大V由于粉丝众多若直接给所有粉丝的收件箱推送消息内存消耗过大且由于大多粉丝读取消息的频率不高只有少部分活跃粉丝所以可以采取“高活跃度粉丝直接推送低活跃度粉丝在读取时主动拉取”的策略在发送消息时将消息在发件箱里保存一份同时推送给活跃粉丝对于普通人由于粉丝较少直接将消息推送至所有粉丝的收件箱即可。Feed流的三种实现方案对比基于推模式实现关注推送功能需求1、修改新增探店笔记的业务在保存blog到数据库的同时推送到粉丝的收件箱。2、收件箱满足可以根据时间戳排序必须用Redis的数据结构实现。思考在Redis中List和Set都可以实现排序和分页那么当前场景应该选择谁呢先自己思考后面会有分析3、查询收件箱数据时可以实现分页查询。Feed流的分页问题Feed流中的数据会不断更新所以数据的角标也在变化因此不能采用传统的分页模式。Feed流的滚动分页总结根据以上分析因为不断会有新的数据插入角标是不断变化的传统的分页模式会导致数据重复查询问题。但由于SortedSet可以通过ZRANGEBYSCORE命令来获取指定score范围内的元素所以SortedSet胜出最终选择使用SortedSet。实现关注推送页面的分页查询滚动分页查询需求在个人主页的“关注”卡片中查询并展示推送的Blog信息滚动分页查询功能分析在Redis命令行窗口可以通过以下命令实现滚动分页查询ZREVRANGEBYSCORE key Max Min WITHSCORES LIMIT offset count滚动分页查询参数max最大分数当前时间戳第一次查询 或 上一次查询的最小时间戳除了第一次以外的查询。min最小分数0固定不变。offset偏移量0第一次查询 或 在上一次的结果中与最小值一样的元素的个数除了第一次以外的查询。count分页数据个数3 。关键代码片段// 查询收件箱 ZREVRANGEBYSCORE key Max Min WITHSCORES LIMIT offset countStringkeyFEED_KEYuserId;SetZSetOperations.TypedTupleStringtypedTuplesstringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,0,max,offset,2);// 解析数据: blogId、minTime(时间戳)、offsetListLongidsnewArrayList(typedTuples.size());longminTime0;// 2intos1;// 2for(ZSetOperations.TypedTupleStringtuple:typedTuples){// 5 4 4 2 2// 4.1.获取idids.add(Long.valueOf(tuple.getValue()));// 4.2.获取分数(时间戳)longtimetuple.getScore().longValue();if(timeminTime){os;}else{minTimetime;os1;}}// 根据id查询blogStringidStrStrUtil.join(conjunction:,,ids);ListBlogblogsquery().in(column:id,ids).last(lastSql:ORDER BY FIELD(id,idStr)).list();// 这部分容易遗漏for(Blogblog:blogs){// 5.1.查询blog有关的用户queryBlogUser(blog);// 5.2.查询blog是否被点赞isBlogLiked(blog);}