在社交应用中,Feed流(信息流)是连接用户与内容的核心桥梁。如何高效分发和读取海量动态,直接决定用户体验。本文聚焦Redis在Feed流场景下的经典实现,深入剖析推拉模式的取舍,并详解基于有序集合的滚动分页技术,助你构建高性能关注推送系统。
Feed流业务场景与模式选择
Feed流通常分为两种模式:Timeline(时间线)和智能排序(算法推荐)。
- Timeline模式:严格按发布时间排序,如朋友圈、关注页。优点是数据完整、逻辑简单;缺点是信息噪音大,阅读效率随关注量下降。
- 智能排序:通过算法(如协同过滤)过滤低质内容,如抖音推荐页。优点是用户粘性高;缺点是实现复杂,依赖大量数据训练。
本文场景聚焦个人页面的关注动态流,需保证内容完整性,因此采用Timeline模式。
Timeline模式的核心挑战
Timeline模式的核心在于数据分发与读取的效率平衡。假设用户A关注了B,当B发布动态时,系统需快速让A看到。这涉及两个操作:
- 写扩散:发布时主动推送给粉丝。
- 读扩散:读取时实时聚合关注列表内容。
不同实现方案的本质,就是权衡写操作与读操作的复杂度、存储成本及一致性。
三种实现方案深度对比
拉模式(读扩散)
每个用户维护自己的发件箱(Outbox),发布内容写入自身发件箱。粉丝读取时,实时拉取所有关注用户的发件箱,合并排序后返回。

工作流程:发布时写入发件箱(Redis ZSet,score为时间戳);读取时并行拉取关注列表的发件箱,内存归并排序。
优点:存储成本低,每条内容仅一份;写操作轻量,复杂度O(1)。
缺点:读操作重,关注越多延迟越高;大V发件箱可能成为热点。
适用场景:用户关注数有限(如朋友圈)、读请求量不大。
技术要点:使用ZSet存储,多路归并排序优化,可配合本地缓存。
推模式(写扩散)
每个用户维护收件箱(Inbox)。发布时,系统将内容推送给所有粉丝,写入粉丝收件箱。读取时直接拉取,无需合并。

工作流程:发布时查询粉丝列表,批量写入粉丝收件箱(Redis List/ZSet);读取时直接从收件箱按时间倒序获取。
优点:读操作极轻,O(1)读取;实时性好,内容预聚合。
缺点:写放大严重,大V发布需写入百万级收件箱;存储成本高,内容冗余;删除/更新复杂。
适用场景:粉丝数可控、读多写少的场景(如企业内部系统)。
技术要点:异步写扩散(消息队列),收件箱限制容量(如只存最近1000条),大V特殊处理。
推拉结合模式(混合读写)
结合推拉优势,将用户分为普通用户(推模式)和大V用户(拉模式)。普通用户发布时推送所有粉丝;大V只推送给活跃粉丝,不活跃粉丝读取时采用拉模式。
工作流程:发布时判断用户类型;读取时先取收件箱,再拉取大V发件箱,合并排序。
优点:平衡读写压力,节省存储,提升读性能。
缺点:实现复杂,需维护用户类型和活跃状态,处理合并去重。
适用场景:大型社交平台(如微博、Twitter)。
技术要点:活跃度判定(最近登录时间),收件箱容量限制,并发拉取优化,推拉阈值动态调整(如粉丝数超10万采用拉模式)。
方案对比总结
| 维度 | 拉模式 | 推模式 | 推拉结合 |
| 写复杂度 | O(1) | O(粉丝数) | O(活跃粉丝数) |
| 读复杂度 | O(关注数) | O(1) | O(1 + 大V数) |
| 存储成本 | 低 | 高 | 中 |
| 实时性 | 依赖合并性能 | 高 | 高 |
| 适用场景 | 好友少、读少 | 粉丝少、读多 | 大V存在、读写均衡 |
| 实现难度 | 易 | 中 | 高 |
选择哪种模式,需结合业务规模、读写比例和存储成本。通常,中小型应用采用推模式即可,大型平台需混合模式。
案例实战:基于推模式的Redis实现
在博主发布博客后,将博客推送给粉丝。粉丝读取收件箱时需分页。以下为核心代码实现(推模式):
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
Long userId = UserHolder.getUser().getId();
//查询收件箱 ZREVRANGEBYSCORE key max min LIMIT offset count
String key = RedisConstants.FEED_KEY + userId;
Set> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 2);
if (typedTuples==null||typedTuples.isEmpty()){
return Result.ok();
}
List ids = new ArrayList<>(typedTuples.size());
long minTime = 0;
int os=1;
//解析数据:blogId,minTime(时间戳),offset
for (ZSetOperations.TypedTuple typedTuple : typedTuples){
ids.add(Long.valueOf(typedTuple.getValue()));
//获取分数(时间戳)
long Time = typedTuple.getScore().longValue();
if (Time == minTime){
os++;
}else {
minTime = Time;
os = 1;
}
}
String idstr= StrUtil.join(",", ids);
List blogs = query().in("id", ids)
.last("ORDER BY FIELD(id,"+idstr+")").list();
for (Blog blog : blogs){
isBlogLiked(blog);
}
ScrollResult scrollResult = new ScrollResult();
scrollResult.setList(blogs);
scrollResult.setOffset(os);
scrollResult.setMinTime(minTime);
return Result.ok(scrollResult);
} Redis滚动分页查询详解
初识有序集合与ZREVRANGEBYSCORE
Redis有序集合(Sorted Set)为每个元素关联一个分数(score),按分数从小到大排列。常用于排行榜、时间轴等。ZREVRANGEBYSCORE命令用于按分数从大到小返回元素,语法如下:
参数说明:
key:键名max和min:分数范围(先max后min)WITHSCORES:返回分数LIMIT offset count:分页参数,跳过offset个,取count个
示例:获取分数100-200之间最高分前3个元素:
ZREVRANGEBYSCORE myzset 200 100 LIMIT 0 3滚动分页的需求与挑战
传统分页(LIMIT+OFFSET)存在性能问题(offset大时效率低)和数据不一致(集合变化导致重复/遗漏)。滚动分页(Cursor-based)基于上一页最后一条记录的分数作为下一页起点,避免重复和遗漏,且查询效率稳定。
参数设计解析
为什么用时间戳作为分数?最新内容分数最高,第一页查询max设为当前时间戳,min设为0;后续页以上一页最小分数作为max,保证只取更早记录。
处理分数重复:offset的妙用若多个元素同分(如同一秒发布),仅用分数作游标会导致重复或遗漏。解决方案:记录上一页中与最小分数相同的元素个数,作为下一页的offset,跳过这些已取元素。
参数变化举例:假设集合数据如下:
| member | score(时间戳) | |
| A | 1700000003 | |
| B | 1700000002 | |
| C | 1700000002 | ← 分数重复 |
| D | 1700000001 | |
| E | 1700000000 |
第一页(每页2条):max=1700000003, min=0, offset=0, count=2,结果:A(1700000003)、B(1700000002)。此时最小分数1700000002出现1次。
第二页:max=1700000002, min=0, offset=1, count=2,跳过B,取C(1700000002)和D(1700000001)。
第三页:max=1700000001, offset=1,结果E(1700000000)。
命令执行细节
分数范围与LIMIT协作:ZREVRANGEBYSCORE先按分数筛选出有序子集,再应用LIMIT截断,全程服务端完成。
为何min固定为0?时间戳为正整数,0涵盖所有历史数据。
结束条件:查询结果不足count条,说明已到末尾;结果为空则停止。
实际应用注意事项
- ⚠️ 命令版本:ZREVRANGEBYSCORE在Redis 6.2.0后废弃,推荐使用
ZRANGEBYSCORE并加REV参数,如: - ⚠️ 并发修改:滚动过程中集合变化可能导致offset失效,但静态或只追加数据集无碍。
- ✅ 性能稳定:每次查询时间复杂度O(log(N)+M),不随页数增加而变慢。
- ✅ WITHSCORES:实现滚动分页时建议返回分数,以便记录下一页的max和offset。
总结
Feed流架构的核心是读写平衡。拉模式节省存储但读延迟高,推模式读取快但写放大,混合模式折中但复杂。实际应用中,推模式+异步写扩散是多数场景的优选。滚动分页通过时间戳游标和offset处理同分问题,确保分页稳定高效。掌握这些技术,你就能构建一个高性能的关注推送系统。
[AFFILIATE_SLOT_1]
[AFFILIATE_SLOT_2]
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]ZRANGEBYSCORE key min max REV LIMIT offset count
浙公网安备 33010602011771号