如何在社交网络中实现活动流

我正在开发我自己的社交网络,我还没有在网络上找到实现用户行为流的例子... ... 例如,如何过滤每个用户的行为?如何存储动作事件?哪个数据模型和对象模型可以用于操作流和操作本身?

62499 次浏览

我认为关于通知系统如何在大型网站上工作的解释可以在 杰里米 · 沃尔的答案中的堆栈溢出问题 社交网站如何计算好友更新?中找到。他建议使用 消息队列,并指出了两个实现它的开源软件:

  1. RabbitMQ
  2. Apache QPID

另见问题 实现社会活动流的最佳方式是什么?

摘要 : 对于大约100万活跃用户和1.5亿存储活动,我保持简单:

  • 使用关系数据库来存储独特的活动(每个活动一条记录/“发生的事情”)使记录尽可能紧凑。结构,以便您可以通过活动 ID 或使用一组具有时间限制的朋友 ID 快速获取一批活动。
  • 每当创建一条活动记录时,就将活动 ID 发布到 Redis,并将该 ID 添加到“活动流”列表中,以供应该看到该活动的每个朋友/订阅者使用。

QueryRedis 为任何用户获取活动流,然后根据需要从数据库获取相关数据。如果用户需要及时浏览很久以前的数据库(如果您提供了这种方法) ,那么就回到按时间查询数据库


我使用一个普通的旧 MySQL 表来处理大约1500万个活动。

它看起来像这样:

id
user_id       (int)
activity_type (tinyint)
source_id     (int)
parent_id     (int)
parent_type   (tinyint)
time          (datetime but a smaller type like int would be better)

activity_type告诉我活动的类型,source_id告诉我活动相关的记录。因此,如果活动类型意味着“添加的收藏”,那么我知道 source _ ID 指的是收藏记录的 ID。

parent_id/parent_type对我的应用程序很有用——它们告诉我这个活动与什么相关。如果收藏了一本书,那么 father _ id/father _ type 会告诉我,该活动与具有给定主键(id)的一本书(type)有关

我在 (user_id, time)上建立索引并查询属于 user_id IN (...friends...) AND time > some-cutoff-point的活动。丢弃 id 并选择一个不同的聚集索引可能是一个好主意——我还没有对此进行过实验。

非常基本的东西,但它的工作,它的简单,它很容易与您的需求变化。另外,如果你不使用 MySQL,你可能会在索引方面做得更好。


为了更快地访问最近的活动,我一直在尝试使用 雷迪斯。Redis 将所有数据存储在内存中,因此您不能将所有活动都存储在内存中,但是您可以将足够的数据存储到网站上大多数常见的屏幕上。每个用户最近的100或类似的东西。有了 Redis,它可能会这样工作:

  • 创建 MySQL 活动记录
  • 对于创建该活动的用户的每个朋友,将该 ID 推送到他们在 Redis 的活动列表中。
  • 将每个列表修剪为最后的 X 项

Redis 速度很快,并且提供了一种通过一个连接管道命令的方法——因此将一个活动推送给1000个好友需要花费毫秒的时间。

有关我所说的内容的更详细解释,请参阅 Redis 的 Twitter 示例: http://redis.io/topics/twitter-clone

更新2011年2月 我现在有5000万活跃的活动,我没有改变任何事情。类似这样做的一个好处是它使用紧凑的小行。我计划做一些改变,这将涉及更多的活动和更多的查询这些活动,我一定会使用 Redis 来保持事情的速度。我在其他领域使用 Redis,它确实对某些类型的问题起到了很好的作用。

更新2014年7月 我们每月的活跃用户达到70万。在过去的几年中,我一直使用 Redis (如项目符号列表中所述)来存储每个用户的最后1000个活动 ID。系统中通常有大约1亿条活动记录,它们仍然存储在 MySQL 中,并且仍然是相同的布局。这些记录让我们可以使用更少的 Redis 内存,它们作为活动数据的记录,如果用户需要及时进一步分页以找到某些内容,我们就使用它们。

这不是一个聪明或特别有趣的解决方案,但它对我很有帮助。

这是我使用 mysql 实现的活动流。 有三个类: Activity、 ActivityFeed、 Subscriber。

Activity 表示一个活动条目,它的表如下所示:

id
subject_id
object_id
type
verb
data
time

Subject_id是执行操作的对象的 id,object_id是接收操作的对象的 id。typeverb描述了操作本身(例如,如果用户向一篇文章添加一条评论,它们将分别是“评论”和“创建”) ,数据包含额外的数据以避免连接(例如,它可以包含主题名和姓、文章标题和 url、评论主体等)。

每个 Activity 属于一个或多个 ActivityFeeds,它们通过一个表相关联,该表如下所示:

feed_name
activity_id

在我的应用程序中,我为每个用户提供一个提要,为每个项目提供一个提要(通常是博客文章) ,但是它们可以是您想要的任何内容。

订阅服务器通常是站点的用户,但也可以是对象模型中的任何对象(例如,一篇文章可以订阅其创建者的 feed _ action)。

每个订阅者都属于一个或多个 ActivityFeeds,并且,像上面一样,它们通过这种链接表相关联:

feed_name
subscriber_id
reason

这里的 reason字段解释了订阅者订阅提要的原因。例如,如果用户将一篇博客文章加入书签,原因就是“书签”。这有助于我稍后筛选向用户发送通知的操作。

为了检索订阅者的活动,我对这三个表进行了简单的连接。联接很快,因为我选择了很少的活动,这要感谢现在看起来像 time > some hoursWHERE条件。由于 Activity 表中的数据字段,我避免了其他连接。

reason字段的进一步说明。例如,如果我想过滤邮件通知给用户的动作,并且用户将一篇博客文章加入书签(因此他订阅了这篇文章的原因是“书签”) ,我不希望用户收到关于这个项目的动作的邮件通知,而如果他评论这篇文章(因此它订阅了这篇文章的原因是“评论”) ,我希望当其他用户在同一篇文章中添加评论时,他能得到通知。原因字段可以帮助我进行这种区分(我通过 ActivityFilter 类实现了它) ,以及用户的通知首选项。

您绝对需要一个性能和分布式消息队列。但是这还没有结束,您将不得不决定哪些数据存储为持久数据,哪些数据存储为瞬态数据,等等。

无论如何,这是一个真正的困难的任务,我的朋友,如果你是一个高性能和可扩展的系统。当然,一些慷慨的工程师也分享了他们的经验。LinkedIn 最近将其消息队列系统 Kafka 开源。在此之前,Facebook 已经向开源社区提供了 Scribe。Kafka 是用 Scala 编写的,一开始运行它需要一些时间,但我用几个虚拟服务器进行了测试。真的很快。

Http://blog.linkedin.com/2011/01/11/open-source-linkedin-kafka/

Http://incubator.apache.org/kafka/index.html

您可以查看通过 API 使用的第三方服务,而不是自己滚动。我创建了一个名为 Collabate (http://www.collabinate.com)的系统,它有一个图形数据库后端和一些相当复杂的算法,用于以高度并发、高性能的方式处理大量数据。虽然它没有 Facebook 或者 Twitter 那样的功能广度,但是对于大多数需要在应用程序中构建活动流、社交订阅或者微博功能的用例来说,它已经足够了。