这是基于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上一次查询的最小时间戳用于查询更早的笔记第一次请求为nulloffset偏移量解决「同一时间戳多条笔记」的重复问题默认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; // 拼接用户的收件箱Keyfeed:用户ID if (max null) max System.currentTimeMillis(); // 首次请求默认当前时间戳空用户直接返回避免空指针首次请求默认以当前时间戳作为起点。步骤2从Redis收件箱拉取笔记ID滚动分页核心SetZSetOperations.TypedTupleString 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的keyfeed:用户ID 收件箱 0, // 2. 最小分数最小时间戳 max, // 3. 最大分数上一页最后一条的时间戳 offset, // 4. 偏移量跳过多少条 2 // 5. 取多少条每页条数 )三、返回值是什么SetZSetOperations.TypedTupleString tuplesTypedTuple一条数据 包含 value scorevalue 笔记IDscore 发布时间戳Set查询到的多条数据四、业务含义最关键从当前用户的 Redis 收件箱里 查 0 ~ max 时间范围内的笔记 倒序排列最新的在前 跳过 offset 条 取 2 条 同时拿到 笔记ID 发布时间关键方法reverseRangeByScoreWithScores从ZSet中按score时间戳倒序查询范围[0, max]跳过offset条取2条分页大小返回TypedTuple集合同时拿到笔记ID和对应的时间戳步骤3解析笔记ID并计算下次请求参数ListLong ids new ArrayList(tuples.size()); long minTime 0; int os 0; for (ZSetOperations.TypedTupleString 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); ListBlog 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);将笔记列表、下次请求的minTime和offset封装返回给前端。五、关键技术点解析1. 滚动分页 vs 传统分页维度传统分页PageHelper滚动分页本次实现排序依赖依赖数据库自增ID或固定字段依赖Redis ZSet的时间戳重复/遗漏新增/删除数据时会出现重复或漏数据基于时间戳偏移量无重复/遗漏性能大数据量下OFFSET性能差Redis ZSet操作高效无需数据库全表扫描适用场景列表固定、无新增的场景关注流、feed流等实时新增数据场景2. Redis ZSet 作为收件箱的优势天然有序按时间戳自动排序拉取时直接按score倒序即可高效分页reverseRangeByScoreWithScores支持按score范围分页性能O(log(N))主动推送笔记发布时直接写入粉丝的收件箱拉取时无需关联查询3. 偏移量offset的作用解决同一时间戳多条笔记的问题比如同一秒发布了3条笔记下次请求时从该时间戳开始偏移3条避免重复拉取。六、其他细节说明FEED_KEY常量替代硬编码的feed:统一管理Redis Key前缀。afterCompletion注释移除UserHolder.removeUser()避免请求结束时清除用户信息保证后续异步操作也能获取用户信息。空值处理对max、tuples做了判空处理避免空指针异常。七、总结该功能利用Redis ZSet实现了关注流的滚动分页查询用户下拉时会从自己的“收件箱”中按时间戳倒序拉取关注博主的笔记同时通过时间戳偏移量解决了传统分页的重复/遗漏问题是社交类APP关注流的标准实现方案。
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上一次查询的最小时间戳用于查询更早的笔记第一次请求为nulloffset偏移量解决「同一时间戳多条笔记」的重复问题默认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; // 拼接用户的收件箱Keyfeed:用户ID if (max null) max System.currentTimeMillis(); // 首次请求默认当前时间戳空用户直接返回避免空指针首次请求默认以当前时间戳作为起点。步骤2从Redis收件箱拉取笔记ID滚动分页核心SetZSetOperations.TypedTupleString 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的keyfeed:用户ID 收件箱 0, // 2. 最小分数最小时间戳 max, // 3. 最大分数上一页最后一条的时间戳 offset, // 4. 偏移量跳过多少条 2 // 5. 取多少条每页条数 )三、返回值是什么SetZSetOperations.TypedTupleString tuplesTypedTuple一条数据 包含 value scorevalue 笔记IDscore 发布时间戳Set查询到的多条数据四、业务含义最关键从当前用户的 Redis 收件箱里 查 0 ~ max 时间范围内的笔记 倒序排列最新的在前 跳过 offset 条 取 2 条 同时拿到 笔记ID 发布时间关键方法reverseRangeByScoreWithScores从ZSet中按score时间戳倒序查询范围[0, max]跳过offset条取2条分页大小返回TypedTuple集合同时拿到笔记ID和对应的时间戳步骤3解析笔记ID并计算下次请求参数ListLong ids new ArrayList(tuples.size()); long minTime 0; int os 0; for (ZSetOperations.TypedTupleString 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); ListBlog 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);将笔记列表、下次请求的minTime和offset封装返回给前端。五、关键技术点解析1. 滚动分页 vs 传统分页维度传统分页PageHelper滚动分页本次实现排序依赖依赖数据库自增ID或固定字段依赖Redis ZSet的时间戳重复/遗漏新增/删除数据时会出现重复或漏数据基于时间戳偏移量无重复/遗漏性能大数据量下OFFSET性能差Redis ZSet操作高效无需数据库全表扫描适用场景列表固定、无新增的场景关注流、feed流等实时新增数据场景2. Redis ZSet 作为收件箱的优势天然有序按时间戳自动排序拉取时直接按score倒序即可高效分页reverseRangeByScoreWithScores支持按score范围分页性能O(log(N))主动推送笔记发布时直接写入粉丝的收件箱拉取时无需关联查询3. 偏移量offset的作用解决同一时间戳多条笔记的问题比如同一秒发布了3条笔记下次请求时从该时间戳开始偏移3条避免重复拉取。六、其他细节说明FEED_KEY常量替代硬编码的feed:统一管理Redis Key前缀。afterCompletion注释移除UserHolder.removeUser()避免请求结束时清除用户信息保证后续异步操作也能获取用户信息。空值处理对max、tuples做了判空处理避免空指针异常。七、总结该功能利用Redis ZSet实现了关注流的滚动分页查询用户下拉时会从自己的“收件箱”中按时间戳倒序拉取关注博主的笔记同时通过时间戳偏移量解决了传统分页的重复/遗漏问题是社交类APP关注流的标准实现方案。