LinkedIn之类的网站如何有效地在每个人的姓名旁边显示第一级/第二级/第三级关系?

问题描述 投票:38回答:6

我最近通过差劲回答一个直截了当的问题来搞砸了工作面试:像LinkedIn这样的网站如何有效地显示从您到页面上显示的每个人(例如,人搜索结果,公司工作的人等)?

我得到了解决方案的基本“窍门”:找到“与我的距离”是一种常见的操作(例如,单页显示20x +,每个登录会话100个),因此您可以执行部分​​操作“我到X的距离”,对其进行缓存,然后多次重复使用该缓存的部分结果,以使其他操作更便宜。我还猜测部分结果可能是我的第二级连接,因为“缓存所有第三级连接”在RAM和CPU上的成本太高了。 EDIT>

但是,当试图将这种见解转化为解决方案时,我想到了一个令人困惑的答案,涉及创建站点上每个人的二级连接的持久性缓存(这在性能和维护方面将是非常重要的),并且我以一种毫无意义的方式绕开了Bloom Filters的使用。这样的回答后,我就不会再雇用自己了!

稍后,当我想到这个问题而没有面试的压力时,我想出了一个更合理的答案。

  • 构建一种非常快速的方法来获取每批用户ID(批量大小最大为〜1000?)的第一级连接。这可能意味着一个由大量RAM服务器组成的专用集群,可以将整个网络的第一级连接缓存在内存中。幸运的是,有5000万会员x平均每个成员100个连接x每个成员ID 4个字节= <25GB以缓存在RAM中,这可以通过价格合理的硬件实现。而且每天的更改数量将不到1%,因此保持高速缓存的最新状态并不是一件难事。 (请注意,关系数据库可能不是实现此缓存的最佳选择,因为“大量随机I / O”访问模式会破坏关系数据库的性能。)

  • 当用户登录时,通过获取每个第1级连接的第1级连接来缓存其第2级连接,并粘贴在哈希表中(键=第2级ID,值=第1级数组连接您的水平连接)。此外,还缓存您的第一级连接,因此您可以通过一次调用远程缓存服务器来拉回第一级和第二级。用户ID易于分区,因此像memcached这样的分布式缓存可能会很好地工作。

  • 对于任何用户ID,要查找它是否在您的“网络”中以及与您的关系(第一,第二,第三),请执行以下操作:

    1. 如果该ID在您的第一级连接中,请停止。
    2. 尝试在您缓存的第二级连接哈希表中查找ID。如果找到,则返回连接您的连接数组。
    3. 获取ID的第一级连接,并对每个连接重复步骤2。将所有结果汇总到单个数组中并返回它们。
    4. 重构为批处理实现(“从我到N个不同用户的查找距离”),因此您可以从步骤3获得所有远程结果,而不必进行N个远程调用。 [ EDIT>
但是我敢肯定,对此有更好的答案。你的是啥呢?如果您需要其他挑战,请尝试模拟整体浏览情况(无法在Web上查找解决方案)。

[请注意,问题是关于最佳解决方案的,与how LinkedIn actually does it today无关,我在上面写下自己的答案后便查询了该问题。

caching graph linkedin social-networking
6个回答
5
投票
您也许可以利用有关small world networks的公理来优化这种遍历。小世界网络的特征是“集线器”,它们表示其他节点的非常密集的互连。网络中的大多数节点通常将在几跳之内连接到拓扑附近的节点(相距1-4跳),或者将路由通过一个或多个此类集线器。这是小型世界网络以其行为方式行事的主要原因之一。

4
投票
在即席查询或数据模型维护方面效率不高,因此随着关系数据模型的兴起而失宠。

1
投票
鉴于它最终将在所有地方使用,并且该空间相对便宜的事实……我建议根据您的语言偏好使用Lucene(或Lucene.NET)创建索引。您可以通过这种方式做几件事情。

您可以创建树型数据结构,然后递归爬网索引,以根据当时的需要查找所有父节点或子节点及其父节点或子节点。

或者您可以在创建所有关系时写出它们(空间很便宜)。这将是一次写入的过程(您不会以任何方式经常更新所有过程)。创建或撤消关系时,您将对索引进行更新排队(排队,因为您不想为单个请求打开写入操作...为索引更新添加批处理)。然后,您可以阅读这个非常扁平的结构来获取有问题的ID。

手头的ID(您执行的搜索类型来自于您,然后您可以去数据库获取周围所需的信息。然后缓存您的输出,以进一步最小化非常快速的搜索,数据库查询,数据构建...但是如果它只是来自缓存,则速度更快。

使用Velocity,MemCached或MemCached Win32之类的东西进行Web场的集中式缓存。


1
投票
DECLARE @People table (PersonID int, Name varchar(10)) DECLARE @Network table (PersonID int, NetworkedPersonID int) INSERT INTO @People VALUES (1,'AAA') INSERT INTO @People VALUES (2,'BBB') INSERT INTO @People VALUES (3,'CCC') INSERT INTO @People VALUES (4,'DDD') INSERT INTO @People VALUES (5,'EEE') INSERT INTO @People VALUES (6,'FFF') INSERT INTO @People VALUES (7,'GGG') INSERT INTO @People VALUES (8,'HHH') INSERT INTO @Network VALUES (1,2) INSERT INTO @Network VALUES (1,3) INSERT INTO @Network VALUES (2,5) INSERT INTO @Network VALUES (2,7) INSERT INTO @Network VALUES (4,8) INSERT INTO @Network VALUES (7,8) INSERT INTO @Network VALUES (7,3) INSERT INTO @Network VALUES (8,9) DECLARE @TargetPersonID int SET @TargetPersonID=1 ;WITH NetworkLevels AS ( SELECT NetworkedPersonID,1 AS NetworkLevel FROM @Network WHERE PersonID=@TargetPersonID UNION ALL SELECT n.NetworkedPersonID, l.NetworkLevel+1 FROM @Network n INNER JOIN NetworkLevels l ON n.PersonID=l.NetworkedPersonID WHERE l.NetworkLevel<=2 ) SELECT * FROM NetworkLevels

输出:

NetworkedPersonID NetworkLevel
----------------- ------------
2                 1
3                 1
5                 2
7                 2
8                 3
3                 3

(6 row(s) affected)

1
投票
DistanceCategory(A,B): { 1, 2, 3+}

使用连接是双向的事实。

将一级连接作为排序列表存储在某些KV痛处:

Key: [UserFromId,UserToId]. Value: UserToId

伪代码:

DistanceCategory(A,B)
{
    if ( exists([A,B]) )
        return 1;
    if ( firstCommonElement(getAll([A,B]), getAll([A,B])) != null )
        return 2;
    return 3;
}

复杂度:O(C1 + C2)。 C1,C2-两个用户的连接数。


0
投票
这是我的猜测。请随时指出,这是不切实际的。
© www.soinside.com 2019 - 2024. All rights reserved.