信息流Feed系统架构设计
Feed流系统概述
Feed流是社交和内容平台的核心功能,用户看到的朋友动态、关注者更新、推荐内容等都属于Feed流的范畴。
Feed流的典型形态
mermaid
graph TD
A["Feed流类型"] --> B["时间线Timeline"]
A --> C["智能推荐"]
A --> D["混合模式"]
B --> B1["朋友圈动态"]
B --> B2["关注者更新"]
B --> B3["订阅频道"]
C --> C1["首页推荐"]
C --> C2["个性化内容"]
C --> C3["热门排行"]
D --> D1["推荐+关注结合"]
D --> D2["时间+热度权重"]
style A fill:#4A90E2,color:#fff,rx:10,ry:10
style B fill:#27AE60,color:#fff,rx:10,ry:10
style C fill:#E74C3C,color:#fff,rx:10,ry:10
style D fill:#9B59B6,color:#fff,rx:10,ry:10时间线模式:按时间顺序展示内容,实现简单,适用于好友社交场景,用户更关注"谁发的"而非"发了什么"。
智能推荐模式:基于用户兴趣和行为进行个性化推荐,需要依赖推荐算法,但存在"信息茧房"问题。
混合模式:结合时间线和推荐的优点,既保证时效性,又有一定的个性化,是目前主流的方案。
核心技术挑战
| 挑战维度 | 具体要求 | 技术难点 |
|---|---|---|
| 实时性 | 发布后秒级可见 | 数据同步延迟控制 |
| 高并发 | 几十万QPS | 读写压力分离 |
| 性能 | 100ms内响应 | 多源数据聚合 |
| 准确性 | 不丢不重 | 分布式一致性 |
三种推送模式对比
推模式(Push)
当用户发布内容时,主动将内容推送给所有粉丝的收件箱。
mermaid
sequenceDiagram
participant Author as 内容发布者
participant Service as Feed服务
participant Outbox as 发件箱
participant Inbox as 粉丝收件箱
Author->>Service: 发布新内容
Service->>Outbox: 写入发件箱
loop 遍历粉丝
Service->>Inbox: 写入粉丝1收件箱
Service->>Inbox: 写入粉丝2收件箱
Service->>Inbox: 写入粉丝N收件箱
end
Note over Inbox: 粉丝读取时直接查收件箱数据模型:
sql
-- 发件箱:记录用户发布的内容
CREATE TABLE outbox (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
content_id BIGINT NOT NULL,
create_time DATETIME NOT NULL,
INDEX idx_user_time(user_id, create_time DESC)
);
-- 收件箱:每个用户的feed流
CREATE TABLE inbox (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL COMMENT '收件箱所属用户',
content_id BIGINT NOT NULL,
author_id BIGINT NOT NULL,
create_time DATETIME NOT NULL,
INDEX idx_user_time(user_id, create_time DESC)
);优势:
- 读取性能高,直接查收件箱即可
- 实现逻辑简单
劣势:
- 写扩散严重,大V发一条内容需要写入百万粉丝的收件箱
- 存储成本高
拉模式(Pull)
用户读取Feed时,实时从关注的人那里拉取内容并聚合。
mermaid
sequenceDiagram
participant Reader as 内容读者
participant Service as Feed服务
participant Follow as 关注关系
participant Outbox as 发件箱
Reader->>Service: 请求Feed流
Service->>Follow: 获取关注列表
Follow-->>Service: 返回关注的用户ID
loop 遍历关注用户
Service->>Outbox: 查询用户1最新内容
Service->>Outbox: 查询用户2最新内容
Service->>Outbox: 查询用户N最新内容
end
Service->>Service: 聚合排序
Service-->>Reader: 返回Feed列表优势:
- 存储成本低,只存发件箱
- 发布时写入操作简单
劣势:
- 读取时需要聚合多个用户的内容,性能差
- 关注人数多时延迟高
推拉结合模式
针对不同用户采用不同策略,是实际生产环境的最佳选择:
mermaid
graph TD
A["内容发布"] --> B{"发布者类型判断"}
B -->|普通用户| C["推模式"]
B -->|大V用户| D["拉模式"]
C --> E["推送给所有粉丝"]
D --> F["仅写入发件箱"]
G["用户读取Feed"] --> H{"粉丝类型判断"}
H -->|活跃用户| I["直接读收件箱"]
H -->|不活跃用户| J["实时拉取聚合"]
style A fill:#4A90E2,color:#fff,rx:10,ry:10
style C fill:#27AE60,color:#fff,rx:10,ry:10
style D fill:#E67E22,color:#fff,rx:10,ry:10
style I fill:#27AE60,color:#fff,rx:10,ry:10
style J fill:#E67E22,color:#fff,rx:10,ry:10策略细节:
- 大V定义:粉丝数超过10万的用户
- 活跃用户定义:7天内有登录行为的用户
- 大V发内容时,仅推送给活跃粉丝
- 不活跃用户登录时,再补充拉取
存储架构设计
多级缓存方案
Feed流是典型的读多写少场景,适合采用多级缓存:
mermaid
graph TD
A["用户请求"] --> B{"本地缓存"}
B -->|命中| C["直接返回"]
B -->|未命中| D{"Redis缓存"}
D -->|命中| E["回填本地缓存"]
D -->|未命中| F["数据库查询"]
F --> G["构建Feed数据"]
G --> H["回填Redis"]
H --> E
E --> C
style A fill:#4A90E2,color:#fff,rx:10,ry:10
style B fill:#E67E22,color:#fff,rx:10,ry:10
style D fill:#9B59B6,color:#fff,rx:10,ry:10
style F fill:#27AE60,color:#fff,rx:10,ry:10Redis数据结构选择
使用Sorted Set存储用户的Feed流:
java
@Service
public class FeedCacheService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String FEED_KEY_PREFIX = "feed:inbox:";
private static final int MAX_FEED_SIZE = 1000; // 每个用户最多缓存1000条
/**
* 向用户收件箱推送内容
*/
public void pushToInbox(Long userId, Long contentId, long timestamp) {
String key = FEED_KEY_PREFIX + userId;
// 使用发布时间作为score
redisTemplate.opsForZSet().add(key,
contentId.toString(),
timestamp);
// 控制收件箱大小
Long size = redisTemplate.opsForZSet().size(key);
if (size != null && size > MAX_FEED_SIZE) {
// 删除最旧的内容
redisTemplate.opsForZSet().removeRange(key, 0, size - MAX_FEED_SIZE - 1);
}
}
/**
* 获取用户Feed流
*/
public List<Long> getFeed(Long userId, long maxScore, int count) {
String key = FEED_KEY_PREFIX + userId;
// 按score倒序获取,支持滚动分页
Set<String> contentIds = redisTemplate.opsForZSet()
.reverseRangeByScore(key, 0, maxScore, 0, count);
if (contentIds == null || contentIds.isEmpty()) {
return Collections.emptyList();
}
return contentIds.stream()
.map(Long::parseLong)
.collect(Collectors.toList());
}
}数据库分库分表
当数据量超过单库承载能力时,需要进行分库分表:
mermaid
graph TD
A["inbox表"] --> B["按user_id分片"]
B --> C["分库0"]
B --> D["分库1"]
B --> E["分库..."]
B --> F["分库N"]
C --> C1["inbox_0"]
C --> C2["inbox_1"]
D --> D1["inbox_0"]
D --> D2["inbox_1"]
style A fill:#4A90E2,color:#fff,rx:10,ry:10
style B fill:#9B59B6,color:#fff,rx:10,ry:10
style C fill:#27AE60,color:#fff,rx:10,ry:10
style D fill:#27AE60,color:#fff,rx:10,ry:10分片策略:
- 按用户ID哈希分库,保证同一用户数据在同一库
- 分表数量根据数据增长速度规划
- 历史数据定期归档到冷存储
实时性优化
异步推送流程
mermaid
sequenceDiagram
participant Author as 发布者
participant API as API服务
participant MQ as 消息队列
participant Push as 推送服务
participant Cache as Redis
Author->>API: 发布内容
API->>API: 写入内容表
API-->>Author: 返回成功
API->>MQ: 发送推送消息
MQ->>Push: 消费消息
Push->>Push: 获取活跃粉丝列表
loop 批量推送
Push->>Cache: 写入粉丝Feed缓存
end批量写入优化
java
@Service
public class FeedPushService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 批量推送到粉丝收件箱
*/
public void batchPushToFollowers(Long contentId,
long timestamp,
List<Long> followerIds) {
// 使用Pipeline批量写入
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (Long followerId : followerIds) {
String key = "feed:inbox:" + followerId;
connection.zSetCommands().zAdd(
key.getBytes(),
timestamp,
contentId.toString().getBytes());
}
return null;
});
}
/**
* 异步分批推送
*/
@Async
public void asyncPushToFollowers(Long authorId, Long contentId, long timestamp) {
int pageSize = 1000;
int page = 0;
while (true) {
// 分页获取活跃粉丝
List<Long> followers = followerService.getActiveFollowers(
authorId, page * pageSize, pageSize);
if (followers.isEmpty()) {
break;
}
// 批量推送
batchPushToFollowers(contentId, timestamp, followers);
page++;
}
}
}推荐系统集成
推荐召回与排序
mermaid
graph LR
subgraph 召回阶段
A["协同过滤召回"]
B["内容相似召回"]
C["热门召回"]
D["关注召回"]
end
subgraph 融合排序
E["特征提取"]
F["排序模型"]
G["重排序"]
end
subgraph 展示
H["Feed列表"]
end
A --> E
B --> E
C --> E
D --> E
E --> F
F --> G
G --> H
style A fill:#4A90E2,color:#fff,rx:10,ry:10
style B fill:#4A90E2,color:#fff,rx:10,ry:10
style C fill:#4A90E2,color:#fff,rx:10,ry:10
style D fill:#4A90E2,color:#fff,rx:10,ry:10
style F fill:#9B59B6,color:#fff,rx:10,ry:10
style H fill:#27AE60,color:#fff,rx:10,ry:10混合策略实现
java
@Service
public class HybridFeedService {
/**
* 获取混合Feed流
* 70%关注内容 + 30%推荐内容
*/
public List<FeedItem> getHybridFeed(Long userId, int count) {
int followCount = (int) (count * 0.7);
int recommendCount = count - followCount;
// 获取关注者内容
List<FeedItem> followFeed = getFollowFeed(userId, followCount);
// 获取推荐内容
List<FeedItem> recommendFeed = getRecommendFeed(userId, recommendCount);
// 合并并去重
List<FeedItem> result = new ArrayList<>();
Set<Long> contentIds = new HashSet<>();
// 交叉插入
int i = 0, j = 0;
while (result.size() < count) {
if (i < followFeed.size() && (j >= recommendFeed.size() ||
result.size() % 3 != 2)) {
FeedItem item = followFeed.get(i++);
if (contentIds.add(item.getContentId())) {
result.add(item);
}
} else if (j < recommendFeed.size()) {
FeedItem item = recommendFeed.get(j++);
if (contentIds.add(item.getContentId())) {
result.add(item);
}
} else {
break;
}
}
return result;
}
}更新: 2025-12-06 17:31:22
原文: https://www.yuque.com/u22210564/zoxfmt/sw6y7dft5a6w9rtf