让我开始说,我知道这不是最好的解决方案。我知道这是一个kludgy和一个功能的黑客。 但那就是我在这里的原因!
这个问题/工作建立在Facebook新闻Feed的创建者some discussion on Quora with Andrew Bosworth之上。
我正在构建各种新闻源。它仅由PHP
和MySQL
构建。
Feed的关系模型由两个表组成。一个表用作活动日志;实际上,它的名字是activity_log
。另一个表是newsfeed
。 这些表几乎相同。
日志的架构为activity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)
... Feed的架构为newsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)
。
只要用户执行与新闻Feed相关的内容,例如提出问题,就会立即记录到活动日志。
然后每X分钟(此刻为5分钟,将更改为15-30分钟后),我运行一个执行下面脚本的cron作业。此脚本循环遍历数据库中的所有用户,查找该用户所有朋友的所有活动,然后将这些活动写入新闻源。
目前,剔除活动的SQL
(在ActivityLog::getUsersActivity()
中调用)因性能原因而强加LIMIT 100
。 *不是我知道我在说什么。
<?php
$user = new User();
$activityLog = new ActivityLog();
$friend = new Friend();
$newsFeed = new NewsFeed();
// Get all the users
$usersArray = $user->getAllUsers();
foreach($usersArray as $userArray) {
$uid = $userArray['uid'];
// Get the user's friends
$friendsJSON = $friend->getFriends($uid);
$friendsArray = json_decode($friendsJSON, true);
// Get the activity of each friend
foreach($friendsArray as $friendArray) {
$array = $activityLog->getUsersActivity($friendArray['fid2']);
// Only write if the user has activity
if(!empty($array)) {
// Add each piece of activity to the news feed
foreach($array as $news) {
$newsFeed->addNews($uid, $friendArray['fid2'], $news['activity'], $news['activity_id'], $news['title'], $news['time']);
}
}
}
}
在客户端代码中,在获取用户的新闻源时,我会执行以下操作:
$feedArray = $newsFeed->getUsersFeedWithLimitAndOffset($uid, 25, 0);
foreach($feedArray as $feedItem) {
// Use a switch to determine the activity type here, and display based on type
// e.g. User Name asked A Question
// where "A Question" == $feedItem['title'];
}
现在请原谅我对开发新闻Feed的最佳做法的有限理解,但我理解我正在使用的方法是限制版本所谓的扇出写,受限于我认为我正在运行一个cron作为中间步骤,而不是直接写入用户的新闻源。但这与拉模型非常不同,因为用户的新闻源不是在加载时编译的,而是定期编译的。
这是一个很大的问题,可能需要大量来回,但我认为它可以作为像我这样的新开发者需要拥有的许多重要对话的试金石。我只想弄清楚我做错了什么,如何改进,或者我应该从头开始尝试不同的方法。
另一件让我对这个模型感到困惑的事情是它基于新近度而不是相关性。如果有人可以建议如何改进这种相关性,我会全力以赴。我正在使用Directed Edge的API来生成推荐,但似乎对于像新闻源这样的东西,推荐者将无法工作(因为之前没有任何优势!)。
答案 0 :(得分:12)
真的很酷的问题。我实际上正在实现这样的事情。所以,我要大声思考一下。
以下是我目前在实施中看到的缺陷:
您正在处理所有用户的所有朋友,但由于同一群人有类似的朋友,您最终会多次处理相同的用户。
如果我的一位朋友发布了某些内容,它最多不会在我的新闻Feed中显示最多5分钟。它应该立即出现,对吗?
我们正在阅读用户的整个新闻Feed。自上次我们碾碎日志以来,我们不需要抓住新的活动吗?
这不能很好地扩展。
新闻源看起来与活动日志完全相同,我会坚持使用那个活动日志表。
如果您跨数据库对活动日志进行分片,则可以更轻松地进行扩展。如果您愿意,也可以对用户进行分片,但即使您在一个表中有1000万个用户记录,mysql应该可以正常读取。因此,无论何时查找用户,您都知道从哪个分片访问用户的日志。如果您经常存档旧日志并且只维护一组新的日志,则不必进行多少分片。或者甚至可能。如果调整得适中,你可以在MySQL中管理数百万条记录。
我会将memcached用于你的用户表,甚至可能是日志本身。 Memcached允许高达1mb的缓存条目,如果你聪明地组织密钥,你可能会从缓存中检索所有最新的日志。
就架构而言,这将是更多的工作,但它将允许您实时工作并在未来扩展...尤其是当您希望用户开始评论时在每个帖子上。 ;)
你看到这篇文章了吗?
答案 1 :(得分:0)
您会添加统计关键字吗?我通过展开我的文档正文,剥离HTML,删除常用单词和计算最常用的单词来实现(粗略)实现。几年前我做的只是为了好玩(就像任何这样的项目,源代码都消失了),但它适用于我的临时测试博客/论坛设置。也许它适用于您的新闻源...
答案 2 :(得分:0)
之间你可以使用用户标志和缓存。 可以说,为last_activity提供了一个新用户字段。 用户输入任何活动时更新此字段。 保持一个标志,直到你提取饲料的时间让我们说它feed_updated_on。
现在更新函数$ user-&gt; getAllUsers();仅返回last_activity时间晚于feed_updated_on的用户。 这将排除没有任何活动日志的所有用户:)。 用户朋友的类似过程。
您还可以使用缓存,如memcache或文件级缓存。
或者使用一些nosql DB将所有Feed存储为一个文档。
答案 3 :(得分:0)
我正在尝试自己构建一个Facebook风格的新闻源。我没有创建另一个表来记录用户的活动,而是从帖子,评论等的UNION计算了“边缘”。
通过一些数学,我使用指数衰减模型计算'边缘',时间流逝是自变量,考虑每个帖子必须制定lambda常数的注释,喜欢等的数量。边缘将首先快速减小,但在几天后逐渐变平至几乎为0(但永远不会达到0)
显示Feed时,每个边缘使用RAND()相乘。具有较高优势的帖子会更频繁地出现
这样,更受欢迎的帖子更有可能在新闻Feed中出现更长的时间。
答案 4 :(得分:0)
不是运行cron作业,而是运行某种类型的提交后脚本。我不清楚PHP和MySQL在这方面的功能是什么 - 如果我没记错,MySQL InnoDB允许比其他品种更高级的功能,但我不记得最新版本中是否有类似触发器的东西。
无论如何,一个不依赖于大量数据库魔法的简单变种:
当用户X添加内容时:
1)在数据库提交后从PHP页面进行异步调用(当然是异步的,这样查看页面的用户就不必等待它了!)
调用启动逻辑脚本的实例。
2)逻辑脚本只通过提交新内容的用户[A,B,C]的列表 (而不是数据库中每个人的列表!)和将用户X的操作附加到每个用户的订阅源。
您可以将这些Feed存储为直接的JSON文件,并将新数据附加到每个文件的末尾。当然更好的方法是通过备份到文件系统或BerkeleyDB或Mongo或任何你喜欢的东西来保持缓存中的提要。
这只是基于新近度而非相关性的Feed的基本概念。您可以以这种方式按顺序存储数据,然后根据每个用户进行额外的解析以按相关性进行过滤,但这在任何应用程序中都是一个难题,而且可能不是匿名Web用户可以轻松解决的问题而没有详细说明了解您的要求;)
JSH
答案 5 :(得分:0)
我使用 2 级缓存生成用户提要的机制略有不同。 我对规模的假设是基于我的理论经验,但同样的方法可以根据需要用于不同的规模。
上图试图解释整个提要生成架构
假设您有 1 亿用户。根据 80-20 规则,20% 的活跃用户产生了 80% 的流量。考虑到每个活跃用户每天生成 20 个帖子,您有 2000 万用户每天生成 4 亿个新帖子。考虑到每个活跃用户都有大约 1000 个朋友,其中 20% 是活跃的,即 200 个有最近帖子的活跃朋友。每个用户有(200 个活跃朋友)*(每个用户 20 个帖子)= 4000 个有资格在动态上显示的帖子。
创建一个缓存,存储 24-48 小时的最近帖子,即大约 8 亿个帖子。将这些帖子针对其所有者存储为 userid: posts[]
,其中用户 ID 是创建帖子的用户,帖子包含他最近 24-48 小时的帖子。
创建一个 Feed Generator 服务,为每个活跃用户 (20M) 获取该用户的 200 个活跃朋友的帖子,并在 Feed 的缓存中生成另一个符合 Feed 条件的帖子数组 userid:posts[]
,其中 userid 是打开的用户他的提要和帖子[] 是所有帖子的超集。
此 Feed Generator 服务可以定期为每个活跃用户运行,也可以按需为每个非活跃用户运行。填充 Feed 缓存后,Feed Generator 服务可以每隔一小段时间运行一次,以根据最近帖子缓存中的更新行填充增量帖子
Feed 服务可以连接到 Feed 的缓存并根据相关性、重要性、新近度或任何其他逻辑显示帖子。