Neo4j【從無到有從有到無】【N5】現實世界中的圖形

目錄

1.爲什麼組織選擇圖數據庫

2.常見用例

2.1.社會的(Social)

2.2.推薦建議(Recommendations)

2.3.地理(Geo)

2.4.主數據管理

2.5.網絡和數據中心管理

2.6.授權和訪問控制(通信)

3.實際例子

3.1.社會建議(專業社交網絡)

3.1.1.Talent.net數據模型

3.1.2.推斷社會關係

3.1.3.尋找有特殊興趣的同事

3.1.4.添加WORKED_WITH關係

3.2.授權和訪問控制

3.2.1.TeleGraph數據模型

3.2.2.查找管理員的所有可訪問資源

3.2.3.確定管理員是否有權訪問資源

3.2.4.尋找一個帳戶的管理員

3.3.地理空間與物流(Geospatial and Logistics)

3.3.1.全球郵政數據模型

3.3.2.路線計算

3.3.3.使用Cypher查找最短的交貨路線

3.3.4.使用遍歷框架實現路線計算

4.摘要


在本章中,我們研究了一些圖數據庫的常見實際用法,並確定了組織選擇使用圖數據庫而不是關係數據庫或其他NOSQL存儲的原因。 本章的大部分內容包括三個深入的用例,以及有關數據模型和查詢的詳細信息。 這些示例均來自真實的生產系統。 但是,名稱已更改,必要時簡化了技並在術細節,以突出關鍵設計要點並隱藏任何意外的複雜性。

1.爲什麼組織選擇圖數據庫

在本書中,我們讚揚了圖形數據模型,其強大功能和靈活性以及其與生俱來的表現力。 在將圖形數據庫應用於實際問題時,由於存在實際的技術和業務限制,組織出於以下原因選擇圖形數據庫:

  • “分鐘到毫秒”的性能
    • 查詢性能和響應能力是許多組織在數據平臺方面最關注的問題。 在線交易系統,尤其是大型Web應用程序,要想獲得成功,必須在毫秒內響應最終用戶。 在關係世界中,隨着應用程序數據集大小的增長,連接的痛苦開始顯現出來,並且性能下降。 使用無索引鄰接,圖形數據庫將複雜的聯接轉換爲快速的圖形遍歷,從而無論數據集的總大小如何,都可以保持毫秒級的性能。
  • 大大加快了開發週期
    • 圖形數據模型減少了困擾軟件開發數十年的阻抗失配,從而減少了在對象模型和表格關係模型之間來回轉換的開發開銷。 更重要的是,圖形模型減少了技術和業務領域之間的阻抗失配。 主題專家,架構師和開發人員可以使用共享模型來討論和描繪核心領域,然後將該模型集成到應用程序本身中。
  • 極端的業務響應能力
    • 成功的應用程序很少會停滯不前。 業務條件,用戶行爲以及技術和運營基礎架構的變化推動了新的需求。 過去,這要求組織進行仔細而冗長的數據遷移,其中涉及修改架構,轉換數據以及維護冗餘數據以服務新舊功能。 圖形數據庫的無模式性質以及以多種不同方式同時關聯數據元素的能力,使圖形數據庫解決方案隨着業務的發展而發展,從而降低了風險並縮短了產品上市時間。
  • 企業準備就緒
    • 當用於關鍵業務應用程序中時,數據技術必須是健壯的,可伸縮的,並且通常是事務性的。 儘管某些圖形數據庫是相當新的並且尚未完全成熟,但是市場上有一些圖形數據庫可以提供所有的功能,ACID(原子,一致,隔離,持久)事務性,高可用性,水平讀取可伸縮性以及 數十億的實體,當今大型企業以及先前討論的性能和靈活性特徵都需要。 這是導致組織採用圖形數據庫的重要因素,不僅以適度的離線或部門能力,而且以能夠真正改變業務的方式。

2.常見用例

在本節中,我們描述一些最常見的圖形數據庫用例,確定如何將圖形模型和圖形數據庫的特定特徵應用於產生競爭見解和重大業務價值。

2.1.社會的(Social)

我們纔剛剛開始發現社交數據的力量。 社會科學家尼古拉斯·克里斯塔基斯(Nicholas Christakis)和詹姆斯·福勒(James Fowler)在他們的《Connected》一書中展示了我們如何通過了解一個人的關係來預測一個人的行爲。

社交應用程序使組織可以通過利用人與人之間的聯繫信息來獲得競爭和運營優勢。 通過組合有關個人及其關係的離散信息,組織能夠促進協作,管理信息和預測行爲。

正如Facebook使用“social graph”一詞所暗示的那樣,圖數據模型和圖數據庫很自然地適合於這種以關係爲中心的領域。 社交網絡可幫助我們識別人員,羣體以及與之交互的事物之間的直接和間接關係,從而使用戶能夠對彼此以及他們關心的事物進行評分,查看和發現。 通過了解誰與誰互動,人們如何建立聯繫以及一個團隊中的哪些代表可能根據該團隊的總體行爲來做或選擇,我們對影響個人行爲的看不見的力量產生了巨大的洞察力。 我們將在“圖論與預測模型”中更詳細地討論預測模型及其在社交網絡分析中的作用。

社會關係可以是顯性的也可以是隱性的。 顯式關係發生在社交對象自願建立直接鏈接的任何地方,例如Facebook上的某人,或表明某人是當前或以前的同事,例如LinkedIn上的人。 隱式關係來自通過中介間接連接兩個或多個主題的其他關係。 我們可以根據對象的意見,喜歡,購買甚至他們從事的產品來關聯對象。 這種間接關係使您可以應用各種提示和推論。 我們可以說,A可能基於一些常見的中介而已知,例如與B關聯或以其他方式與B關聯。這樣,我們就進入了社交網絡分析中推薦引擎的領域。

2.2.推薦建議(Recommendations)

有效的建議是通過應用推理能力或暗示能力來產生最終用戶價值的主要示例。 業務線應用程序通常採用演繹和精確的算法(計算工資單,徵稅等)來產生最終用戶價值,而推薦算法則是歸納性和暗示性的,可以識別個人或團體所使用的人員,產品或服務 可能對此感興趣。

推薦算法可建立人與物之間的關係:其他人,產品,服務,媒體內容,無論與推薦所採用的領域有關。關係是根據用戶在購買,生產,消費,評價或審查所涉及資源時的行爲建立的。然後,推薦引擎可以識別特定個人或組感興趣的資源,或可能對特定資源感興趣的個人和組。通過第一種方法,確定特定用戶感興趣的資源,可以將相關用戶的行爲(她的購買行爲,表達的偏好以及在評分和評論中表示的態度)與其他用戶的行爲相關聯,以識別相似的用戶用戶及其之後的事物。第二種方法是識別特定資源的用戶和組,重點是所討論資源的特徵。然後,引擎識別相似的資源,以及與這些資源相關聯的用戶。

就像在社會用例中一樣,做出有效的建議取決於對事物之間的聯繫以及這些聯繫的質量和強度的理解,所有這些最好用屬性圖表示。 查詢主要是局部的圖,因爲它們以一個或多個可識別的主題(無論是人員還是資源)開始,然後發現圖的周圍部分。

總之,社交網絡和推薦引擎在零售,招聘,情感分析,搜索和知識管理領域提供了關鍵的差異化功能。 圖形非常適合與這些區域緊密相關的緊密連接的數據結構。 使用圖形數據庫存儲和查詢此數據,使應用程序可以顯示最終用戶的實時結果,這些結果反映了數據的最新更改,而不是預先計算的過時結果。

2.3.地理(Geo)

地理空間是原始圖形用例。 歐拉(Euler)通過提出一個數學定理解決了柯尼斯堡七橋(Königsberg)問題,該定理後來成爲圖論的基礎。 圖數據庫的地理空間應用範圍從計算抽象網絡(例如公路或鐵路網絡,空域網絡或物流網絡)中的位置之間的路線(如本章後面的物流示例所示)到空間操作(例如查找所有點) 對邊界區域感興趣,找到區域的中心,然後計算兩個或多個區域之間的交點。

地理空間操作取決於特定的數據結構,從簡單的加權關係和定向關係到空間索引(例如R-Trees),R-Trees使用樹形數據結構表示多維屬性。 作爲索引,這些數據結構自然採用圖的形式,通常爲分層形式,因此非常適合圖數據庫。 由於圖形數據庫的無模式性質,地理空間數據可以與其他類型的數據(例如,社交網絡數據)一起駐留在數據庫中,從而允許跨多個域進行復雜的多維查詢。

圖數據庫的地理空間應用在電信,物流,旅行,時間表和路線規劃領域特別重要。

2.4.主數據管理

主數據是對業務運營至關重要的數據,但它本身是非事務性的。 主數據包括有關用戶,客戶,產品,供應商,部門,地理位置,站點,成本中心和業務部門的數據。 在大型組織中,此數據通常保存在許多不同的地方,具有許多重疊和冗餘,以幾種不同的格式,並且具有不同程度的質量和訪問方式。 主數據管理(MDM)是識別,清理,存儲,最重要的是管理此數據的實踐。 它的主要關注點包括隨着組織結構的變化,業務合併和業務規則的變化來管理隨時間的變化。 納入新的數據來源; 用外部來源的數據補充現有數據; 解決報告,法規遵從和商業智能消費者的需求; 和版本控制數據,因爲其值和化學變化。

圖形數據庫不一定提供完整的MDM解決方案。 但是,它們理想地應用於層次結構,主數據元數據和主數據模型的建模,存儲和查詢。 這樣的模型包括類型定義,約束,實體之間的關係以及模型與基礎源系統之間的映射。 圖數據庫的結構化但無模式的數據模型提供了臨時,可變和特殊的結構(在存在多個冗餘數據源時通常會出現模式異常),同時允許快速發展主數據模型。 符合不斷變化的業務需求。

2.5.網絡和數據中心管理

在第3章中,我們研究了一個簡單的數據中心域模型,展示瞭如何使用圖形輕鬆地對數據中心內部的物理和虛擬資產進行建模。 通信網絡是圖結構。 因此,圖形數據庫非常適合建模,存儲和查詢此類領域數據。 大型通信網絡的網絡管理與數據中心管理之間的區別在很大程度上取決於您在防火牆的哪一側工作。 出於所有意圖和目的,這兩件事是一回事。

網絡的圖形表示使我們能夠對資產進行分類,可視化資產的部署方式以及識別資產之間的依賴關係。 圖表的連接結構,以及諸如Cypher之類的查詢語言,使我們能夠進行復雜的影響分析,回答以下問題:

  • 重要客戶依賴於網絡的哪些部分(哪些應用程序,服務,虛擬機,物理機,數據中心,路由器,交換機和光纖)? (自上而下的分析)
  • 相反,如果特定的網絡元素(例如路由器或交換機)發生故障,則網絡中的哪些應用程序和服務以及最終的客戶將受到影響? (自下而上的分析)
  • 對於最重要的客戶,整個網絡是否存在冗餘?

圖形數據庫解決方案是對現有網絡管理和分析工具的補充。 與主數據管理一樣,它們可以用於收集來自不同庫存系統的數據,從最小的網絡元素一直到應用程序和服務以及使用它們的客戶,提供網絡及其使用者的單一視圖。 網絡的圖形數據庫表示也可以用於基於事件相關性豐富操作智能。 每當事件相關引擎(例如,複雜事件處理器)從低級網絡事件流中推斷出複雜事件時,它都可以使用圖模型評估該事件的影響,然後觸發任何必要的補償或緩解措施 。

如今,圖數據庫已成功應用於電信,網絡管理和分析,雲平臺管理,數據中心和IT資產管理以及網絡影響分析等領域,從而減少了從幾天到幾小時的影響分析和問題解決時間 到分鐘和幾秒鐘。 在這裏,性能,面對不斷變化的網絡架構的靈活性以及與域的匹配都是重要的因素。

2.6.授權和訪問控制(通信)

授權和訪問控制解決方案存儲有關各方(例如管理員,組織單位,最終用戶)和資源(例如文件,共享,網絡設備,產品,服務,協議)的信息以及管理對這些資源的訪問的規則。 然後,他們應用這些規則來確定誰可以訪問或操縱資源。 傳統上,訪問控制是使用目錄服務或通過在應用程序後端內部構建自定義解決方案來實現的。 但是,分層目錄結構無法應對錶徵多方分佈式供應鏈的非分層組織和資源依賴性結構。 手動解決方案,尤其是在關係數據庫上開發的解決方案,隨着數據集規模的增長,緩慢且無響應,最終帶來差勁的最終用戶體驗而遭受連接痛苦。

圖形數據庫可以存儲複雜,密集連接的訪問控制結構,這些結構跨越數十億方和資源。 它的結構化但無模式的數據模型支持層次結構和非層次結構,而其可擴展的屬性模型則可以捕獲有關係統中每個元素的豐富元數據。查詢引擎每秒可以遍歷數百萬個關係,可以進行大範圍的訪問查詢, 複雜的結構以毫秒爲單位執行。

與網絡管理和分析一樣,圖形數據庫訪問控制解決方案允許自上而下和自下而上的查詢:

  • 特定管理員可以管理哪些資源(公司結構,產品,服務,協議和最終用戶)? (自頂向下)
  • 最終用戶可以訪問哪些資源?
  • 給定特定資源,誰可以修改其訪問設置? (自下而上)

圖形數據庫訪問控制和授權解決方案特別適用於內容管理,聯合授權服務,社交網絡首選項和軟件即服務(SaaS)產品領域,在這些領域中,與手動滾動相比,它們可以實現幾分鐘到幾毫秒的性能提升, 關係的前輩。

3.實際例子

在本節中,我們詳細描述了三個示例用例:社交和建議,授權和訪問控制以及後勤。 每個用例都來自圖形數據庫的一個或多個生產應用程序(在這些情況下,尤其是Neo4j)。 公司名稱,上下文,數據模型和查詢已進行了調整,以消除意外的複雜性並突出顯示重要的設計和實現選擇。

3.1.社會建議(專業社交網絡)

Talent.net是一個社交推薦應用程序,使用戶可以發現自己的專業網絡,並識別具有特定技能的其他用戶。 用戶在公司工作,從事項目並具有一種或多種興趣或技能。 根據這些信息,Talent.net可以通過識別共享其興趣的其他訂閱者來描述用戶的專業網絡。 搜索可以限於用戶當前的公司,也可以擴展到涵蓋整個訂戶羣。 Talent.net還可以識別直接或間接與當前用戶相關的具有特定技能的個人。 在尋找當前參與的主題專家時,此類搜索非常有用。

Talent.net展示瞭如何使用圖形數據庫開發強大的推理能力。 儘管許多業務線應用程序都是演繹且精確的(例如,計算稅金或薪水,或平衡借方和貸方),但是當我們將歸納算法應用於數據時,最終用戶價值的新縫隙就打開了。 這就是Talent.net所做的。 根據人們的興趣和技能以及他們的工作經歷,該應用程序可以建議可能的應聘者加入一個人的專業網絡。這些結果並不像工資計算必須精確那樣精確,但是無疑仍然有用。

Talent.net推斷人與人之間的聯繫。 與此相反,LinkedIn則是用戶明確聲明他們認識或曾與某人合作的地方。 這並不是說LinkedIn是一種精確的社交網絡功能,因爲它也應用歸納算法來產生更多的見解。 但是通過Talent.net,甚至可以推斷出主要的領帶 (A)-[:KNOWS]->(B),而不是自願的。

Talent.net的第一個版本取決於用戶提供了有關他們的興趣,技能和工作經歷的信息,以便可以推斷出他們的專業社會關係。但是,有了核心推理功能,該平臺將能夠以更少的最終用戶精力來產生更大的洞察力。例如,可以從一個人的日常工作活動的流程和產品中推斷出技能和興趣。無論是編寫代碼,編寫文檔還是交換電子郵件,用戶都必須與後端系統進行交互。通過截取這些交互,Talent.net可以捕獲表明一個人具有哪些技能以及他們從事的活動的數據。有助於使用戶上下文關聯的其他數據源包括組成員身份和聚會列表。儘管此處介紹的用例並未涵蓋這些高階推理功能,但其實現主要需要應用程序集成和合作夥伴協議,而不需要對所使用的圖形或算法進行任何重大更改。

3.1.1.Talent.net數據模型

爲了幫助描述Talent.net數據模型,我們創建了一個小樣本圖,如圖5-1所示,在本節中將使用該圖來說明主要Talent.net用例背後的Cypher查詢。

此處顯示的樣本圖只有兩家公司,每個公司都有數名員工。一個員工通過WORKS_FOR關係與其僱主聯繫。 每個員工INTERESTED_IN一個或多個主題,並且WORKED_ON一個或多個項目。 有時,來自不同公司的員工從事同一項目。

該結構解決了兩個重要的用例:

  • 給定用戶,可以基於共享的興趣和技能來推斷社會關係,即確定其專業的社交網絡。
  • 給一個用戶,推薦一個與他們一起工作過的人,或者與他們一起工作過的人一起工作的具有特定技能的人。

第一個用例幫助圍繞共同利益建立社區。 第二個幫助確定人員以擔任特定項目角色。

3.1.2.推斷社會關係

Talent.net的圖表可通過查找共享用戶興趣的人來推斷用戶的專業社交網絡。 推薦的強度取決於共同利益的數量。 如果Sarah對Java,圖形和REST感興趣,Ben對圖形和REST感興趣,而Charlie對圖形,汽車和醫學感興趣,那麼Sarah和Ben可能對圖形和REST有共同的興趣,所以他們之間可能存在聯繫 根據莎拉(Sarah)和查理(Charlie)之間在圖表上的共同興趣,他們之間的平局比莎拉和本之間的平局強於莎拉(Sarah)和查理(Charlie)之間的平局(兩個共同的利益對一個)。

圖5-2顯示了代表共享用戶興趣的同事的模式。 主題節點引用查詢的主題(在前面的示例中,這是Sarah)。 可以在索引中查找此節點。 一旦將模式錨定到主題節點,然後在圖形周圍彎曲,就會發現其餘的節點。

此處顯示了實現此查詢的Cypher:

MATCH (subject:User {name:{name}})
MATCH (subject)-[:WORKS_FOR]->(company:Company)<-[:WORKS_FOR]-(person:User),
      (subject)-[:INTERESTED_IN]->(interest)<-[:INTERESTED_IN]-(person:User)
RETURN person.name AS name,
       count(interest) AS score,
       collect(interest.name) AS interests
ORDER BY score DESC

查詢的工作方式如下:

  • 第一個MATCH在標記爲User的節點中找到主題(這裏是Sarah),並將結果分配給主題標識符。
  • 然後,第二個MATCH將此用戶與在同一家公司工作並且共享一個或多個興趣的人員進行匹配。 如果查詢的主題是爲Acme工作的Sarah,那麼在Ben的情況下,MATCH將匹配兩次:Ben爲Acme工作,並且對圖(第一個匹配)和REST(第二個匹配)感興趣。 對於Charlie,它將匹配一次:Charlie在Acme工作,並對圖形感興趣。
  • RETURN創建匹配數據的投影。 對於每個匹配的同事,我們提取他們的名字,計算他們與查詢主題有共同興趣的數目(將該結果別名爲score),然後使用collect創建這些共同興趣的逗號分隔列表。 當一個人有多個匹配項時(如本例中的Ben一樣),將返回的結果計數並收集到其匹配項彙總到一行中。 (實際上,計數和收集都可以彼此獨立地執行此聚合功能。)
  • 最後,我們根據每個同事的得分對結果進行排序,得分最高。

以Sarah爲主題,對我們的示例圖運行此查詢將產生以下結果:

+---------------------------------------+
| name | score | interests |
+---------------------------------------+
| "Ben" | 2 | ["Graphs","REST"] |
| "Charlie" | 1 | ["Graphs"] |
+---------------------------------------+
2 rows

圖5-3顯示了圖的匹配部分以生成這些結果。

請注意,此查詢僅查找與Sarah在同一家公司工作的人。 如果我們想擴展搜索範圍以找到在其他公司工作的人,我們需要對查詢進行一些修改:

MATCH (subject:User {name:{name}})
MATCH (subject)-[:INTERESTED_IN]->(interest:Topic)<-[:INTERESTED_IN]-(person:User),
      (person)-[:WORKS_FOR]->(company:Company)
RETURN person.name AS name,
       company.name AS company,
       count(interest) AS score,
       collect(interest.name) AS interests
ORDER BY score DESC

更改如下:

  • 在MATCH子句中,我們不再要求匹配的人員與查詢的對象在同一家公司工作。 (但是,我們仍然會捕獲與之匹配的人所在的公司,因爲我們想在結果中返回此信息。)
  • 現在,在RETURN子句中,包含每個匹配人員的公司詳細信息。

針對我們的樣本數據運行此查詢將返回以下結果:

+---------------------------------------------------------------+
| name | company | score | interests |
+---------------------------------------------------------------+
| "Arnold" | "Startup, Ltd" | 3 | ["Java","Graphs","REST"] |
| "Ben" | "Acme, Inc" | 2 | ["Graphs","REST"] |
| "Gordon" | "Startup, Ltd" | 1 | ["Graphs"] |
| "Charlie" | "Acme, Inc" | 1 | ["Graphs"] |
+---------------------------------------------------------------+
4 rows

圖5-4顯示了圖的匹配部分以生成這些結果。

儘管Ben和Charlie仍然出現在結果中,但事實證明,爲Startup, Ltd.工作的Arnold與Sarah的共同點最多:三個主題,而Ben的兩個主題和Charlie的三個主題相比。

3.1.3.尋找有特殊興趣的同事

在第二個Talent.net用例中,我們從基於共同利益推斷社會關係,轉而尋找具有特定技能的個人,或者與查詢對象一起工作的人,或者與擁有相關技能的人一起工作的人。 與該主題合作。 通過以這種方式應用圖表,我們可以找到個人來擔任項目角色,這是基於他們與我們信任的人或至少與我們合作過的人的社會聯繫。

所討論的社會紐帶來自於從事同一項目的個人,這與之前的用例形成了鮮明對比,後者是根據共同的利益推斷出社會紐帶的。 如果人們在同一個項目上工作,我們就可以推斷出社交關係。 然後,這些項目形成將兩個或多個人綁定在一起的中間節點。 換句話說,一個項目是協作的一個實例,它使多個人相互聯繫。 我們以這種方式發現的任何人都可以將其包括在我們的結果中,只要他們具備我們所尋找的興趣或技能即可。

這是一個Cypher查詢,可查找對一個或多個特定興趣感興趣的同事和同事:

MATCH (subject:User {name:{name}})
MATCH p=(subject)-[:WORKED_ON]->(:Project)-[:WORKED_ON*0..2]-(:Project)
        <-[:WORKED_ON]-(person:User)-[:INTERESTED_IN]->(interest:Topic)
WHERE person<>subject AND interest.name IN {interests}
WITH person, interest, min(length(p)) as pathLength
ORDER BY interest.name
RETURN person.name AS name,
       count(interest) AS score,
       collect(interest.name) AS interests,
       ((pathLength - 1)/2) AS distance
ORDER BY score DESC
LIMIT {resultLimit}

這是一個非常複雜的查詢。 讓我們分解一下,然後更詳細地看一下每個部分:

  • 第一個MATCH在標記爲User的節點中找到查詢的主題,並將結果分配給主題標識符。.
  • 第二個MATCH查找通過從事同一項目或與從事過該項目的人們在同一項目上從事過工作的人。 對於我們匹配的每個人,我們都會抓住他的興趣。 然後,通過WHERE子句進一步完善此匹配項,該子句將排除與查詢主題匹配的節點,並確保我們僅匹配對我們關心的事情感興趣的人。 對於每個成功的匹配,我們將匹配的整個路徑(即從查詢的主題一直延伸到匹配的人直到他的興趣的路徑)分配給標識符p。 我們稍後將更詳細地討論此MATCH子句。
  • WITH將結果通過管道傳遞到RETURN子句,從而過濾掉多餘的路徑。 此時,結果中存在冗餘路徑,因爲同事和同事之間通常可以通過不同的路徑到達,有些路徑比其他路徑更長。 我們要過濾掉這些較長的路徑。 這正是WITH子句的作用。 WITH子句會產生三元組,該三元組包含一個人,一個興趣以及從查詢主題到該人到他的興趣的路徑長度。 假設任何特定的人/興趣組合可能在結果中出現多次,但路徑長度不同,我們希望通過將這些多行摺疊爲僅包含最短路徑的三元組來彙總這些行,我們使用  min(length(p)) 作爲 pathLength。
  • RETURN創建數據的投影,同時執行更多聚合。 通過WITH子句傳遞給RETURN的數據每人每人包含一個條目。 如果某人符合所提供的兩個興趣,則將有兩個單獨的數據條目。 我們使用count和collect收集這些條目:count爲一個人創建一個總分,collect爲該人創建一個匹配興趣的逗號分隔列表。 作爲結果的一部分,我們還計算匹配的人與查詢主題之間的距離。 爲此,我們採用該人的pathLength減去一個(對於路徑末尾的INTERESTED_IN關係),然後除以2(因爲該人與人之間是通過成對的WORKED_ON關係分開的)。 最後,我們根據得分排序,首先是最高得分,然後根據查詢客戶端提供的結果Limit參數對它們進行限制。

前面查詢中的第二個MATCH子句使用可變長度路徑[:WORKED_ON*0..2]作爲較大模式的一部分,以匹配直接與查詢主題相關的人員以及與從事該主題的人在同一個項目上工作。因爲每個人都通過一對或兩對WORKED_ON關係與查詢的主題隔開,所以Talent.net可以將查詢的這一部分寫爲MATCH p=(subject)-[:WORKED_ON*2..4]-(person)-[:INTERESTED_IN]->(interest) ,其變長路徑介於兩個和四個WORKED_ON關係之間。但是,較長的可變長度路徑可能效率較低。編寫此類查詢時,建議將可變長度路徑限制爲儘可能窄的範圍。爲了提高查詢的性能,Talent.net使用從主題擴展到她的第一個項目的固定長度傳出WORKED_ON關係,以及將匹配的人連接到項目的另一個固定長度WORKED_ON關係,該變量具有較小的變量,兩者之間的長度路徑。

針對示例圖運行此查詢,然後再次將Sarah作爲查詢的主題,如果我們尋找對Java,旅行(travel)或醫學(medicine)感興趣的同事和同事,則會得到以下結果:

+--------------------------------------------------+
| name | score | interests | distance |
+--------------------------------------------------+
| "Arnold" | 2 | ["Java","Travel"] | 2 |
| "Charlie" | 1 | ["Medicine"] | 1 |
+--------------------------------------------------+
2 rows

請注意,結果是按分數而不是距離排序的。 阿諾德(Arnold)擁有三分之二的利益,因此比查理(Charlie)的得分高,後者只有一分,即使他與莎拉(Sarah)的差距爲兩人,而查理(Charlie)直接與莎拉(Sarah)合作。

圖5-5顯示了遍歷並匹配以生成這些結果的圖形部分。

我們花一點時間來詳細瞭解該查詢的執行方式。 圖5-6顯示了查詢執行的三個階段。 (爲清晰起見,我們刪除了標籤,並着重強調了重要的屬性值。)第一階段顯示每條路徑,由MATCH和WHERE子句匹配。 如我們所見,存在一條冗餘路徑:通過下一代平臺直接匹配查理(Charlie),但也可以通過Quantum Leap和Emily間接匹配。 第二階段表示在WITH子句中進行的過濾。 在這裏,我們發出的三元組包括匹配的人,匹配的興趣以及從對象穿過匹配的人到她的興趣的最短路徑的長度。 第三階段代表RETURN子句,其中我們代表每個匹配的人彙總結果,並計算其得分和與主題的距離。

3.1.4.添加WORKED_WITH關係

在Talent.net網站上最常執行的查詢是尋找具有特殊興趣的同事和同事,該網站的成功在很大程度上取決於其性能。 該查詢使用成對的WORKED_ON關係(例如,  ('Sarah')-[:WORKED_ON]->('Next  Gen  Platform')<-[:WORKED_ON]-('Charlie') )可以推斷用戶之間已經進行過合作。儘管性能合理,但效率低下,因爲它需要遍歷兩個顯式關係來推斷單個隱式關係的存在。

爲了消除這種低效率,Talent.net決定預先計算一種新的關係,即WORKED_WITH,從而爲這些性能至關重要的訪問模式提供了快捷方式來豐富該圖。 正如我們在“迭代式和增量式開發”中所討論的,通過在兩個節點之間添加直接關係來優化圖形訪問是很普遍的,否則這些關係只能通過中介進行連接。

就Talent.net域而言,WORKED_WITH是雙向關係。 但是,在圖中,它是使用單向關係實現的。 儘管關係的方向通常可以在其定義中添加有用的語義,但是在這種情況下,方向是沒有意義的。 只要操作WORKED_WITH關係的查詢忽略關係方向,這就不是重要的問題。

圖形數據庫以相同的低成本支持在任一方向上遍歷關係,因此,是否應包括域的對等關係的決定應由域決定。 例如,在鏈接列表中,不一定需要PREVIOUS和NEXT,但是在表示情感的社交網絡中,重要的是要明確表明誰愛誰,而不是相互的。

計算用戶的WORKED_WITH關係並將其添加到圖形中並不困難,而且就資源消耗而言也不是特別昂貴。 但是,它可能會將毫秒數添加到最終用戶交互中,從而以新的項目信息更新用戶的個人資料,因此Talent.net已決定對最終用戶活動異步執行此操作。 每當用戶更改其項目歷史記錄時,Talent.net都會將作業添加到隊列中。 這項工作會重新計算用戶的WORKED_WITH關係。 單個編寫器線程輪詢此隊列,並使用以下Cypher語句執行作業:

MATCH (subject:User {name:{name}})
MATCH (subject)-[:WORKED_ON]->()<-[:WORKED_ON]-(person:User)
WHERE NOT((subject)-[:WORKED_WITH]-(person))
WITH DISTINCT subject, person
CREATE UNIQUE (subject)-[:WORKED_WITH]-(person)
RETURN subject.name AS startName, person.name AS endName

圖5-7顯示了我們的示例圖通過WORKED_WITH關係進行充實後的樣子。

使用豐富的圖形,Talent.net現在使用我們之前看過的查詢的稍微簡單的版本來查找具有特殊興趣的同事和同事同事:

MATCH (subject:User {name:{name}})
MATCH p=(subject)-[:WORKED_WITH*0..1]-(:Person)-[:WORKED_WITH]-(person:User)
        -[:INTERESTED_IN]->(interest:Topic)
WHERE person<>subject AND interest.name IN {interests}
WITH person, interest, min(length(p)) as pathLength
RETURN person.name AS name,
       count(interest) AS score,
       collect(interest.name) AS interests,
       (pathLength - 1) AS distance
ORDER BY score DESC
LIMIT {resultLimit}

3.2.授權和訪問控制

TeleGraph Communications是一家國際通信服務公司,數百萬的家庭和企業用戶訂閱其產品和服務。 幾年來,它爲最大的商業客戶提供了自助服務其帳戶的功能。 使用基於瀏覽器的應用程序,這些客戶組織中的每個組織中的管理員都可以代表其員工添加和刪除服務。 爲了確保用戶和管理員僅看到和更改組織的部分以及他們有權管理的產品和服務,該應用程序採用了複雜的訪問控制系統,該系統爲數以千萬計的產品和產品中的數百萬用戶分配了特權和服務實例。

TeleGraph已決定用圖形數據庫解決方案代替現有的訪問控制系統。 這裏有兩個驅動因素:性能和業務響應能力。

性能問題一直困擾着TeleGraph的自助服務應用幾年。原始系統基於關係數據庫,該數據庫使用遞歸聯接對複雜的組織結構和產品層次結構進行建模,並使用存儲過程來實現訪問控制業務邏輯。 由於數據模型的連接密集型性質,許多最重要的查詢速度慢得令人無法接受。 對於大型公司,生成管理員可以管理的事物的視圖需要花費很多時間。 這會產生非常差的用戶體驗,並阻礙自助服務產品提供的創收機會。

TeleGraph制定了進軍新地區和市場的雄心勃勃的計劃,有效地將其客戶羣提高了一個數量級。 但是,影響原始應用程序的性能問題表明,該應用程序已不再適合當今的需求,更不用說明天的需求了。 相反,圖形數據庫解決方案提供了應對快速變化的市場所必需的性能,可伸縮性和自適應性。

3.2.1.TeleGraph數據模型

圖5-8顯示了TeleGraph數據模型的示例。 (爲清楚起見,標籤僅在每個節點集的頂部顯示一次,而不是附加到每個節點。在真實數據中,所有節點至少具有一個標籤。)

該模型包含兩個層次結構。 在第一個層次結構中,將每個客戶組織內的管理員分配給組。 然後,向這些組授予該組織的組織結構各種權限:

  • ALLOWED_INHERIT  將管理員組連接到組織單位,從而允許該組中的管理員管理組織單位。 此權限由上級組織單位的子級繼承。 我們在第1組和Acme以及Acme的子級Spinoff之間的關係中,在TeleGraph示例數據模型中看到了一個繼承權限的示例。 組1通過LLOWED_INHERIT關係連接到Acme。作爲組1的成員,Ben可以通過此ALLOWED_INHERIT關係管理Acme和Spinoff的員工。
  • ALLOWED_DO_NOT_INHERIT  將管理員組連接到組織單位,該方式允許該組中的管理員管理組織單位,但不能管理其任何子級。 Sarah作爲Group 2的成員,可以管理Acme,但不能管理其子Spinoff,因爲Group 2是通過ALLOWED_DO_NOT_INHERIT關係而不是ALLOWED_INHERIT關係連接到Acme的。
  • DENIED  禁止管理員訪問組織單位。 此權限由上級組織單位的子級繼承。 在TeleGraph圖表中,Liz及其對Big Co,Acquired Ltd,Subsidiary和One-Map Shop的許可最好地說明了這一點。 由於她是第4組成員,並且擁有Big Co的ALLOWED_INHERIT權限,因此Liz可以管理BigCo。 但是,儘管這是可繼承的關係,但Liz無法管理Acquired Ltd或Subsidiary,因爲Liz是其成員的第5組已拒絕訪問Acquired Ltd及其子公司(包括Subsidiary)。 但是,由於授予Liz所屬的最後一個組Group 6的ALLOWED_DO_NOT_INHERIT權限,Liz可以管理One-Map Shop。

DENIED優先於ALLOWED_INHERIT,但從屬於ALLOWED_DO_NOT_INHERIT。 因此,如果管理員通過ALLOWED_DO_NOT_INHERIT和DENIED連接到公司,則以ALLOWED_DO_NOT_INHERIT爲準。

精細的關係或與屬性的關係?

請注意,TeleGraph訪問控制數據模型使用細粒度的關係(ALLOWED_INHERIT,ALLOWED_DO_NOT_INHERIT和DENIED),而不是使用受屬性限定的單個關係類型,例如具有允許和繼承的布爾屬性的PERMISSION。 TeleGraph對這兩種方法進行了性能測試,並確定使用細粒度關係幾乎是使用屬性的兩倍。 有關設計關係的更多詳細信息,請參見第4章。

3.2.2.查找管理員的所有可訪問資源

TeleGraph應用程序使用許多不同的Cypher查詢。 我們在這裏只介紹其中一些。

首先是能夠找到管理員可以訪問的所有資源。 每當現場管理員登錄到系統時,都會向他顯示所有他可以管理的僱員和僱員帳戶的列表。 該列表是根據以下查詢返回的結果生成的:

MATCH (admin:Admin {name:{adminName}})
MATCH paths=(admin)-[:MEMBER_OF]->(:Group)-[:ALLOWED_INHERIT]->(:Company)
        <-[:CHILD_OF*0..3]-(company:Company)<-[:WORKS_FOR]-(employee:Employee)
        -[:HAS_ACCOUNT]->(account:Account)
WHERE NOT ((admin)-[:MEMBER_OF]->(:Group)
            -[:DENIED]->(:Company)<-[:CHILD_OF*0..3]-(company))
RETURN employee.name AS employee, account.name AS account
UNION
MATCH (admin:Admin {name:{adminName}})
MATCH paths=(admin)-[:MEMBER_OF]->(:Group)-[:ALLOWED_DO_NOT_INHERIT]->(:Company)
        <-[:WORKS_FOR]-(employee:Employee)-[:HAS_ACCOUNT]->(account:Account)
RETURN employee.name AS employee, account.name AS account

與本節中將要討論的所有其他查詢一樣,該查詢包含兩個單獨的查詢,並由UNION運算符連接。 UNION運算符之前的查詢將處理由任何DENIED關係限定的ALLOWED_INHERIT關係。 UNION運算符之後的查詢將處理所有ALLOWED_DO_NOT_INHERIT權限。 在我們將要查看的所有訪問控制示例查詢中,都重複使用ALLOWED_INHERIT減去DENIED,然後是ALLOWED_DO_NOT_INHERIT這樣的模式。

這裏的第一個查詢,即UNION運算符之前的查詢,可以細分如下:

  • 第一個MATCH從標記爲Administrator的節點中選擇登錄的管理員,並將結果綁定到admin標識符。
  • MATCH匹配此管理員所屬的所有組,並且通過ALLOWED_INHERIT關係將這些組中的所有母公司匹配。 然後,MATCH使用變長路徑( [:CHILD_OF*0..3])發現這些母公司的子公司,然後發現與所有匹配公司(母公司或子公司)關聯的員工和帳戶。 此時,查詢已匹配通過ALLOWED_INHERIT關係訪問的所有公司,員工和帳戶。
  • WHERE消除了其公司(或母公司)通過DENIED關係與管理員組聯繫的匹配。 該WHERE子句是WHERE,用於消除其公司(或母公司)通過DENIED關係與管理員組關聯的匹配項。 每次匹配都會調用此WHERE子句。 如果管理節點與匹配項所綁定的公司節點之間的任何地方都存在DENIED關係,則該匹配項將被消除。
  • RETURN以員工姓名和帳戶列表的形式創建匹配數據的投影。

緊隨UNION運算符之後的第二個查詢要簡單一些:

  • 第一個MATCH從標記爲Admin istrator的節點中選擇已登錄的管理員,並將結果綁定到admin標識符。
  • 第二個MATCH僅匹配通過ALLOWED_DO_NOT_INHERIT關係直接連接到管理員組的公司(包括員工和帳戶)。

UNION運算符將這兩個查詢的結果連接在一起,從而消除了任何重複項。 請注意,每個查詢中的RETURN子句必須包含結果的相同投影。 換句話說,兩個結果集中的列名必須匹配。

圖5-9在示例TeleGraph圖中顯示了該查詢如何匹配Sarah的所有可訪問資源。 請注意,由於第2組到Skunkworkz之間的拒絕關係,Sarah無法管理Kate和Account 7。

Cypher支持UNION和UNION ALL運算符。 UNION從最終結果集中消除重複的結果,而UNION ALL包括所有重複的結果。

3.2.3.確定管理員是否有權訪問資源

我們剛剛查看的查詢返回了管理員可以管理的員工和帳戶列表。 在Web應用程序中,可以通過其自己的URI訪問這些資源(employee, account)中的每一個。 給定友好的URI(例如http://TeleGraph/accounts/5436),如何阻止某人入侵URI並獲得對帳戶的非法訪問權?

所需要的是一個查詢,它將確定管理員是否有權訪問特定資源。 這是該查詢:

MATCH (admin:Admin {name:{adminName}}),
      (company:Company)-[:WORKS_FOR|HAS_ACCOUNT*1..2]
        -(resource:Resource {name:{resourceName}})
MATCH p=(admin)-[:MEMBER_OF]->(:Group)-[:ALLOWED_INHERIT]->(:Company)
        <-[:CHILD_OF*0..3]-(company)
WHERE NOT ((admin)-[:MEMBER_OF]->(:Group)-[:DENIED]->(:Company)
            <-[:CHILD_OF*0..3]-(company))
RETURN count(p) AS accessCount
UNION
MATCH (admin:Admin {name:{adminName}}),
      (company:Company)-[:WORKS_FOR|HAS_ACCOUNT*1..2]
        -(resource:Resource {name:{resourceName}})
MATCH p=(admin)-[:MEMBER_OF]->()-[:ALLOWED_DO_NOT_INHERIT]->(company)
RETURN count(p) AS accessCount

該查詢通過確定管理員是否有權訪問員工或帳戶所屬的公司來工作。 給定一個僱員或帳戶,我們需要確定與此資源相關聯的公司,然後確定管理員是否有權訪問該公司。

我們如何識別員工或帳戶所屬的公司? 通過將其標記爲Resource(以及Company或Account)。 員工通過WORKS_FOR關係連接到公司資源。 帳戶通過員工與公司關聯。 HAS_ACCOUNT將員工連接到該帳戶。 然後,WORKS_FOR將此員工連接到公司。 換句話說,員工離公司一跳,而帳戶離公司兩跳。

有了一點洞察力,我們可以看到此資源授權檢查與查找所有公司,員工和帳戶的查詢類似,只是有一些小差異:

  • 第一個MATCH查找僱員或帳戶所屬的公司。 它使用Cypher的OR運算符 | ,以匹配深度1或2的WORKS_FOR和HAS_ACCOUNT關係。
  • UNION運算符之前的查詢中的WHERE子句消除了將所討論的公司通過DENIED關係連接到管理員組之一的匹配項。
  • UNION運算符之前和之後的查詢的RETURN子句返回匹配數的計數。 爲了使管理員能夠訪問資源,這兩個accessCount值之一或兩個都必須大於0。

因爲UNION運算符消除了重複的結果,所以此查詢的整個結果集可以包含一個或兩個值。 可以使用Java輕鬆表達用於確定管理員是否有權訪問資源的客戶端算法:

private boolean isAuthorized( Result result )
{
    Iterator<Long> accessCountIterator = result.columnAs( "accessCount" );
    while ( accessCountIterator.hasNext() )
    {
        if (accessCountIterator.next() > 0L)
        {
            return true;
        }
    }
    return false;
}

3.2.4.尋找一個帳戶的管理員

前兩個查詢代表圖形的“自頂向下”視圖。 我們將在這裏討論的最後一個TeleGraph查詢提供了數據的“自下而上”視圖。 給定資源( employee or account),誰可以管理它? 查詢如下:

MATCH (resource:Resource {name:{resourceName}})
MATCH p=(resource)-[:WORKS_FOR|HAS_ACCOUNT*1..2]-(company:Company)
        -[:CHILD_OF*0..3]->()<-[:ALLOWED_INHERIT]-()<-[:MEMBER_OF]-(admin:Admin)
WHERE NOT ((admin)-[:MEMBER_OF]->(:Group)-[:DENIED]->(:Company)
            <-[:CHILD_OF*0..3]-(company))
RETURN admin.name AS admin
UNION
MATCH (resource:Resource {name:{resourceName}})
MATCH p=(resource)-[:WORKS_FOR|HAS_ACCOUNT*1..2]-(company:Company)
        <-[:ALLOWED_DO_NOT_INHERIT]-(:Group)<-[:MEMBER_OF]-(admin:Admin)
RETURN admin.name AS admin

和以前一樣,該查詢由兩個獨立的查詢組成,並由UNION運算符連接在一起,特別需要注意以下子句:

  • 第一個MATCH子句使用一個Resource標籤,該標籤允許它標識僱員(employee)和帳戶(account)。
  • 第二個MATCH子句包含使用 的變長路徑表達式。 操作符,用於指定深度爲一或兩個關係且其關係類型包括WORKS_FOR或HAS_ACCOUNT的路徑。 該表達式適應了以下事實:查詢的主題可以是僱員(employee)或帳戶(account)。

圖5-10顯示了當要求查找帳戶10的管理員時查詢所匹配的圖形部分

3.3.地理空間與物流(Geospatial and Logistics)

全球郵政是一家全球快遞公司,其國內業務每天向數以千萬計的3000萬個地址運送數百萬個包裹。 近年來,由於在線購物的增加,包裹的數量顯着增加。 現在,Amazon和eBay的交付量佔每天由Global Post路由和交付的包裹的一半以上。

隨着包裹數量的持續增長,以及面臨來自其他快遞服務的強大競爭,Global Post已開始一項大型變革計劃,以升級包裹網絡的各個方面,包括建築物,設備,系統和流程。

包裹網絡中最重要且最關鍵的組件之一是路線計算引擎。 每秒有一千到三千個包裹進入網絡。 當包裹進入網絡時,它們會根據目的地進行機械分類。 爲了在此過程中保持穩定的流量,引擎必須在到達分揀設備必須做出選擇的點之前計算包裹的路線,這在包裹進入網絡後僅幾秒鐘就發生了,因此對引擎的時間要求很嚴格。

引擎路線不僅必須以毫秒爲單位進行打包,而且還必須根據特定時間段內安排的路線進行。 包裹路線全年都會發生變化,例如,聖誕節期間卡車,送貨員和貨物的數量比在夏天的多。 因此,引擎必須僅使用在特定時期內可用的那些路線來應用其計算。

除了適應不同的路線和包裹流量水平,新的包裹網絡還必須考慮到重大的變化和發展。 Global Post今天開發的平臺將構成其未來10年或更長時間運營的關鍵業務基礎。 在此期間,該公司預計網絡的大部分(包括設備,房屋和運輸路線)將發生變化,以適應業務環境的變化。 因此,路由計算引擎所基於的數據模型必須允許快速而重要的模式演變。

3.3.1.全球郵政數據模型

圖5-11顯示了Global Post包裹網絡的簡單示例。 該網絡包括包裹中心,這些包裹中心連接到交付基地,每個交付基地都覆蓋多個交付區域。 這些交付區域又細分爲覆蓋許多交付單元的交付段。 大約有25個國家包裹中心和大約200萬個派遞單位(對應於郵政編碼)。

隨着時間的流逝,交貨路線會發生變化。 圖5-12、5-13和5-14顯示了三個不同的交貨期。 對於任何給定的時間段,交付基地與任何特定交付區域或區段之間最多隻有一條路線。 相比之下,全年交付基地和包裹中心之間有多條路線。 因此,對於任何給定的時間點,圖的下部(每個交付基礎之下的各個子圖)都包含簡單的樹狀結構,而圖的上部由交付基礎和包裹中心組成,則相互關聯。

請注意,交貨單位不包括在生產數據中。 這是因爲每個交貨單位始終與相同的交貨段相關聯,而與期間無關。 由於此不變性,可以通過每個傳送段的許多傳送單位來爲其編制索引。 要計算到特定交付單位的路線,系統僅需要實際計算到其關聯交付段的路線,就可以使用交付單位作爲關鍵字從索引中恢復其名稱。 這種優化不僅有助於減小生產圖的大小,而且可以減少計算路線所需的遍歷次數。

生產數據庫包含所有不同交貨期的詳細信息。 如圖5-15所示,存在如此衆多的特定於週期的關係,從而形成了緊密連接的圖。

在生產數據中,節點通過多個關係連接,每個關係都帶有start_date和end_date屬性的時間戳。 關係有兩種類型:CONNECTED_TO(將包裹中心和交付基地連接起來)和DELIVERY_ROUTE(將交付基地連接到交付區域,以及交付區域到交付分段)。 這兩種不同類型的關係有效地將圖形分爲上下兩部分,該策略提供了非常有效的遍歷。 圖5-16顯示了帶有時間戳的CONNECTED_TO關係中的三個關係,這些關係將包裹中心連接到交貨基地。

3.3.2.路線計算

如上一節所述,CONNECTED_TO和DELIVERY_ROUTE關係將圖分爲上下兩部分,上部由複雜連接的包裹中心和交付中心組成,交付基地,交付區域和交付段的下部 -在任何給定時期內-以簡單的樹狀結構。

路線計算涉及在圖表下部找到兩個位置之間的最便宜路線。 起始位置通常是交貨段或交貨區域,而結束位置始終是交貨段。 正如我們前面所討論的,交付部門實際上是交付部門的關鍵。 無論起點和終點位置如何,計算出的路線都必須經過圖形上方至少一個包裹中心。

就遍歷圖形而言,可以將計算分爲三部分。 如圖5-17所示,第1條和第2條腿分別從起點和終點向上運動,每條腿都終止於交貨中心。 由於在任何給定的交付期限內,圖表下部的任意兩個元素之間最多隻有一條路線,因此從一個元素遍歷到下一個元素僅是查找傳入的DELIVERY ROUTE關係,其時間間隔時間戳涵蓋了當前交付時間 。 通過遵循這些關係,一條腿和兩條腿的遍歷可導航到植根於兩個不同交付中心的一對樹結構。 然後,這兩個交付中心形成了第三條腿的起點和終點位置,第三條腿與圖形的上部交叉。

與第1條和第2條腿一樣,對第3條腿的遍歷如圖5-18所示,查找的關係(這次是CONNECTED_TO關係)的時間戳包含當前的交付週期。 但是,即使進行了此時間過濾,在任何給定的時間段內,圖形上部的任意兩個傳遞中心之間也可能存在數條路徑。 因此,第三步遍歷必須對每條路線的成本求和,並選擇最便宜的路線,從而使這是最短的加權路徑計算。

爲了完成計算,我們只需要簡單地添加腿1、3和2的路徑,就可以提供從起點到終點的完整路徑。

3.3.3.使用Cypher查找最短的交貨路線

實現包裹路徑計算引擎的Cypher查詢如下:

MATCH (s:Location {name:{startLocation}}),
      (e:Location {name:{endLocation}})
MATCH upLeg = (s)<-[:DELIVERY_ROUTE*1..2]-(db1)
WHERE all(r in relationships(upLeg)
        WHERE r.start_date <= {intervalStart}
        AND r.end_date >= {intervalEnd})
WITH e, upLeg, db1
MATCH downLeg = (db2)-[:DELIVERY_ROUTE*1..2]->(e)
WHERE all(r in relationships(downLeg)
        WHERE r.start_date <= {intervalStart}
        AND r.end_date >= {intervalEnd})
WITH db1, db2, upLeg, downLeg
MATCH topRoute = (db1)<-[:CONNECTED_TO]-()-[:CONNECTED_TO*1..3]-(db2)
WHERE all(r in relationships(topRoute)
        WHERE r.start_date <= {intervalStart}
        AND r.end_date >= {intervalEnd})
WITH upLeg, downLeg, topRoute,
    reduce(weight=0, r in relationships(topRoute) | weight+r.cost) AS score
    ORDER BY score ASC
    LIMIT 1
RETURN (nodes(upLeg) + tail(nodes(topRoute)) + tail(nodes(downLeg))) AS n

乍一看,這個查詢看起來很複雜。 但是,它由四個更簡單的查詢以及WITH子句組成。 我們將依次查看每個子查詢。

這是第一個子查詢:

MATCH (s:Location {name:{startLocation}}),
      (e:Location {name:{endLocation}})
MATCH upLeg = (s)<-[:DELIVERY_ROUTE*1..2]-(db1)
WHERE all(r in relationships(upLeg)
        WHERE r.start_date <= {intervalStart}
        AND r.end_date >= {intervalEnd})

此查詢計算整個路線的第一段。 它可以細分如下:

  • 第一個MATCH在標記爲Location的節點子集中找到起點和終點,並將它們分別綁定到s和e標識符。
  • 第二個MATCH使用定向的可變長度DELIVERY_ROUTE路徑找到從起始位置s到交貨基地的路線。 然後,此路徑綁定到標識符upLeg。 由於傳遞基準始終是DELIVERY_ROUTE樹的根節點,因此沒有傳入的DELIVERY_ROUTE關係,因此我們可以確信,此可變長度路徑末尾的db1節點代表傳遞基準,而不是其他宗地網絡元素。
  • WHERE在路徑upLeg上應用了其他約束,確保我們僅匹配其start_date和end_date屬性包含所提供的交付期限的DELIVERY_ROUTE關係

第二個子查詢計算路線的第二條腿,該第二條腿包括從終點到到達其DELIVERY_ROUTE樹包括該終點作爲葉節點的傳遞基地的路徑。 此查詢與第一個查詢非常相似:

WITH e, upLeg, db1
MATCH downLeg = (db2)-[:DELIVERY_ROUTE*1..2]->(e)
WHERE all(r in relationships(downLeg)
        WHERE r.start_date <= {intervalStart}
        AND r.end_date >= {intervalEnd})

這裏的WITH子句將第一個子查詢鏈接到第二個子查詢,將結束位置和第一條腿的路徑和傳遞基礎傳遞到第二個子查詢。 第二個子查詢在其MATCH子句中僅使用結束位置e; 提供了其餘部分,以便可以將其通過管道傳遞給後續查詢。

第三個子查詢標識路線第三條腿(即交付基礎db1和db2之間的路線)的所有候選路徑,如下所示:

WITH db1, db2, upLeg, downLeg
MATCH topRoute = (db1)<-[:CONNECTED_TO]-()-[:CONNECTED_TO*1..3]-(db2)
WHERE all(r in relationships(topRoute)
        WHERE r.start_date <= {intervalStart}
        AND r.end_date >= {intervalEnd})

此子查詢細分如下:

  • WITH將此子查詢鏈接到上一個查詢,將傳遞基礎db1和db2以及在第1和2分支中標識的路徑傳遞到當前查詢。
  • MATCH標識第一支腿和第二支腿傳遞基地之間的所有路徑,最大深度爲4,並將它們綁定到topRoute標識符。
  • WHERE將topRoute路徑限制爲那些start_date和end_date屬性包含所提供的交付期限的路徑。

第四個也是最後一個子查詢選擇第三條航路的最短路徑,然後計算總路線:

WITH upLeg, downLeg, topRoute,
     reduce(weight=0, r in relationships(topRoute) | weight+r.cost) AS score
     ORDER BY score ASC
     LIMIT 1
RETURN (nodes(upLeg) + tail(nodes(topRoute)) + tail(nodes(downLeg))) AS n

該子查詢的工作方式如下:

  • WITH用管道將一個或多個三元組(包括upLeg,downLeg和topRoute路徑)傳遞到當前查詢。 與第三個子查詢匹配的每個路徑將有一個三元組,每個路徑以連續的三元組綁定到topRoute(綁定到upLeg和downLeg的路徑將保持不變,因爲第一個和第二個子查詢每個僅匹配一個路徑) )。 每個三元組都有一個分數,該分數綁定到該三元組的topRoute。 該分數是使用Cypher的reduce函數計算的,該函數對每個三元組求和,將當前綁定到topRoute的路徑中的關係的成本屬性相加。 然後按此分數對三元組進行排序,從最低的順序開始,然後將其限制爲排序列表中的第一個三元組。
  • RETURN對路徑upLeg,topRoute和downLeg中的節點求和以產生最終結果。 tail函數將第一個節點放在Route和downLeg頂部的每個路徑中,因爲該節點已經在前面的路徑中了。

3.3.4.使用遍歷框架實現路線計算

路線計算的時間緊迫性對路線計算引擎提出了嚴格的要求。 只要各個查詢延遲足夠低,就始終可以水平擴展以提高吞吐量。 基於Cypher的解決方案速度很快,但是每秒有成千上萬的包裹進入網絡,每毫秒都會影響羣集的佔用空間。 因此,Global Post採用了另一種方法:使用Neo4j的Traversal Framework計算路線。

基於遍歷的路由計算引擎實現必須解決兩個問題:找到最短路徑,並根據時間段對路徑進行過濾。我們將研究如何首先根據時間段來過濾路徑。

遍歷只能遵循在指定的交付期限內有效的關係。 換句話說,隨着遍歷圖的進行,應該僅向其顯示有效期(由其start_date和end_date屬性定義)包含指定的交付期的那些關係。

我們使用PathExpander實現此關係過濾。 給定從遍歷的起始節點到當前所在節點的路徑,PathExpander的expand()方法返回可用於進一步遍歷的關係。 每當框架將另一個節點前進到圖中時,遍歷框架都會調用此方法。 如果需要,客戶端可以爲遍歷提供一些初始狀態,稱爲分支狀態。 expand()方法可以在決定返回哪些關係的過程中使用(甚至更改)提供的分支狀態。 路徑計算器的ValidPathExpander實現使用此分支狀態爲擴展器提供交貨期。

這是ValidPathExpander的代碼:

private static class ValidPathExpander implements PathExpander<Interval>
{
    private final RelationshipType relationshipType;
    private final Direction direction;
    private ValidPathExpander( RelationshipType relationshipType,
                               Direction direction )
    {
        this.relationshipType = relationshipType;
        this.direction = direction;
    }
    @Override
    public Iterable<Relationship> expand( Path path,
                                          BranchState<Interval> deliveryInterval )
    {
        List<Relationship> results = new ArrayList<Relationship>();
        for ( Relationship r : path.endNode()
                .getRelationships( relationshipType, direction ) )
        {
            Interval relationshipInterval = new Interval(
                (Long) r.getProperty( "start_date" ),
                (Long) r.getProperty( "end_date" ) );
            if ( relationshipInterval.contains( deliveryInterval.getState() ) )
            {
                results.add( r );
            }
        }
        return results;
    }
}

ValidPathExpander的構造函數採用兩個參數:RelationshipType和direction。 這使擴展器可以重新用於不同類型的關係。 對於“全局發佈”圖,將使用擴展器過濾CONNECTED_TO和DELIVERY_ROUTE關係。

expander的expand()方法採用從遍歷的起始節點到當前遍歷所在的節點的路徑以及客戶端提供的deliveryInterval分支狀態作爲參數。 每次調用它時,expand()都會迭代當前節點上的相關關係(當前節點由path.endNode()給出)。 然後,對於每種關係,該方法都會將關係的間隔與提供的傳遞間隔進行比較。 如果關係的間隔包含交付間隔,則將關係添加到結果中。

看了ValidPathExpander之後,我們現在可以轉向ParcelRouteCalcu自身。 此類封裝了計算包裹進入網絡的點與最終交付目的地之間的路徑所需的所有邏輯。 它採用了與我們已經研究過的Cypher查詢類似的策略。 也就是說,它以兩個獨立的遍歷從起始節點和結束節點沿圖形向上移動,直到找到每條支路的交貨基礎。 然後,它執行最短的加權路徑搜索,將這兩個傳遞基礎合併在一起。

這是ParcelRouteCalculator類的開始:

public class ParcelRouteCalculator
{
    private static final PathExpander<Interval> DELIVERY_ROUTE_EXPANDER =
        new ValidPathExpander( withName( "DELIVERY_ROUTE" ),
            Direction.INCOMING );
    private static final PathExpander<Interval> CONNECTED_TO_EXPANDER =
        new ValidPathExpander( withName( "CONNECTED_TO" ),
            Direction.BOTH );
    private static final TraversalDescription DELIVERY_BASE_FINDER =
        Traversal.description()
                 .depthFirst()
                 .evaluator( new Evaluator()
    {
        private final RelationshipType DELIVERY_ROUTE =
            withName( "DELIVERY_ROUTE");
        @Override
        public Evaluation evaluate( Path path )
        {
            if ( isDeliveryBase( path ) )
            {
                return Evaluation.INCLUDE_AND_PRUNE;
            }
            return Evaluation.EXCLUDE_AND_CONTINUE;
         }
         private boolean isDeliveryBase( Path path )
         {
            return !path.endNode().hasRelationship(
                DELIVERY_ROUTE, Direction.INCOMING );
         }
    } );
    private static final CostEvaluator<Double> COST_EVALUATOR =
        CommonEvaluators.doubleCostEvaluator( "cost" );
    public static final Label LOCATION = DynamicLabel.label("Location");
    private GraphDatabaseService db;
    public ParcelRouteCalculator( GraphDatabaseService db )
    {
        this.db = db;
    }
    ...
}

在這裏,我們定義了兩個擴展器-一個用於DELIVERY_ROUTE關係,另一個用於CONNECTED_TO關係-以及將找到我們路線兩條邊的遍歷。 只要遇到沒有傳入DELIVERY_ROUTE關係的節點,該遍歷就會終止。 因爲每個交付基礎都位於交付路由樹的根部,所以我們可以推斷出沒有任何傳入DELIVERY_ROUTE關係的節點代表了圖中的交付基礎。

每個路線計算引擎都維護此路線計算器的單個實例。 該實例能夠處理多個請求。 對於每條要計算的路線,客戶端調用計算器的calculateRoute()方法,並傳入起點和終點的名稱以及要計算路線的間隔:

public Iterable<Node> calculateRoute( String start,
                                      String end,
                                      Interval interval )
{
    try ( Transaction tx = db.beginTx() )
    {
        TraversalDescription deliveryBaseFinder =
            createDeliveryBaseFinder( interval );
        Path upLeg = findRouteToDeliveryBase( start, deliveryBaseFinder );
        Path downLeg = findRouteToDeliveryBase( end, deliveryBaseFinder );
        Path topRoute = findRouteBetweenDeliveryBases(
            upLeg.endNode(),
            downLeg.endNode(),
            interval );
        Set<Node> routes = combineRoutes(upLeg, downLeg, topRoute);
        tx.success();
        return routes;
    }
}

computeRoute()首先獲取指定間隔的deliveryBaseFinder,然後將其用於查找兩條支路的路線。 接下來,它會在每條路段頂部的交付基地之間找到路線,這些路線是每條路段路徑中的最後一個節點。 最後,它將這些路線結合起來以產生最終結果。

createDeliveryBaseFinder()幫助器方法創建一個使用提供的時間間隔配置的遍歷描述:

private TraversalDescription createDeliveryBaseFinder( Interval interval )
{
    return DELIVERY_BASE_FINDER.expand( DELIVERY_ROUTE_EXPANDER,
    new InitialBranchState.State<>( interval, interval ) );
}

該遍歷描述是通過使用DELIVERY_ROUTE_EXPANDER擴展ParcelRouteCalculator的靜態DELIVERY_BASE_FINDER遍歷描述而構建的。此時,擴展器的分支狀態將以客戶端提供的間隔進行初始化。 這使我們能夠對多個請求使用相同的基本遍歷描述實例(DELIVERY_BASE_FINDER)。 該基本描述針對每個請求進行了擴展和參數化。

正確配置了一個間隔,然後將遍歷描述提供給findRouteToDeliveryBase(),後者在位置索引中查找起始節點,然後執行遍歷:

private Path findRouteToDeliveryBase( String startPosition,
                                      TraversalDescription deliveryBaseFinder )
{
    Node startNode = IteratorUtil.single(
    db.findNodesByLabelAndProperty(LOCATION, "name", startPosition));
    return deliveryBaseFinder.traverse( startNode ).iterator().next();
}

那是兩條腿。 計算的最後一部分要求我們在每條支腿的頂部找到交貨基地之間的最短路徑。 計算Route()獲取每條路徑的最後一個節點,並將這兩個節點以及客戶端提供的間隔提供給findRouteBetweenDeliveryBases()。這是findRouteBetweenDeliveryBases()的實現。

private Path findRouteBetweenDeliveryBases( Node deliveryBase1,
                                            Node deliveryBase2,
                                            Interval interval )
{
    PathFinder<WeightedPath> routeBetweenDeliveryBasesFinder =
    GraphAlgoFactory.dijkstra(
        CONNECTED_TO_EXPANDER,
        new InitialBranchState.State<>( interval, interval ),
    COST_EVALUATOR );
    return routeBetweenDeliveryBasesFinder
        .findSinglePath( deliveryBase1, deliveryBase2 );
}

該方法不是使用遍歷描述來查找兩個節點之間的最短路徑,而是使用Neo4j的圖算法庫中的最短加權路徑算法-在這種情況下,我們使用Dijkstra算法(請參閱“使用Dijkstra的算法進行路徑查找” (有關Dijkstra算法的更多詳細信息)。此算法由ParcelRouteCalculator的靜態CONNECTED_TO_EXPANDER配置,該靜態CONNECTED_TO_EXPANDER依次由客戶端提供的分支狀態間隔初始化。 該算法還配置了一個費用評估程序(另一個靜態成員),該程序可以簡單地識別代表該關係的權重或成本的關係上的屬性。 在Dijkstra路徑查找器上對findSinglePath的調用返回兩個交付基地之間的最短路徑。

那就是辛苦的工作。 剩下的就是加入這些路線以形成最終結果。 這是相對簡單的方法,唯一的缺點是下腿的路徑必須先反轉,然後才能添加到結果中(該腿是從最終目標向上計算的,而應在結果傳遞基礎中向下出現):

private Set<Node> combineRoutes( Path upLeg,
                                 Path downLeg,
                                 Path topRoute )
{
    LinkedHashSet<Node> results = new LinkedHashSet<>();
    results.addAll( IteratorUtil.asCollection( upLeg.nodes() ));
    results.addAll( IteratorUtil.asCollection( topRoute.nodes() ));
    results.addAll( IteratorUtil.asCollection( downLeg.reverseNodes() ));
    return results;
}

4.摘要

在本章中,我們研究了一些實際的圖形數據庫實際用例,並詳細描述了三個案例研究,這些案例研究顯示瞭如何使用圖形數據庫來構建社交網絡,實現訪問控制和管理複雜的物流計算 。

在下一章中,我們將更深入地研究圖形數據庫的內部。 在最後一章中,我們介紹了一些用於處理圖形數據的分析技術和算法。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章