我如何改进这个 PHP/MySQL 新闻提要?

首先我要说的是,我知道这不是最好的解决方案。我知道这很复杂,而且只是个小插曲。但这就是我来这里的原因!

这个问题/工作建立在 和 Andrew Bosworth 在 Quora 上讨论的基础上,和 Andrew Bosworth 在 Quora 上讨论是 Facebook 新闻订阅的创建者。

我正在构建一个类似于 的新闻源,它完全是用 PHPMySQL构建的。

alt text


MySQL

提要的关系模型由两个表组成。一个表用作活动日志; 实际上,它被命名为 activity_log。另一个表是 newsfeed这些桌子几乎一模一样。

日志架构activity_log(uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)

提要的架构newsfeed(uid INT(11), poster_uid INT(11), activity ENUM, activity_id INT(11), title TEXT, date TIMESTAMP)

任何时候,当用户执行与新闻提要相关的 操作时,例如提出一个问题,就立即执行 它会被记录到活动日志中操作。


正在生成新闻

然后 每个 X 分钟(目前5分钟,将改为15-30分钟后) ,我经营一家老古董公司执行下面的脚本。这个脚本循环遍历数据库中的所有用户,查找该用户所有朋友的所有活动,然后将这些活动写入新闻提要。

目前,剔除活动(在 ActivityLog::getUsersActivity()中调用)的 SQL由于性能 * 的原因强加了一个 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'];


}

改善新闻推送

现在请原谅我对开发新闻提要的最佳实践的有限理解,但是我理解我所使用的方法是所谓的 书面展开的有限版本,有限的意思是我运行的是一个 cron 作业作为一个中间步骤,而不是直接写到用户的新闻提要。但是这与拉模型有很大的不同,因为用户的新闻提要不是按负载编译的,而是按常规编译的。

这是一个很大的问题,可能需要进行大量的反复讨论,但是我认为它可以作为许多像我这样的新开发人员需要进行的重要对话的试金石。我只是想弄清楚我做错了什么,我该如何改进,或者我应该如何从头开始,尝试一种不同的方法。

关于这个模型,另一件让我困扰的事情是,它是基于最近发生的事情而不是相关性来工作的。如果有人能提出如何改进这个工作的相关性,我会洗耳恭听。我正在使用 Directed Edge 的 API 来生成推荐,但似乎对于像新闻提要这样的东西,推荐不起作用(因为以前没有什么是受欢迎的!).

29399 次浏览

你会添加统计关键字吗?我做了一个(粗糙的)实现,通过爆炸我的文档正文,剥离 HTML,删除常见的单词,并计算最常见的单词。几年前我做这个只是为了好玩(对于任何这样的项目来说,源代码都已经没有了) ,但它对我的临时测试博客/论坛设置很有用。也许对你的新闻推送有用。

之间可以使用用户标志和缓存。 假设,有一个新的 user 字段作为 last _ activity。 每当用户输入任何活动时更新此字段。 保留一个标志,直到获取提要的时间,让我们说它 feed _ update _ on。

现在更新函数 $user-> getAllUsers () ; 只返回在 feed _ update _ on 之后具有 last _ activity 时间的用户。 这将排除所有没有任何活动日志的用户:)。 类似的过程为用户朋友。

您还可以使用诸如 memcache 或文件级别缓存之类的缓存。

或者使用一些 nosqlDB 将所有提要存储为一个文档。

问得好。其实我自己也正在实施这样的计划。所以,我要把我的想法说出来。

以下是我在脑海中看到的你当前实现的缺陷:

  1. 您正在为所有用户处理所有的好友,但是由于同一组人有相似的好友,所以最终将多次处理相同的用户。

  2. 如果我的一个朋友发布了一些东西,它不会显示在我的新闻饲料最多5分钟。但它应该马上出现,对吧?

  3. 我们正在为用户阅读整个新闻提要。我们不是应该抓住上次处理日志后的新活动吗?

  4. 这东西伸缩性不太好。

Newsfeed 看起来与活动日志完全相同,我会坚持使用那个活动日志表。

如果跨数据库分割活动日志,则可以更容易地进行伸缩。如果您愿意,也可以将用户分片,但是即使在一个表中有1000万条用户记录,mysql 也可以进行读操作。因此,无论何时查找用户,您都知道从哪个碎片访问用户的日志。如果您经常对旧日志进行归档,并且只维护一组新的日志,那么就不必进行那么多的分片。甚至可能根本没有。如果调优得当,您可以在 MySQL 中管理数百万条记录。

我将利用 memcached 为您的用户表,甚至可能是日志本身。Memcached 允许高达1mb 的缓存条目,如果您聪明地组织了您的键,您可能会从缓存中检索到所有最新的日志。

就架构而言,这将是更多的工作,但是它将允许您实时工作并在未来扩展... ... 特别是当您希望用户在每次发布时启动 评论时。;)

你看到这篇文章了吗?

Http://bret.appspot.com/entry/how-friendfeed-uses-mysql

我正在尝试自己建立一个 Facebook 风格的新闻订阅。我没有创建另一个表来记录用户的活动,而是从 UNION 中计算了帖子、评论等的“边缘”。

通过一些数学运算,我使用一个指数衰减模型来计算“优势”,时间是一个自变量,考虑到每篇文章的评论数量、点赞数量等等,我必须用 lambda 常数来表示。边缘开始会迅速下降,但几天后逐渐变平,几乎为0(但永远不会达到0)

在显示提要时,使用 RAND ()将每个边缘相乘。边缘较高的帖子将更频繁地出现

这样一来,更受欢迎的帖子出现在新闻推送中的可能性就会更高,时间也会更长。

不运行 cron 作业,而是某种后提交脚本。我不知道 PHP 和 MySQL 在这方面的具体功能是什么——如果我没记错的话,MySQL InnoDB 比其他版本允许更高级的功能,但我不记得最新版本中是否有触发器之类的东西。

无论如何,这是一个不依赖于大量数据库魔法的简单品种:

当用户 X 添加内容时:

1)在数据库提交后从 PHP 页面执行异步调用(当然是异步的,这样用户查看页面时就不必等待了!)

调用启动逻辑脚本的一个实例。

2)逻辑脚本通过提交新内容的用户的好友列表(而不是数据库中所有人的列表)进行 只有操作并将用户 X 的操作附加到每个用户的提要。

您可以将这些提要存储为直接的 JSON 文件,并在每个提要的末尾添加新数据。当然,最好将提要保存在缓存中,并备份到文件系统或 BerkeleyDB 或 Mongo 或任何您喜欢的地方。

这只是一个基于新近性而非相关性的订阅源的基本概念。你可以按照这种方式顺序存储数据,然后根据相关性对每个用户进行额外的解析,但这在任何应用程序中都是一个难题,可能不是一个匿名网络用户在没有详细了解你的需求的情况下就能轻易解决的问题;)

JSH

使用2级缓存生成用户提要的机制略有不同。 我对规模的假设是基于我的理论经验,但同样的方法可以根据需要用于不同的规模。

Feed service with dual caching mechanism

上图试图解释整个提要生成架构

假设您有1亿用户。根据80-20规则,20% 的活跃用户产生80% 的流量。考虑到每个活跃用户每天生成20个帖子,你有2000万用户每天生成400万个新帖子。考虑到每个活跃用户都有大约1000个朋友,其中20% 是活跃的,也就是说,有200个活跃的朋友发布了最近的帖子。每个用户有(200个活跃的朋友) * (每个用户20个帖子) = 4000个帖子有资格出现在 feed 上。

创建一个缓存,其中存储24-48小时的最近的职位,即大约800M 职位。将这些帖子存储在其所有者的 userid: posts[]中,其中 userid 是创建帖子的用户,帖子包含他最后24-48小时的帖子。

创建一个 Feed Generator 服务,为每个活跃用户(20M)获取该用户的200个活跃好友的帖子,并在 Feed 的缓存中生成另一个符合 Feed 条件的帖子数组,比如 userid:posts[],其中 userid 是打开 Feed 的用户,post []是所有帖子的超集。

这个 Feed Generator 服务可以为每个活动用户周期性地运行,也可以根据需要为每个非活动用户运行。一旦填充了 Feed 缓存,Feed Generator 服务就可以每隔一小段时间运行一次,仅仅是为了根据最近帖子缓存中更新的行填充 delta 帖子

Feed 服务可以连接到 Feed 的缓存,并根据相关性、重要性、近期性或任何其他逻辑显示帖子。