使用Neo4j和Java進行大數據分析 第1部分

幾十年來,關係數據庫一直主導着數據管理,但它們最近已經失去了NoSQL的替代品。雖然NoSQL數據存儲不適合每一個用例,但它們一般更適合html

大數據
,這是處理大量數據的系統的簡寫。四種類型的數據存儲用於大數據:

  • 鍵/值存儲,例如Memcached和Redis
  • 面向文檔的數據庫,如MongoDB,CouchDB和DynamoDB
  • 面向列的數據存儲,如Cassandra和HBase
  • 圖形數據庫,如Neo4j和OrientDB

本文介紹Neo4j,它是用於與java

高度相關的數據
進行交互的圖形數據庫。雖然關係數據庫擅長管理數據
之間的
關係,但圖形數據庫更擅長管理
n維關係的數據
。例如,在社交網絡中,您要分析涉及朋友,朋友的朋友等模式。一個圖形數據庫能夠很容易地回答一個問題,「給定五個分離度,個人社交網絡中未看過的流行的五部電影是什麼?」 這些問題在推薦軟件中很常見,圖形數據庫很是適合解決它們。此外,圖形數據庫擅長表示分層數據,例如訪問控制,產品目錄,電影數據庫,甚至網絡拓撲和組織結構圖。當您擁有具備多個關係的對象時,您會很快發現圖形數據庫提供了一種優雅的,面向對象的範例來管理這些對象。

圖數據庫的狀況

顧名思義,圖形數據庫擅長表示數據圖形。這對社交軟件特別有用,每次與某人聯繫時,大家之間就會創建關係。可能在你上次求職時,你選擇了一些你感興趣的公司,而後搜索你的社交網絡以獲取與他們的聯繫。雖然你可能不知道有那些人爲這些公司工做,但你的社交網絡中的某些人可能會這樣作。很容易在一個或兩個分離度(你的朋友或朋友的朋友)內解決這樣的問題,但當你開始在網絡中擴展搜索時會發生什麼?node

在他們的書中,Neo4j In Action,Aleksa Vukotic和Nicki Watt探討了關係數據庫和圖形數據庫之間的差別,以解決社交網絡問題。爲了向你展現爲何圖形數據庫正成爲關係數據庫日益流行的替代方案,我將在接下來的幾個示例中使用它們的工做。docker

建模複雜的關係:Neo4j與MySQL

從計算機科學的角度來看,當咱們考慮在社交網絡中建模用戶之間的關係時,咱們可能會繪製如圖1所示的圖形。數據庫



用戶與其餘用戶有IS_FRIEND_OF關係,這些用戶與其餘用戶也有IS_FRIEND_OF關係,等等。圖2顯示了咱們如何在關係數據庫中表示這一點。編程

USER表與USER_FRIEND表具備一對多的關係,USER_FRIEND表模擬兩個用戶之間的「朋友」關係。如今咱們已經創建了關係模型,咱們將如何查詢數據?Vukotic和Watt測量了查詢性能,用於計算出五個級別深度的不一樣朋友的數量(朋友的朋友的朋友的朋友)。在關係數據庫中,查詢看起來以下:瀏覽器

# Depth 1
select count(distinct uf.*) from user_friend uf where uf.user_1 = ?

# Depth 2
select count(distinct uf2.*) from user_friend uf1
  inner join user_friend uf2 on uf1.user_1 = uf2.user_2
  where uf1.user_1 = ?

# Depth 3
select count(distinct uf3.*) from t_user_friend uf1
  inner join t_user_friend uf2 on uf1.user_1 = uf2.user_2
  inner join t_user_friend uf3 on uf2.user_1 = uf3.user_2
  where uf1.user_1 = ?

# And so on...
複製代碼

這些查詢的有趣之處在於,每次咱們再出一個級別時,咱們都須要本身加入USER_FRIEND表格。表1顯示了研究人員Vukotic和Watt在插入1,000個用戶時發現了什麼,每一個用戶大約有50個關係(50,000個關係)並運行查詢。bash

表1.各類關係深度的MySQL查詢響應時間

深度執行時間(秒)計數結果網絡

2 0.028〜900架構

3 0.213〜999

4 10.273〜999

5 92.613〜999

MySQL能夠很好地將數據鏈接到三個級別,但以後性能會迅速降低。緣由是每次USER_FRIEND表與自身鏈接時,MySQL必須計算表的笛卡爾積,即便大部分數據將被丟棄。例如,當執行該鏈接五次時,笛卡爾積產生50,000 ^ 5行,或102.4 * 10 ^ 21行。當咱們只對其中的1000個感興趣時,這是一種浪費!

接下來,Vukotic和Watt嘗試對Neo4j執行相同類型的查詢。這些徹底不一樣的結果如表2所示。

表2.各類關係深度的Neo4j響應時間

深度執行時間(秒)計數結果

2 0.04〜900

3 0.06〜999

4 0.07〜999

5 0.07〜999

從這些執行比較中得出的結論

並非

Neo4j比MySQL更好。相反,當遍歷這些類型的關係時,Neo4j的性能取決於檢索的記錄數,而MySQL的性能取決於USER_FRIEND表中的記錄數。所以,隨着關係數量的增長,MySQL查詢的響應時間也會增長,而Neo4j查詢的響應時間將保持不變。這是由於Neo4j的響應時間取決於特定查詢的關係數,而不取決於關係總數。

擴展Neo4j以獲取大數據

將這個思想項目進一步擴展,Vukotic和Watt接下來建立了一百萬用戶,他們之間有5000萬個關係。表3顯示了該數據集的結果。

表3.對於5000萬關係的Neo4j響應時間

深度執行時間(秒)計數結果

2 0.01〜2500

3 0.168〜11萬

4 1.359〜60萬

5 2.132〜80萬

毋庸置疑,我很是感謝Aleksa Vukotic和Nicki Watt,並強烈建議您查看他們的做品。我從本書的第一章

Neo4j in Action中

提取了本節中的全部測試。

Neo4j入門

您已經看到Neo4j可以很是快速地執行大量高度相關的數據,毫無疑問,它比MySQL(或任何關係數據庫)更適合某些類型的問題。若是您想了解有關Neo4j如何工做的更多信息,最簡單的方法是經過Web控制檯與其進行交互。

首先下載Neo4j。對於本文,您將須要Community Edition,在撰寫本文時版本爲3.2.3。

  • 在Mac上,下載DMG文件並像安裝任何其餘應用程序同樣進行安裝。
  • 在Windows上,要麼下載EXE並瀏覽安裝嚮導,要麼下載ZIP文件並在硬盤驅動器上解壓縮。
  • 在Linux上,下載TAR文件並在硬盤驅動器上解壓縮。
  • 或者,在任何操做系統上使用Docker鏡像

安裝Neo4j後,啓動它並打開瀏覽器窗口到如下URL:

http://127.0.0.1:7474/browser/複製代碼

使用默認用戶名neo4j和默認密碼登陸neo4j。您應該看到相似於圖3的屏幕。


Neo4j中的節點和關係

Neo4j是圍繞節點和關係的概念設計的:

  • 一個
    節點
    表明一個東西,好比一個用戶,電影,或者一本書。
  • 節點包含一組
    鍵/值對
    ,例如名稱,標題或發佈者。
  • 節點的
    標籤
    定義了它的類型 - 用戶,電影或書籍。
  • 關係
    定義節點之間的關聯,而且是特定類型。

舉個例子,咱們能夠定義像鋼鐵俠和美國隊長這樣的角色節點; 定義一個名爲「復仇者」的電影節點; 而後定義APPEARS_IN爲鋼鐵俠和復仇者之間以及美國隊長和復仇者之間的關係。全部這些都顯示在圖4中。

圖4顯示了三個節點(兩個Character節點和一個Movie節點)和兩個關係(兩種類型APPEARS_IN)。

建模和查詢節點和關係

與關係數據庫如何使用結構化查詢語言(SQL)與數據交互相似,Neo4j使用Cypher查詢語言與節點和關係進行交互。

讓咱們使用Cypher建立一個簡單的家庭表示。在Web界面的頂部,查找美圓符號。這表示容許您直接對Neo4j執行Cypher查詢的字段。在該字段中輸入如下Cypher查詢(我以個人家人爲例,但若是您願意,能夠隨意更改細節以建模您本身的家庭):

CREATE (person:Person {name: "Steven", age: 45}) RETURN person複製代碼

結果如圖5所示。

在圖5中,您能夠看到一個標記爲Person且名稱爲Steven的新節點。若是將鼠標懸停在Web控制檯中的節點上,您將在底部看到其屬性。在這種狀況下,屬性是ID:19,名稱:Steven,年齡:45。如今讓咱們分解Cypher查詢:

  • CREATE:該CREATE關鍵字用於建立節點和關係。在這種狀況下,咱們傳遞一個參數,它Person括在括號中,所以它意味着建立一個單獨的節點。
  • (person:Person {...}):小寫「 person」是一個變量名稱,經過它咱們能夠訪問正在建立的人,而大寫「 Person」是標籤。請注意,冒號將變量名稱與標籤分開。
  • {name:「Steven,年齡:45}:這些是咱們爲咱們正在建立的節點定義的鍵/值屬性.Neo4j不要求您在建立節點以前定義架構,而且每一個節點均可以具備惟一性元素集。(大多數狀況下,您使用相同的標籤訂義具備相同屬性的節點,但這不是必需的。)
  • 返回人:建立節點後,咱們要求Neo4j將其返回給咱們。這就是咱們看到節點出如今用戶界面中的緣由。

CREATE命令(不區分大小寫)用於建立節點,能夠按以下方式讀取:

使用包含名稱和年齡屬性的Person標籤建立一個新節點; 將其分配給person變量並將其返回給調用者

查詢Cypher查詢語言

接下來咱們想嘗試一下Cypher的查詢。首先,咱們須要建立更多人,以便咱們能夠定義它們之間的關係。

CREATE (person:Person {name: "Michael", age: 16}) RETURN person
    CREATE (person:Person {name: "Rebecca", age: 7}) RETURN person
    CREATE (person:Person {name: "Linda"}) RETURN person
複製代碼

建立四我的後,您能夠單擊節點標籤下的「 人員」按鈕(若是單擊網頁左上角的數據庫圖標,則可見)或執行如下Cypher查詢:

MATCH (person: Person) RETURN person複製代碼

Cypher使用MATCH關鍵字在Neo4j中查找內容。在此示例中,咱們要求Cypher匹配全部標記爲Person的節點,將這些節點分配給

person

變量,並返回與該變量關聯的值。所以,你應該看到您建立的四個節點。若是將鼠標懸停在Web控制檯中的每一個節點上,你將看到每一個人的屬性。(你可能會注意到我將我妻子的年齡排除在她的節點以外,說明屬性不須要在節點之間保持一致,即便是相同的標籤。我也不會愚蠢地公佈我妻子的年齡。)

咱們能夠經過MATCH向咱們想要返回的節點添加條件來進一步擴展此示例。例如,若是咱們只想要「Steven」節點,咱們能夠經過匹配name屬性來檢索它:

MATCH (person: Person {name: "Steven"}) RETURN person複製代碼

或者,若是咱們想要歸還全部孩子,咱們能夠要求全部18歲如下的人:

MATCH (person: Person) WHERE person.age < 18 RETURN person複製代碼

在此示例中,咱們WHERE在查詢中添加了子句以縮小結果範圍。WHERE與其SQL等價物很是類似:MATCH (person: Person)查找具備Person標籤的全部節點,而後該WHERE子句過濾結果集中的值。

關係中的建模方向

咱們有四個節點,因此讓咱們建立一些關係。首先,讓咱們建立史蒂文和琳達之間的IS_MARRIED_TO關係:

MATCH (steven:Person {name: "Steven"}), (linda:Person {name: "Linda"}) CREATE (steven)-[:IS_MARRIED_TO]->(linda) return steven, linda複製代碼

在這個例子中,咱們匹配標記爲Steven和Linda的兩個Person節點,而且咱們建立了一個從Steven到Linda 的IS_MARRIED_TO類型關係。建立關係的格式以下:

(node1)-[relationshipVariable:RELATIONSHIP_TYPE->(node2)複製代碼

relationshipVariable是可選的,但若是您但願可以在RETURN語句(或WHERE子句)中訪問它,則須要它。箭頭()-[]->()表示Cypher要求的關係方向。若是你想表達Linda與Steven結婚,那麼你能夠按照如下方式在另外一個方向寫下這段關係:()<-[]-()。若是你想建立一個雙向關係,代表Linda和Steve彼此結婚,那麼你須要建立兩個獨立的關係。雖然Cypher要求您定義關係的方向,但您可使用方向查詢,也能夠不使用方向查詢。

如下查詢查找此係列中已結婚的全部人(請注意查詢中缺乏任何方向):

MATCH (p1:Person)-[:IS_MARRIED_TO]-(p2:Person) RETURN p1, p2複製代碼

結果如圖6所示。

如今讓咱們建立一些關係:

MATCH (michael:Person {name: "Michael"}), (rebecca:Person {name: "Rebecca"}) CREATE (michael)-[:IS_SIBLILNG]->(rebecca) return michael, rebecca
MATCH (steven:Person {name: "Steven"}), (michael:Person {name: "Michael"}) CREATE (steven)-[:HAS_CHILD]->(michael) return steven, michael
MATCH (steven:Person {name: "Steven"}), (rebecca:Person {name: "Rebecca"}) CREATE (steven)-[:HAS_CHILD]->(rebecca) return steven, rebecca
MATCH (linda:Person {name: "Linda"}), (michael:Person {name: "Michael"}) CREATE (linda)-[:HAS_CHILD]->(michael) return linda, michael
MATCH (linda:Person {name: "Linda"}), (rebecca:Person {name: "Rebecca"}) CREATE (linda)-[:HAS_CHILD]->(rebecca) return linda, rebecca
複製代碼

咱們如今能夠經過如下查詢查看全部人及其關係:

MATCH (p:Person) RETURN p複製代碼

結果如圖7所示。

遍歷社交圖

要真正探索圖數據庫的力量,咱們須要擴展咱們的社交圖。首先,讓咱們添加一些FRIEND關係:

MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(charlie:Person {name: "Charlie", age: 16}) RETURN michael, charlie
        MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(koby:Person {name: "Koby"}) RETURN michael, koby
        MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(grant:Person {name: "Grant"}) RETURN michael, grant
        MATCH (rebecca:Person {name: "Rebecca"}) CREATE (rebecca)-[:FRIEND]->(jordyn:Person {name: "Jordyn"}) RETURN rebecca, jordyn
        MATCH (rebecca:Person {name: "Rebecca"}) CREATE (rebecca)-[:FRIEND]->(katie:Person {name: "Katie"}) RETURN rebecca, katie
    複製代碼

關於這些關係的一些有趣的事情是朋友節點與FRIEND關係同時建立。例如,執行第一個語句時,「Charlie」Person節點不存在,但該語句建立了從現有「Michael」Person節點到名爲「Charlie」的新Person節點的FRIEND關係。您能夠拉出全部Person節點並驗證節點是否已建立,如圖8所示。

咱們已經啓動了一個很是好的社交圖,因此讓咱們嘗試編寫一個更復雜的查詢來查找我孩子的全部朋友:

MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(:Person)-[:FRIEND]-(friend:Person) RETURN friend複製代碼

結果如圖9所示。

在此查詢中,咱們從名爲「Steven」的HAS_CHILDPerson節點開始,遍歷全部與Person節點的FRIEND關係,遍歷全部Person節點的關係,並返回朋友列表。咱們能夠包含方向關係,但省略箭頭可讓咱們遍歷兩個方向。

社交圖中的鍵/值對

除了定義兩個節點之間的關係以外,關係自己能夠具備鍵/值對。例如,咱們可能決定建立Movie節點,而後HAS_SEEN在他們看到的人和電影之間建立關係。在這些HAS_SEEN關係中,咱們還能夠添加「評級」屬性。下面的代碼建立一個標題爲Avengers的電影,而後HAS_SEEN在Michael和電影復仇者之間建立一個關係,評級爲5。

CREATE (movie:Movie {title:"Avengers"}) RETURN movie
        MATCH (michael:Person {name:"Michael"}), (avengers:Movie {title:"Avengers"}) CREATE (michael)-[:HAS_SEEN {rating:5}]->(avengers) return michael, avengers
    複製代碼

圖10顯示告終果。

Java中的圖形分析對於咱們在進入Java代碼以前的最後一個例子,讓咱們嘗試使用圖形分析進行簡單的實驗。咱們會給孩子們的朋友添加一些電影,設置我孩子的性別,而後查詢個人一個孩子(邁克爾)可能想要看的電影。結果如圖11所示。

CREATE (movie:Movie {title:"Batman"}) RETURN movie
        CREATE (movie:Movie {title:"Gone with the Wind"}) RETURN movie
        CREATE (movie:Movie {title:"Spongebob Square Pants"}) RETURN movie
        CREATE (movie:Movie {title:"Avengers 2"}) RETURN movie
        MATCH (charlie:Person {name:"Charlie"}), (movie:Movie {title:"Batman"}) CREATE (charlie)-[:HAS_SEEN {rating:4}]->(movie) return charlie, movie
        MATCH (charlie:Person {name:"Charlie"}), (movie:Movie {title:"Gone with the Wind"}) CREATE (charlie)-[:HAS_SEEN {rating:0}]->(movie) return charlie, movie
        MATCH (koby:Person {name:"Koby"}), (movie:Movie {title:"Batman"}) CREATE (koby)-[:HAS_SEEN {rating:4}]->(movie) return koby, movie
        MATCH (koby:Person {name:"Koby"}), (movie:Movie {title:"Avengers 2"}) CREATE (koby)-[:HAS_SEEN {rating:5}]->(movie) return koby, movie
        MATCH (grant:Person {name:"Grant"}), (movie:Movie {title:"Spongebob Square Pants"}) CREATE (grant)-[:HAS_SEEN {rating:1}]->(movie) return grant, movie
        MATCH (jordyn:Person {name:"Jordyn"}), (movie:Movie {title:"Spongebob Square Pants"}) CREATE (jordyn)-[:HAS_SEEN {rating:5}]->(movie) return jordyn, movie
        MATCH (michael:Person {name: "Michael"}) SET michael.gender = "male" RETURN michael
        MATCH (rebecca:Person {name: "Rebecca"}) SET rebecca.gender = "female" RETURN rebecca
        MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(child:Person)-[:FRIEND]-(friend:Person)-[hasSeen:HAS_SEEN]-(movie:Movie) WHERE child.gender = "male" AND hasSeen.rating > 3 RETURN DISTINCT movie.title
    複製代碼
osjp neo4j fig11

圖11.查詢我孩子的朋友看過而且評分大於3的全部電影的結果

上面的前四個陳述創造了四部電影。接下來的六個陳述創造了個人孩子的朋友和他們看過的電影之間的HAS_SEEN關係,具備不一樣的評級。接下來的兩個語句爲個人孩子添加了一個性別,這是經過按名稱查找Person節點而後調用來完成的SET childName.gender = "male|female"。在Cypher中,該SET語句容許您經過將值設置爲更改現有屬性,添加新屬性或刪除屬性NULL。最後的查詢須要一些工做才能理解。咱們從名爲「Steven」的Person開始,跟隨他與子節點Person關係的HAS_CHILD關係,跟隨那些Person節點到FRIENDPerson節點,經過HAS_SEEN關係跟隨那些朋友Person節點到Movie節點,而後添加一個WHERE檢查二者性別的子句史蒂文的孩子和評級屬性的HAS_SEEN價值。最後,由於有些孩子看過同一部電影(蝙蝠俠),咱們只想要回歸DISTINCT電影片頭。在這種狀況下,咱們不返回電影節點,而是返回電影的標題屬性,這就是輸出顯示在表格中的緣由。對於聰明的觀察者,咱們能夠經過將性別添加到子節點查詢來簡化這一點,以下所示:

MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(child:Person {gender:"male"})-[:FRIEND]-(friend:Person)-[hasSeen:HAS_SEEN]<-(movie:Movie) WHERE hasSeen.rating > 3 RETURN DISTINCT movie.title
複製代碼

第1部分的結論

Cypher是一種考慮編寫查詢的不一樣方式,我鼓勵您閱讀正式文檔以瞭解更多信息。一旦掌握了編寫Cypher查詢的過程,Java編程將是最簡單的部分!咱們將在本簡介的後半部分中對圖形數據和與Neo4j的關係進行選擇。

英文原文:www.javaworld.com/article/325…

更多文章歡迎訪問: http://www.apexyun.com

公衆號:銀河系1號

聯繫郵箱:public@space-explore.com

(未經贊成,請勿轉載)

相關文章
相關標籤/搜索