使用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

(未经赞成,请勿转载)

相关文章
相关标签/搜索