本文目录导读:

- 方案一:全量拉取(Pull 模式)——适合小型项目、用户量少
- 方案二:预生成推模式(Push 模式)——适合中等规模、高并发
- 方案三:推拉结合(Fanout-on-write + 读扩散)——大规模系统首选
- 关键技术和性能优化点
- 简易 Demo 代码思路(基于推模式 + Redis)
- 总结推荐
在 PHP 项目中实现信息流(Feed/News Feed),核心思路与社交平台(如微博、朋友圈)类似:拉取用户关注对象或相关实体的最新动态,并按时间倒序或权重排序展示。
根据项目的规模和性能要求,主要有以下 3 种常见实现方案,从简单到复杂排序:
全量拉取(Pull 模式)——适合小型项目、用户量少
原理:每次用户刷新信息流时,直接查询数据库,找出该用户关注的所有人发布的动态,然后按时间排序。
实现步骤:
- 数据表设计:
posts(动态表):id, user_id, content, created_atfollows(关注关系表):user_id, follower_id
- 核心查询:
SELECT p.* FROM posts p JOIN follows f ON f.user_id = p.user_id WHERE f.follower_id = ? ORDER BY p.created_at DESC LIMIT 20 OFFSET 0;
优点:实现简单,数据实时性强。
缺点:当关注人数很多或动态数据量大时,JOIN 查询性能急剧下降,造成“粉丝越多,查询越慢”的问题(即读扩散)。
预生成推模式(Push 模式)——适合中等规模、高并发
原理:当用户发布动态时,系统立即将这条动态“推送到”所有粉丝的收件箱(Redis List 或数据库 Feed 表),用户查看信息流时,直接从自己的收件箱读取。
核心逻辑:写时扩散。
实现步骤:
- 收件箱存储:使用 Redis 的
LPUSH或Sorted Set。- Key 设计:
feed:{user_id}(每个用户一个 List 或 Zset) - Value:动态 ID 或动态内容(建议存 ID 以节省内存)。
- Key 设计:
- 发布流程:
- 用户 A 发布一条动态。
- 从数据库或缓存查询用户 A 的粉丝列表。
- 循环向每个粉丝的
feed:{follower_id}中LPUSH动态 ID。
- 读取流程:
- 用户 B 请求信息流。
- 从 Redis 中
LRANGE feed:B_ID 0 19取出最新 20 条动态 ID。 - 从数据库或缓存中
SELECT * FROM posts WHERE id IN (...)获取完整数据。
优点:用户读取信息流速度极快(只读自己的列表),读性能很好。
缺点:
- 写放大:大 V(如拥有 1000 万粉丝)发布一条动态,系统需要推送 1000 万次,压力极大。
- 存储浪费:每个粉丝的收件箱都存一份数据。
推拉结合(Fanout-on-write + 读扩散)——大规模系统首选
原理:区分普通用户和大 V。
- 对于普通用户(粉丝数少,如 < 1000):使用 推模式。
- 对于明星/大 V(粉丝数多,如 > 1000):使用 拉模式(不推送,粉丝刷新时临时拉取并合并)。
实现流程(典型百度/微博的方案):
- 发布时:
- 判断用户粉丝数,若粉丝数 < 阈值,推送到所有粉丝收件箱。
- 若粉丝数 >= 阈值,将动态直接写入大 V 的发件箱(
timeline:{author_id}的 Redis List)。
- 读取时:
- 获取登录用户关注的所有人列表。
- 将这些关注者分为两组:普通用户列表 + 大 V 列表。
- 从普通用户收件箱中读取动态(已预推送)。
- 从大 V 发件箱中拉取最新动态。
- 在应用层内存中将所有动态合并、排序、去重。
- 特殊处理:
对于大 V 最新发布的动态,在粉丝收件箱中标记一个“拉取指针”,下次读取时只拉取新增部分。
优点:平衡了读写性能和资源消耗,是工业级主流方案。
缺点:实现复杂,需要缓存、队列、异步任务支持。
关键技术和性能优化点
- 缓存为王:信息流一定是缓存密集型。
- 用 Redis 存储收件箱/发件箱(List/Sorted Set)。
- 用 Redis/Memcached 缓存用户对象、动态详情(减少数据库查询)。
- 异步队列:推模式中的“写扩散”操作必须异步。
- 用户发布动态 -> 请求入队(如 RabbitMQ/Redis Stream) -> Worker 消费队列,执行推送任务。
- 这样可以避免用户发布动态后等待过长响应。
- 分页与游标:避免使用传统
OFFSET分页,使用cursor(游标) 分页。- 客户端上次最后一条动态的
id或created_at。 - 查询
WHERE created_at < $cursor ORDER BY created_at DESC LIMIT 20。
- 客户端上次最后一条动态的
- 冷热数据分离:动态数据通常热度集中在最近几天,可将超过 30 天的历史数据归档到冷库(如 MySQL 历史表或 HBase),主库只保留热点数据。
- 数据一致性:推模式中,异步推送可能会失败(如 Redis 宕机),解决方案:
- 用户读取时进行“修复”:如果发现收件箱中缺少某人的最新动态,主动拉取并补入。
- 定时离线计算。
简易 Demo 代码思路(基于推模式 + Redis)
// 类定义
class FeedService {
private $redis;
private $db;
public function publishPost($userId, $content) {
$postId = $this->db->insert('posts', ['user_id' => $userId, 'content' => $content]);
// 获取粉丝列表(异步,这里为了示例同步处理)
$fans = $this->db->select("SELECT follower_id FROM follows WHERE user_id = ?", [$userId]);
// 批量异步推送(最佳实践:使用消息队列)
foreach ($fans as $fan) {
$this->redis->lPush("feed:{$fan['follower_id']}", $postId);
}
// 同时将自己的动态也推给自己(便于查看自己的个人主页)
$this->redis->lPush("feed:{$userId}", $postId);
return $postId;
}
// 获取用户信息流
public function getFeed($userId, $limit = 20) {
// 从 Redis 中取出最新的 $limit 条动态 ID
$postIds = $this->redis->lRange("feed:{$userId}", 0, $limit - 1);
if (empty($postIds)) return [];
// 批量获取动态详情(从缓存或数据库)
// 注意:SQL 中 IN 的顺序可能被打乱,需要在 PHP 中按 $postIds 的顺序重新排序
$placeholders = implode(',', array_fill(0, count($postIds), '?'));
$posts = $this->db->select("SELECT * FROM posts WHERE id IN ($placeholders) ORDER BY FIELD(id, ".implode(',', $postIds).")", $postIds);
return $posts;
}
}
总结推荐
| 方案 | 项目规模 | 优点 | 缺点 |
|---|---|---|---|
| 全量拉取 | < 5万用户 | 实现简单 | 性能差,无法扩展 |
| 推模式 | < 100万用户 | 读取极快 | 写放大,大 V 场景不可用 |
| 推拉结合 | 百万级以上 | 平衡读写,业界标准 | 实现最复杂 |
针对你的项目:
- 如果是博客系统、公司内部系统,全量拉取 加好索引(
user_id, created_at联合索引)足够。 - 如果是社交类 App,建议直接用 推模式(因为用户量通常可控),若出现大 V 问题再升级为推拉结合。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播