Facebook数据库设计?

144

我一直想知道Facebook如何设计朋友<->用户关系。

我认为用户表格大致是这样的:

user_email PK
user_id PK
password 

我想要通过用户的电子邮件连接与用户相关的数据表格(例如性别、年龄等)。

它如何将所有朋友与此用户连接起来?

是这样的吗?

user_id
friend_id_1
friend_id_2
friend_id_3
friend_id_N 

可能不行。因为用户数量未知且将会扩大。


14
有一个 Facebook 工程页面提供很多这种类型的信息,但可能不完全符合你的问题。你可以在那里提问,看看能否得到答案。 http://www.facebook.com/FacebookEngineering - John Meagher
1
谷歌“图形数据库”。它肯定不是关系型数据库。 - user177800
11个回答

91

保留一个朋友表,其中包含用户ID及其朋友的用户ID(我们将其称为FriendID)。这两列都将是对Users表的外键。

一个有些有用的示例:

Table Name: User
Columns:
    UserID PK
    EmailAddress
    Password
    Gender
    DOB
    Location

TableName: Friends
Columns:
    UserID PK FK
    FriendID PK FK
    (This table features a composite primary key made up of the two foreign 
     keys, both pointing back to the user table. One ID will point to the
     logged in user, the other ID will point to the individual friend
     of that user)

使用示例:

Table User
--------------
UserID EmailAddress Password Gender DOB      Location
------------------------------------------------------
1      bob@bob.com  bobbie   M      1/1/2009 New York City
2      jon@jon.com  jonathan M      2/2/2008 Los Angeles
3      joe@joe.com  joseph   M      1/2/2007 Pittsburgh

Table Friends
---------------
UserID FriendID
----------------
1      2
1      3
2      3

这将显示Bob与Jon和Joe都是朋友,而Jon也与Joe是朋友。在这个例子中,我们假设友谊总是双向的,所以你不需要在表中再添加像(2,1)或(3,2)这样的行,因为它们已经在另一个方向上表示了。对于友谊或其他关系没有明确双向的情况,您需要添加这些行来指示双向关系。


14
想想这个过程的低效性吧——你必须在多对多关系的列上进行一个分离的查询,平均而言搜索时间增加了一倍。 - Anthony Bishopric
2
就我个人而言,我不想让这两个字段成为组合主键。绝对需要一个唯一键。在该唯一键上建立聚集索引,当然可以。但我还会将某种非组合标识作为PK与非聚集索引一起使用。这将允许其他需要“朋友关系ID” FK的表轻松地连接到此表,并且各种触发器可以触发朋友关系、取消关注等级联事件。 - Jesse C. Slicer
1
据称Facebook拥有约10亿用户。如果平均每个用户有100个朋友,那么这个表将包含1,000,000,000,000行。MySQL分区? - veidelis
忘记这种方法吧。如果你有大量的用户,它肯定会变得非常慢。看看我的回答,试着自己进行基准测试。我已经对10k个用户和250万个好友连接进行了一些基准测试,结果令人失望。如果你运行一个小社区,它会很好地工作,但需要考虑性能问题。 - floriank
13
可以确定的是,Facebook不会使用关系型数据库管理系统来运行此类查询。众所周知,Facebook、Twitter以及其他需要运行此类查询的人都使用某种图形数据库。至少有69个人从未在大规模环境下工作过或不知道如何进行大规模计算。 - user177800

63

TL;DR:

他们使用堆栈体系结构,并为堆栈上方的所有内容使用缓存图形,而底部则是MySQL。

长答案:

我自己进行了一些研究,因为我很好奇他们如何处理大量数据并快速搜索。我看到有人抱怨自定义社交网络脚本在用户基数增长时变得缓慢。在我自己进行了一些基准测试后,只有10k用户和2.5百万个朋友连接 - 甚至不想麻烦关注组权限、点赞和墙贴 - 很快就发现这种方法是有缺陷的。所以我花了一些时间在网上搜索如何更好地做到这一点,并找到了这篇官方Facebook文章: 我强烈推荐您在继续阅读之前观看上面第一个链接的演示。这可能是关于FB后台运作方式最好的解释。
视频和文章告诉您以下几点:
  • 他们在堆栈的最底层使用MySQL
  • 在SQL DB之上是TAO层,其中包含至少两个级别的缓存,并使用图形描述连接。
  • 我找不到有关他们实际用于缓存图形的软件/数据库的任何信息
让我们来看一下,朋友连接在左上角:

enter image description here

好的,这是一张图表。 :) 它并不告诉你如何在SQL中构建它,有几种方法可以做到,但this site提供了许多不同的方法。注意:请考虑关系型数据库是什么:它被设计用来存储规范化的数据,而不是图形结构。因此,它的性能不会像专门的图形数据库那样好。

还要考虑到您需要执行比仅仅查找朋友的朋友更复杂的查询,例如当您想要过滤出您和您的朋友喜欢的给定坐标周围的所有位置时。图形是完美的解决方案。

我无法告诉您如何构建它以使其性能良好,但显然需要进行一些试验和基准测试。

这是我仅仅查找朋友的朋友的令人失望的测试结果:

数据库架构:

CREATE TABLE IF NOT EXISTS `friends` (
`id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `friend_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

朋友的朋友查询:

(
        select friend_id
        from friends
        where user_id = 1
    ) union (
        select distinct ff.friend_id
        from
            friends f
            join friends ff on ff.user_id = f.friend_id
        where f.user_id = 1
    )

我强烈建议您创建至少10,000个用户记录的示例数据,每个用户都至少有250个好友连接,然后运行此查询。在我的机器上(i7 4770k,SSD,16GB RAM),该查询的结果为约0.18秒。也许它可以进行优化,我不是一个DB天才(欢迎提供建议)。但是,如果这是线性比例,仅仅100k用户就需要1.8秒,而100万用户需要18秒。
这可能对于~100k用户来说仍然可以,但请考虑一下您仅获取了朋友的朋友,并且没有执行任何更复杂的查询,如“仅显示来自朋友的帖子+检查权限是否允许或禁止查看其中一些+执行子查询以检查我是否喜欢过其中任何一个”。您希望让数据库检查您是否已经喜欢帖子,否则您需要在代码中进行检查。另外,请考虑这不是您运行的唯一查询,并且您在一个更或多或少受欢迎的网站上拥有多个活动用户。
我认为我的回答很好地解释了Facebook如何设计其朋友关系,但很抱歉我不能告诉你如何以能快速运行的方式来实现它。 实施社交网络很容易,但确保它能够良好运行显然并不容易-在我看来。

我已经开始尝试使用OrientDB进行图形查询,并将我的边缘映射到底层SQL数据库。 如果我完成了,我会写一篇文章介绍它。

如何创建一个性能良好的社交网络网站?

更新2021-04-10: 我可能永远不会写这篇文章;) 但是这里有一些要点,您可以尝试将其扩展:

  • 使用不同的读取和写入存储库
  • 建立特定的读取存储库,基于更快的非关系型DB系统构建,不要害怕去正常化数据。 写入规范化的数据库,但从专门的视图中读取。
  • 使用最终一致性
  • 看一下CQRS
  • 对于社交网络,以图形为基础的读取存储库也可能是一个好主意。
  • 在其中使用Redis作为读取存储库,其中存储了整个序列化数据集。
如果您以聪明的方式将上述列表中的要点结合起来,您可以构建一个表现非常好的系统。该列表不是“待办事项”列表,您仍然需要理解、思考和适应它!https://microservices.io/ 是一个不错的网站,涵盖了我之前提到的一些主题。
我的做法是存储由聚合生成的事件,并使用项目和处理程序将其写入上面提到的不同数据库。这个酷炫的事情是,我可以随时根据需要重新构建我的数据。

1
那么,你最终有没有写那篇文章呢? - Just a coder
1
不,我除了做编程之外还很忙,没有时间和心情去这样做。如果要实现高性能的好友关联,这里的答案包含了你需要知道的一切。可以缓存每个用户的好友列表,或将关系型数据库的部分或整个映射到图形,并查询图形数据库。你可以使用OrientDB或Neo4j来做到这一点。我很想编写自己的开源社交网络软件,但还有很多其他事情要做。不管你做什么:请进行基准测试。 :) - floriank
还没有。但是OrientDB文档解释了朋友连接以及一旦理解了基础知识,其他所有内容都可以被建模。http://orientdb.com/docs/2.1/Tutorial-Working-with-graphs.html 如果您想使用关系型数据库作为基础,则只需在“保存后”和“删除后”回调中添加一些代码来更新图形数据库(您用于读取数据)。如果您没有这样的回调,请实现它们,但我想几乎所有类型的ORM实现和框架都有类似的功能。实际上,OrientDB也可以存储文档。 - floriank
1
还没有,但我们在工作中做了类似的事情:我们将我们的关系型数据映射到Elastic Search索引中,正如我之前在评论中写的那样,只需要获取要存储在索引或图形中的数据(在我们的情况下是afterSave() / afterDelete()回调)然后更新索引或图形。很简单吧? :)同样的方式也可以用于朋友列表,无论你将它们存储在ES、图形或基于内存的缓存中(只要你有足够的RAM)。这真的不难,难的是在你成长时使整个系统的规模扩展。 - floriank

55

9
这是一个类图,而不是数据库架构。 - PeakGen
2
那么每个“用户”都有自己专用的数据库吗?就像上面那个一样?它是如何工作的?例如,当用户登录时,FB会检查是否为有效的用户+密码,然后如果有效,Facebook将重定向他们到其数据库,然后显示来自上述数据库的所有内容。 - James111
这个存储只包含与用户相关的信息,我特别在寻找帖子及其受众? - Waseem Ahmad Naeem

37

我个人认为他们可能创建了一个图形结构。其中节点是用户,"友谊"则是边缘。

保留一个用户表格,保留另一个边缘表格。然后可以存储有关边缘的数据,例如 "成为朋友的日期" 和 "批准状态"等。


49
我感觉你需要对一些人在这里进行更详细的解释。 - TheTXI
4
我认为一个更有趣的问题是如何以一种易于搜索和更新的方式维护如此庞大的结构(我们正在谈论2亿个节点和数十亿条边)。 - Dirk Vollmar

21

很可能是多对多的关系:

FriendList(表)

user_id -> users.user_id
friend_id -> users.user_id
friendVisibilityLevel

编辑

用户表可能没有将user_email作为主键,但可能作为唯一键。

用户(表)

user_id PK
user_email
password

5
尽管这样做确实最合理,但考虑到Facebook用户众多且每个用户都有很多好友,我认为性能可能会非常糟糕。 - Kevin Pang

19

查看以下文章,描述LinkedIn和Digg如何构建:

还有“来自Facebook数据团队的大数据视角”的文章可能有所帮助:

http://developer.yahoo.net/blogs/theater/archives/2008/01/nextyahoonet_big_data_viewpoints_from_the_fac.html

此外,还有一篇文章讨论非关系型数据库以及它们如何被某些公司使用:

http://www.readwriteweb.com/archives/is_the_relational_database_doomed.php

你会发现,这些公司正在处理数据仓库、分区数据库、数据缓存等比我们大多数人平时接触到的更高级概念。至少,也许我们不知道我们在做什么。

第一和第二篇文章上有很多链接,可以让你了解更多信息。

更新于2014年10月20日

Murat Demirbas写了一份关于:

  • TAO:Facebook的分布式数据存储(ATC'13)
  • F4:Facebook的温暖BLOB存储系统(OSDI'14)

http://muratbuffalo.blogspot.com/2014/10/facebooks-software-architecture.html

希望这有所帮助。


12

当涉及到跨越超过50亿条数据并且要在恒定时间内检索用户好友信息时,无法从关系型数据库(RDBMS)中检索数据。

因此,Facebook使用哈希数据库(no SQL)实现了这一功能,并将该数据库开源为Cassandra。

因此,每个用户都有自己的键和放置好友详细信息的队列;若想了解Cassandra的工作方式,请查看此链接:

http://prasath.posterous.com/cassandra-55


2
注意:Posterous Spaces已经停止运营...因此链接失效。 - Niki Romagnoli

6

5

您正在寻找外键。基本上,除非它有自己的表格,否则您无法在数据库中拥有数组。


示例模式:

    用户表
        userID PK
        其他数据
    好友表
        userID   -- FK 指向用户表,代表拥有好友的用户。
        friendID -- FK 指向用户表,代表朋友的用户 ID

0

可能有一张表,存储着朋友与用户之间的关系,称为“frnd_list”,具有字段'user_id'和'frnd_id'。

每当一个用户将另一个用户添加为好友时,就会创建两个新行。

例如,假设我的ID是“deep9c”,我将一个ID为“akash3b”的用户添加为我的好友,则在表“frnd_list”中创建了两个新行,其值分别为('deep9c','akash3b')和('akash3b','deep9c')。

现在,在向特定用户显示好友列表时,可以使用简单的SQL语句:“select frnd_id from frnd_list where user_id =”,其中 是已登录用户的ID(存储为会话属性)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接