当前位置: 首页 > news >正文

redis_点评(24.好友关注—实现关注推送页面的「滚动分页查询」)

这是基于Redis ZSet 实现的「关注流滚动分页」完整代码,解决了传统分页在关注流场景下的性能与一致性问题,实现了类似朋友圈的“下拉加载更多”效果。


一、整体架构概览

层级

核心内容

作用

Controller

新增/of/follow接口,接收滚动分页参数

接收前端的lastId(最后一条笔记时间戳)和offset(偏移量)

DTO

新增ScrollResult,封装分页结果

返回给前端的结构:list(笔记列表)、minTime(本次最小时间戳)、offset(下次偏移量)

Service

实现queryBlogOfFollow方法

从Redis收件箱拉取笔记ID,再批量查询笔记详情并返回


二、Controller层接口

@GetMapping("/of/follow") public Result queryBlogOfFollow( @RequestParam(value = "lastId", required = false) Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset ) { return blogService.queryBlogOfFollow(max, offset); }
  • lastId:上一次查询的最小时间戳(用于查询更早的笔记),第一次请求为null

  • offset:偏移量,解决「同一时间戳多条笔记」的重复问题,默认0


三、ScrollResult 数据结构

@Data public class ScrollResult { private List<?> list; // 本次查询的笔记列表 private Long minTime; // 本次查询的最小时间戳(下次请求的lastId) private Integer offset; // 下次请求的偏移量 }
  • 作用:为前端提供滚动分页的上下文,让前端能正确发起下一次请求。


四、Service层核心逻辑queryBlogOfFollow

步骤1:获取当前用户并处理空请求

UserDTO user = UserHolder.getUser(); if (user == null) { return Result.ok(); } Long userId = user.getId(); String key = FEED_KEY + userId; // 拼接用户的收件箱Key(feed:用户ID) if (max == null) max = System.currentTimeMillis(); // 首次请求默认当前时间戳
  • 空用户直接返回,避免空指针;首次请求默认以当前时间戳作为起点。

步骤2:从Redis收件箱拉取笔记ID(滚动分页核心)

Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet() .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
一、这行代码总结

从 Redis 的 ZSet(有序集合)中,按分数倒序查询一批数据,并且同时拿到【值 + 分数】。

在你的业务里 =从粉丝收件箱里,按时间倒序,分页查笔记ID + 发布时间戳


二、逐部分超精炼解释
1.stringRedisTemplate.opsForZSet()
  • 获取 RedisZSet有序集合操作对象

  • 专门用来操作带排序功能的 Redis 集合

2.reverseRangeByScoreWithScores(...)

核心方法:按分数倒序查询 + 同时返回值和分数

  • reverse:倒序(从大到小,最新时间在前)

  • RangeByScore:按分数范围查询

  • WithScores同时返回 value(笔记ID)和 score(时间戳)

3. 六个参数含义(必背)
reverseRangeByScoreWithScores( key, // 1. Redis的key(feed:用户ID = 收件箱) 0, // 2. 最小分数(最小时间戳) max, // 3. 最大分数(上一页最后一条的时间戳) offset, // 4. 偏移量(跳过多少条) 2 // 5. 取多少条(每页条数) )

三、返回值是什么?
Set<ZSetOperations.TypedTuple<String>> tuples
  • TypedTuple一条数据 = 包含 value + score

    • value= 笔记ID

    • score= 发布时间戳

  • Set:查询到的多条数据


四、业务含义(最关键)
从当前用户的 Redis 收件箱里: 查 0 ~ max 时间范围内的笔记 倒序排列(最新的在前) 跳过 offset 条 取 2 条 同时拿到 笔记ID + 发布时间

  • 关键方法:reverseRangeByScoreWithScores

    • 从ZSet中按score(时间戳)倒序查询,范围[0, max]

    • 跳过offset条,取2条(分页大小)

    • 返回TypedTuple集合,同时拿到笔记ID和对应的时间戳

步骤3:解析笔记ID并计算下次请求参数

List<Long> ids = new ArrayList<>(tuples.size()); long minTime = 0; int os = 0; for (ZSetOperations.TypedTuple<String> tuple : tuples) { String blogId = tuple.getValue(); ids.add(Long.valueOf(blogId)); long score = tuple.getScore().longValue(); if (score == minTime) { os++; // 同一时间戳的笔记数量+1,作为下次偏移量 } else { minTime = score; os = 1; } }
  • 解析笔记ID,同时记录本次查询的最小时间戳minTime和偏移量os

  • 解决同一时间戳多条笔记的重复问题:下次请求时,从minTime开始,偏移os条。

步骤4:批量查询笔记详情并按原顺序返回

String idStr = StrUtil.join(",", ids); List<Blog> blogs = query() .in("id", ids) .last("ORDER BY FIELD(id," + idStr + ")") // 按Redis返回的顺序排序 .list();
  • ORDER BY FIELD强制按Redis返回的顺序查询,避免数据库打乱排序。

步骤5:封装结果并返回

ScrollResult result = new ScrollResult(); result.setList(blogs); result.setMinTime(minTime); result.setOffset(os); return Result.ok(result);
  • 将笔记列表、下次请求的minTimeoffset封装返回给前端。


五、关键技术点解析

1. 滚动分页 vs 传统分页

维度

传统分页(PageHelper)

滚动分页(本次实现)

排序依赖

依赖数据库自增ID或固定字段

依赖Redis ZSet的时间戳

重复/遗漏

新增/删除数据时会出现重复或漏数据

基于时间戳+偏移量,无重复/遗漏

性能

大数据量下OFFSET性能差

Redis ZSet操作高效,无需数据库全表扫描

适用场景

列表固定、无新增的场景

关注流、feed流等实时新增数据场景

2. Redis ZSet 作为收件箱的优势

  • 天然有序:按时间戳自动排序,拉取时直接按score倒序即可

  • 高效分页:reverseRangeByScoreWithScores支持按score范围分页,性能O(log(N))

  • 主动推送:笔记发布时直接写入粉丝的收件箱,拉取时无需关联查询

3. 偏移量(offset)的作用

  • 解决同一时间戳多条笔记的问题:比如同一秒发布了3条笔记,下次请求时,从该时间戳开始,偏移3条,避免重复拉取。


六、其他细节说明

  1. FEED_KEY常量:替代硬编码的"feed:",统一管理Redis Key前缀。

  2. afterCompletion注释:移除UserHolder.removeUser(),避免请求结束时清除用户信息,保证后续异步操作也能获取用户信息。

  3. 空值处理:对maxtuples做了判空处理,避免空指针异常。


七、总结

该功能利用Redis ZSet实现了关注流的滚动分页查询,用户下拉时会从自己的“收件箱”中按时间戳倒序拉取关注博主的笔记,同时通过时间戳+偏移量解决了传统分页的重复/遗漏问题,是社交类APP关注流的标准实现方案。

http://www.cnnetsun.cn/news/2628259.html

相关文章:

  • 智能戒指技术解析:医疗监测与人机交互的硬件与算法
  • 单片机串口通信异常问题分析与解决方案
  • 别再只看Top-1了!用Python实战解析Rank-1与Rank-5正确率,帮你更懂模型真实能力
  • 嵌入式文件系统断电损坏问题与解决方案
  • 别再为Qt程序中文输入发愁了!一份通用的 fcitx5-qt 插件编译指南(覆盖Qt5/Qt6)
  • 从时序图到实战:拆解ZYNQ VDMA的Line Buffer,搞定视频流拼接与缩放
  • 如何快速清理重复图片:开源智能去重工具的终极指南
  • Go语言并发编程模式与实战技巧
  • OpenCV项目实战:给你的C++图像处理程序加上自定义字体和中文水印
  • Windows鼠标指针美化终极指南:免费获取macOS风格指针包
  • 终极指南:三步轻松解密网易云音乐NCM格式,实现音频自由播放
  • VMware给Kali扩容后开机卡黑屏?别慌,可能是swap的UUID在捣鬼(附详细排查步骤)
  • 5分钟搭建工控 HMI:WinForm 状态/报警/趋势控件库及模板
  • 2026顶级黑客练成计划,学会就入狱,手把手带你从零入门白帽黑客网络安全行业,学不会我退出网安圈
  • 家具厂能源监测可视化管理平台解决方案
  • 别再乱删文件了!手把手教你用chattr给Linux文件上锁(附防误删实战)
  • Win10蓝屏后无限重启?可能是硬盘在‘求救’!一个案例教你识别硬件故障征兆
  • 如何快速从图表图片中提取数据:WebPlotDigitizer的完整解决方案指南
  • 手把手教你搞定神州龙芯GSC3290与裕太YT8521S的千兆网卡适配(附完整寄存器配置代码)
  • 告别命令行:在银河麒麟桌面版上,用图形化工具快速配置vsftpd文件共享
  • 044、手持视频抖动严重?OpenCV 光流 + IMU 融合的电子防抖工程方案
  • 【数据分析】分数阶混沌系统的混沌附matlab代码
  • 【OFDM通信】室内NOMA-OFDM-VLC系统Matlab仿真
  • LeetCode 121 · 买卖股票的最佳时机:一次遍历,记住最低价就够了
  • 扎克伯格夫妇旗下Biohub发布蛋白质“世界模型“
  • Dotween动画控制避坑指南:从播放、暂停到倒放,这些细节新手容易忽略
  • 告别RST折腾:在开启Intel快速存储的电脑上,无损安装Ubuntu 22.04的另一种思路
  • 2026年,专业商用面条机公司有何独特之处,带你一探究竟!
  • GP2Y0D80Z0F红外接近传感器与Arduino实战:从原理到应用
  • ClaudeCode深度使用一年,这5个技能让我效率直接翻倍