[英]How do sites like LinkedIn efficiently display 1st/2nd/3rd-level relationship next to each person's name?
我最近糟糕地回答了一個直截了當的問題而在面試中搞砸了:像 LinkedIn 這樣的網站如何有效地顯示從你到頁面上顯示的每個人的關系距離(第一/第二/第三)(例如,在人員搜索結果中,工作人員列表在公司等)?
<編輯>我得到了解決方案的基本“技巧”:找到“與我的距離”是一個常見的操作(例如,單個頁面上 20 倍以上,每個登錄會話 100 次),因此您可以執行“與我的距離”的一部分X”,緩存它,然后多次重用緩存的部分結果,以使其他操作更便宜。 我還猜測部分結果可能是我的二級連接,因為“緩存所有三級連接”在 RAM 和 CPU 中的成本太高。 </編輯>
但是,當試圖將這種洞察力轉化為解決方案時,我想出了一個笨拙的答案,涉及為網站上的每個人創建二級連接的持久緩存(這在性能上會非常昂貴且維護起來很復雜),我接受了以一種幾乎沒有技術意義的方式使用布隆過濾器的莫名繞道。 在得到這樣的答案后,我不會雇用自己!
后來,當我在沒有面試壓力的情況下思考這個問題時,我想出了一個更合理的答案。
構建一種非常快速的方法來獲取每批用戶 ID 的第一級連接(批大小高達 ~1000?)。 這可能意味着大量 RAM 服務器的專用集群,可以將整個網絡的第一級連接緩存在內存中。 幸運的是,50M 成員 x 平均。 每個成員 100 個連接 x 每個成員 ID 4 個字節 = <25GB 以緩存在 RAM 中,這對於價格合理的硬件是可行的。 每天的更改次數將低於 1%,因此保持緩存最新並不難。 (請注意,關系數據庫可能不是實現此緩存的錯誤選擇,因為“大量隨機 I/O”訪問模式會降低關系數據庫的性能。)
當用戶登錄時,通過獲取每個第一級連接的第一級連接來緩存他或她的第二級連接,並粘貼在一個哈希表中(鍵 = 第二級 ID,值 = 連接的第一級連接數組你)。 也緩存你的第一級連接,這樣你就可以通過一次調用回你的遠程緩存服務器來拉回第一級和第二級。 用戶 ID 很容易分區,所以像 memcached 這樣的分布式緩存可以很好地解決這個問題。
對於任何用戶 ID,要查找它是否在您的“網絡”中以及它與您的關系(第一、第二、第三),請執行以下操作:
但我相信對此有更好的答案。 你的是啥呢? 如果您想要額外的挑戰,請嘗試模擬面試情況(無法在網上查找解決方案)。
請注意,這個問題是關於一個最佳解決方案的,不管LinkedIn 今天實際上是如何做的,我在上面寫下自己的答案后查了一下。
您可以利用關於小世界網絡的公理來優化這種類型的遍歷。
小世界網絡的特點是“集線器”,代表其他節點的非常密集的互連。 網絡中的大多數節點通常會在幾跳內連接到拓撲附近的節點(1-4 跳遠),或者將通過一個或多個這樣的集線器進行路由。 這是小世界網絡以它們的方式行事的主要原因之一。
有趣的是,1970 年代的技術可以很好地對此進行建模。 網絡數據庫模型有效地管理這種類型的關系。
它在即席查詢或數據模型維護方面效率不高,因此隨着關系數據模型的興起而失寵。
如果您考慮一下,在 SQL 中執行此操作可能會占用大量處理器。
鑒於這一點以及它最終將在所有地方使用的事實,而且該空間相對便宜......我建議根據您的語言偏好使用 Lucene(或 Lucene.NET)創建索引。 你可以用這種方式做幾件事。
您可以創建一個樹型數據結構,然后根據當時的需要遞歸地爬取索引以查找所有父節點或子節點及其父節點或子節點。
或者您可以在創建所有關系時寫出它們(空間是廉價的概念)。 這將是一次寫入過程(您不會以任何方式經常更新)。 創建或撤銷關系時,您會將更新排入索引(排隊是因為您不想為單個請求打開寫入...批處理索引更新)。 然后你可以閱讀這個非常扁平的結構來獲取有問題的 ID。
有了 ID(從您執行的任何搜索類型),您就可以轉到數據庫以獲取周圍所需的信息。 然后緩存您的輸出,以進一步最小化非常快速的搜索、數據庫查詢、數據構建……但如果它只是來自緩存,則速度仍然更快。
使用 Velocity、MemCached 或 MemCached Win32 之類的東西來實現跨 Web 場的集中緩存。
我不確定表結構或系統的復雜性,但這是一個使用遞歸 CTE 的簡單 SQL Server 示例:
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)
實施
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 - 兩個用戶的連接數。
Linkedin 數據不是表示為一個巨大的巨圖嗎? 當一個人登錄時,系統會處理它的節點,然后通過廣度優先遍歷3個級別,系統會將這些節點作為一個集合(以及哪個級別的信息),當一個人出現在網頁上時,系統會在這個節點集上查找並給出關系距離。
這是我的猜測。 請隨時指出,是什么使它不切實際。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.