图数据库Neo4j全栈Web技术解密
注:作者的代表作品【Neo4j + D3.js 項目視頻教程】http://edu.51cto.com/course/11315.html?reci
Neo4j是什么?
圖1. Neo4j Web控制臺
Neo4j是一個圖形數據庫,這也就意味著它的數據并非保存在表或集合中,而是保存為節點以及節點之間的關系。在Neo4j中,節點以及關系都能夠包含保存值的屬性,此外:
可以為節點設置零或多個標簽(例如Author或Book)
每個關系都對應一種類型(例如WROTE或FRIEND_OF)
關系總是從一個節點指向另一個節點(但可以在不考慮指向性的情況下進行查詢)
為什么要選擇Neo4j?
在考慮為web應用選擇某個數據庫時,我們需要考慮對它有哪些方面的期望,其中最重要的一些條件包括:
它是否易于使用?
它是否允許你方便地回應對需求的變更?
它是否支持高性能查詢?
是否能夠方便地對其進行數據建模?
它是否支持事務?
它是否支持大規模應用?
它是否足夠有趣(很遺憾的是對于數據庫的這方面要求經常被忽略)?
從這幾個方面來說,Neo4j是一個合適的選擇。Neo4j……
自帶一套易于學習的查詢語言(名為Cypher)
不使用schema,因此可以滿足你的任何形式的需求
與關系型數據庫相比,對于高度關聯的數據(圖形數據)的查詢快速要快上許多
它的實體與關系結構非常自然地切合人類的直觀感受
支持兼容ACID的事務操作
提供了一個高可用性模型,以支持大規模數據量的查詢,支持備份、數據局部性以及冗余
提供了一個可視化的查詢控制臺,你不會對它感到厭倦的
什么時候不應使用Neo4j?
作為一個圖形NoSQL數據庫,Neo4j提供了大量的功能,但沒有什么解決方案是完美的。在以下這些用例中,Neo4j就不是非常適合的選擇:
記錄大量基于事件的數據(例如日志條目或傳感器數據)
對大規模分布式數據進行處理,類似于Hadoop
二進制數據存儲
適合于保存在關系型數據庫中的結構化數據
在上面的示例中,你看到了由Author、City、Book和Category以及它們之間的關系所組成的一個圖形。如果你希望通過Cypher語句在Neo4j web控制臺中列出這些數據結果,可以執行以下語句:
MATCH(city:City)<-[:LIVES_IN]-(:Author)-[:WROTE]->(book:Book)-[:HAS_CATEGORY]->(category:Category)WHERE?city.name?=?“Chicago”RETURN?*請注意這種ASCII風格的語法,它在括號內表示節點名稱,并用箭頭表示一個節點指向另一個節點的關系。Cypher通過這種方式允許你匹配某個指定的子圖形模式。
當然,Neo4j的功能不僅僅在于展示漂亮的圖片。如果你希望按照作者所處的地點(城市)計算書籍的分類數目,你可以通過使用相同的MATCH模式,返回一組不同的列,例如:
MATCH(city:City)<-[:LIVES_IN]-(:Author)-[:WROTE]->(book:Book)-[:HAS_CATEGORY]->(category:Category)RETURN?city.name,?category.name,?COUNT(book)執行這條語句將返回以下結果:
?
city.name | category.name | COUNT(category) |
Chicago | Fantasy | 1 |
Chicago | Non-Fiction | 2 |
雖然Neo4j也能夠處理“大數據”,但它畢竟不是Hadoop、HBase或Cassandra,通常來說不會在Neo4j數據庫中直接處理海量數據(以PB為單位)的分析。但如果你樂于提供關于某個實體及其相鄰數據關系(比如你可以提供一個web頁面或某個API返回其結果),那么它是一種良好的選擇。無論是簡單的CRUD訪問,或是復雜的、深度嵌套的資源視圖都能夠勝任。
你應該選擇哪種技術棧以配合Neo4j?
所有主流的編程語言都通過HTTP API的方式支持Neo4j,或者采用基本的HTTP類庫,或是通過某些原生的類庫提供更高層的抽象。此外,由于Neo4j是以Java語言編寫的,因此所有包含JVM接口的語言都能夠充分利用Neo4j中的高性能API。
Neo4j本身也提供了一個“技術棧”,它允許你選擇不同的訪問方式,包括簡單訪問乃至原生性能等等。它提供的特性包括:
通過一個HTTP API執行Cypher查詢,并獲取JSON格式的結果
一種“非托管擴展”機制,允許你為Neo4j數據庫編寫自己的終結點
通過一個高層Java API指定節點與關系的遍歷
通過一個低層的批量加載API處理海量初始數據的獲取
通過一個核心Java API直接訪問節點與關系,以獲得最大的性能
一個應用程序示例
最近我正好有機會將一個項目擴展為基于Neo4j的應用程序。該應用程序(可以訪問graphgist.neo4j.com查看)是關于GraphGist的一個門戶網站。GraphGist是一種通過交互式地渲染(在你的瀏覽器中)生成的文檔,它基于一個簡單的文本文件(AsciiDoctor),其中用文字描述以及圖片描述了整個數據模型、架構以及用例查詢,可以在線執行它們,并使它們保持可視化。它非常類似一個iPython?notebook或是一張交互式的白紙。GraphGist也允許讀者在瀏覽器中編寫自己定義的查詢,以查看整個數據集。
Neo4j的原作者Neo Technology希望為GraphGist提供一個由社區創建的展示項目。當然,后端技術選用了Neo4j,而整個技術棧的其余部分,我的選擇是:
Node.js配合Express.js,其中引入了neo4j包
Angular.js
Swagger UI
所有代碼都已開源,可以在GitHub上任意瀏覽。
從概念上講,GraphGist門戶網站是一個簡單的應用,它提供了一個GraphGist列表,允許用戶查看每個GraphGist的詳細內容。數據領域是由Gist、Keyword/Domain/Use Case(作為Gist分類)以及Person(作為Gist的作者)所組成的:
現在你已經熟悉這個模型了,在繼續深入學習之前,我想為你快速地介紹一下Cypher這門查詢語言。舉例來說,如果我們需要返回所有的Gist和它們的關鍵字,可以通過以下語句實現:
MATCH?(gist:Gist)-[:HAS_KEYWORD]->(keyword:Keyword) RETURN?gist.title,?keyword.name這段語句將返回一張表,其中的每一行是由每個Gist和Keyword的組合構成的,正如同SQL join的行為一樣。現在我們更深入一步,假設我們想要找到某個人所編寫的Gist對應的所有Domain,我們可以執行下面這條查詢語句:
MATCH?(person:Person)-[:WRITER_OF]->(gist:Gist)-[:HAS_DOMAIN]->(domain:Domain) WHERE?person.name?=?“John?Doe” RETURN?domain.name,?COUNT(gist)該語句將返回另一個結果表,其中的每一行包含Domain的名稱,以及這個Person對于這一Domain所編寫的全部Gist的數量。這里無需使用GROUP BY語句,因為當我們使用例如COUNT()這樣的聚合函數時,Neo4j會自動在RETURN語句中對其它列進行分組操作。
現在你對Cypher已經有一點感覺了吧?那么讓我們來看一個來自實際應用中的查詢。在創建這個門戶時,如果能夠通過某種方式,只需對數據庫進行一次請求就能夠返回我們所需的所有數據,并且以一種我們需要的格式進行結構組織,那將十分有用。
讓我們開始創建這個用于門戶的API(可以在GitHub上找到)的查詢吧。首先,我們需要按照Gist的title屬性進行匹配,并匹配所有相關的Gist節點:
//?Match?Gists?based?on?titleMATCH?(gist:Gist)?WHERE?gist.title?=~?{search_query}//?Optionally?match?Gists?with?the?same?keyword//?and?pass?on?these?related?Gists?with?the//?most?common?keywords?firstOPTIONAL?MATCH?(gist)-[:HAS_KEYWORD]->(keyword)<-[:HAS_KEYWORD]-(related_gist)這里有幾個要注意的地方。首先,WHERE語句是通過一個正則表達式(即=~操作符)和一個參數對title屬性進行匹配的。參數(Parameter)是Neo4j的一項特性,它能夠將查詢與其所代表的數據進行分離。使用參數能夠讓Neo4j對查詢和查詢計劃進行緩存,這也意味著你無需擔心遭遇查詢注入***。其次,我們在這里使用了一個OPTIONAL MATCH語句,它表示我們希望始終返回原始的Gist,即使它并沒有相關的Gist。
現在讓我們對之前的查詢進行擴展,將RETURN語句替換為WITH語句:
MATCH?(gist:Gist)?WHERE?gist.title?=~?{search_query}OPTIONAL?MATCH?(gist)-[:HAS_KEYWORD]->(keyword)<-[:HAS_KEYWORD]-(related_gist)WITH?gist,?related_gist,?COUNT(DISTINCT?keyword.name)?AS?keyword_countORDER?BY?keyword_count?DESCRETURNgist,COLLECT(DISTINCT?{related:?{?id:?related_gist.id,?title:? related_gist.title,?poster_p_w_picpath:?related_gist.poster_p_w_picpath,?url:? related_gist.url?},?weight:?keyword_count?})?AS?related在RETURN語句中的COLLECT()作用是將由Gist和相關Gist所組成的節點轉換為一個結果集,讓其中每一行Gist只出現一次,并對應一個相關Gist的節點數組。在COLLECT()語句中,我們在相關Gist中僅指定了所需的部分數據,以減小整個響應的大小。
最后,我們將產生這樣一條查詢語句,這也是最后一次使用WITH語句了:
MATCH?(gist:Gist)?WHERE?gist.title?=~?{search_query}OPTIONAL?MATCH?(gist)-[:HAS_KEYWORD]->(keyword)<-[:HAS_KEYWORD]-(related_gist)WITH?gist,?related_gist,?COUNT(DISTINCT?keyword.name)?AS?keyword_countORDER?BY?keyword_count?DESCWITHgist,COLLECT(DISTINCT?{related:?{?id:?related_gist.id,?title:?related_gist.title,?poster_p_w_picpath:?related_gist.poster_p_w_picpath,?url:?related_gist.url?},?weight:?keyword_count?})?AS?related//?Optionally?match?domains,?use?cases,?writers,?and?keywords?for?each?GistOPTIONAL?MATCH?(gist)-[:HAS_DOMAIN]->(domain:Domain)OPTIONAL?MATCH?(gist)-[:HAS_USECASE]->(usecase:UseCase)OPTIONAL?MATCH?(gist)<-[:WRITER_OF]-(writer:Person)OPTIONAL?MATCH?(gist)-[:HAS_KEYWORD]->(keyword:Keyword)//?Return?one?Gist?per?row?with?arrays?of?domains,?use?cases,?writers,?and?keywordsRETURNgist,related,COLLECT(DISTINCT?domain.name)?AS?domains,COLLECT(DISTINCT?usecase.name)?AS?usecases,COLLECT(DISTINCT?keyword.name)?AS?keywordsCOLLECT(DISTINCT?writer.name)?AS?writers,ORDER?BY?gist.title在這個查詢中,我們將選擇性地匹配所有相關的Domain、Use Case、Keyword和Person節點,并且將它們全部收集起來,與我們對相關Gist的處理方式相同。現在我們的結果不再是平坦的、反正規化的,而是包含一列Gist,其中每個Gist都對應著相關Gist的數組,形成了一種“has many”的關系,并且沒有任何重復數據。太酷了!
不僅如此,如果你覺得用表的形式返回數據太老土,那么Cypher也可以返回對象:
RETURN{ gist:?gist,domains:?collect(DISTINCT?domain.name)?AS?domains,usecases:?collect(DISTINCT?usecase.name)?AS?usecases,writers:?collect(DISTINCT?writer.name)?AS?writers,keywords:?collect(DISTINCT?keyword.name)?AS?keywords,related_gists:?related}ORDER?BY?gist.title通常來說,在稍具規模的web應用程序中,需要進行大量的數據庫調用以返回HTTP響應所需的數據。雖然你可以并行地執行查詢,但通常來說你需要首先返回某個查詢的結果集,才能發送另一個數據庫請求以獲取相關的數據。在SQL中,你可以通過生成復雜的、開銷很大的表join語句,通過一個查詢從多張表中返回結果。但只要你在同一個查詢中進行了多次SQL join,這個查詢的復雜性將會飛快地增長。更不用說數據庫仍然需要進行表或索引掃描才能夠獲得相應的數據了。而在Neo4j中,通過關系獲取實體的方式是直接使用對應于相關節點的指針,因此服務器可以隨意進行遍歷。
轉載于:https://blog.51cto.com/cloudy/1976777
總結
以上是生活随笔為你收集整理的图数据库Neo4j全栈Web技术解密的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你知道Unity IoC Contain
- 下一篇: mysql linux版安装