最近我在用图形数据库来完成对一个初创项目的支持。在使用过程当中以为这种图形数据库实际上挺有意思的。所以在这里给你们作一个简单的介绍。html
NoSQL数据库相信你们都据说过。它们经常能够用来处理传统的关系型数据库所难以解决的一系列问题。一般状况下,这些NoSQL数据库分为Graph,Document,Column Family以及Key-Value Store等四种。这四种类型的数据库分别使用了不一样的数据结构来记录数据。所以它们所适用的场景也不尽相同。node
其中最为特别的即是图形数据库了。能够说,它和其它的一系列NoSQL数据库很是不一样:丰富的关系表示,完整的事务支持,却没有一个纯正的横向扩展解决方案。spring
在本文中,咱们就将对业界很是流行的图形数据库Neo4J进行简单的介绍。sql
图形数据库简介数据库
相信您和我同样,在使用关系型数据库时经常会遇到一系列很是复杂的设计问题。例如一部电影中的各个演员经常有主角配角之分,还要有导演,特效等人员的参与。一般状况下这些人员经常都被抽象为Person类型,对应着同一个数据库表。同时一位导演自己也能够是其它电影或者电视剧的演员,更多是歌手,甚至是某些影视公司的投资者(没错,我这个例子的确是以赵薇为模板的)。而这些影视公司则经常是一系列电影,电视剧的资方。这种彼此关联的关系经常会很是复杂,并且在两个实体之间经常同时存在着多个不一样的关系:数组
在尝试使用关系型数据库对这些关系进行建模时,咱们首先须要创建表示各类实体的一系列表:表示人的表,表示电影的表,表示电视剧的表,表示影视公司的表等等。这些表经常须要经过一系列关联表将它们关联起来:经过这些关联表来记录一我的到底参演过哪些电影,参演过哪些电视剧,唱过哪些歌,同时又是哪些公司的投资方。同时咱们还须要建立一系列关联表来记录一部电影中哪些人是主角,哪些人是配角,哪一个人是导演,哪些人是特效等。能够看到,咱们须要大量的关联表来记录这一系列复杂的关系。在更多实体引入以后,咱们将须要愈来愈多的关联表,从而使得基于关系型数据库的解决方案繁琐易错。缓存
这一切的症结主要在于关系型数据库是觉得实体建模这一基础理念设计的。该设计理念并无提供对这些实体间关系的直接支持。在须要描述这些实体之间的关系时,咱们经常须要建立一个关联表以记录这些数据之间的关联关系,并且这些关联表经常不用来记录除外键以外的其它数据。也就是说,这些关联表也仅仅是经过关系型数据库所已有的功能来模拟实体之间的关系。这种模拟致使了两个很是糟糕的结果:数据库须要经过关联表间接地维护实体间的关系,致使数据库的执行效能低下;同时关联表的数量急剧上升。服务器
这种执行效能到底低下到什么程度呢?就以创建人和电影之间的投资关系为例。一个使用关联表的设计经常以下所示:数据结构
若是如今咱们想要经过该关系找到一部电影的全部投资人,关系型数据库经常会执行哪些操做呢?首先,在关联表中执行一个Table Scan操做(假设没有获得索引支持),以找到全部film域的值与目标电影id相匹配的记录。接下来,经过这些记录中的person域所记录的Person的主键值来从Person表中找到相应的记录。若是记录较少,那么这步就会使用Clustered Index Seek操做(假设是使用该运算符)。整个操做的时间复杂度将变为O(nlogn):架构
能够看到,经过关联表组织的关系在运行时的性能并非很好。若是咱们所须要操做的数据集包含了很是多的关系,并且主要是在对这些关系进行操做,那么能够想象到关系数据库的性能将变得有多差。
除了性能以外,关联表数量的管理也是一个很是让人头疼的问题。刚刚咱们仅仅是举了一个具备四个实体的例子:人,电影,电视剧,影视公司。现实生活中的例子可不是这么简单。在一些场景下,咱们经常须要对更多的实体进行建模,从而完整地描述某一领域内的关联关系。这种关联关系所涵盖的可能包含影视公司的控股关系,各控股公司之间复杂的持股关系以及各公司之间的借贷款状况及担保关系等,更多是人之间的关系,人与各个品牌之间的代言关系,各个品牌与所属公司之间的关系等。
能够看到,在须要描述大量关系时,传统的关系型数据库已经不堪重负。它所能承担的是较多实体可是实体间关系略显简单的状况。而对于这种实体间关系很是复杂,经常须要在关系之中记录数据,并且大部分对数据的操做都与关系有关的状况,原生支持了关系的图形数据库才是正确的选择。它不只仅能够为咱们带来运行性能的提高,更能够大大提升系统开发效率,减小维护成本。
在一个图形数据库中,数据库的最主要组成主要有两种,结点集和链接结点的关系。结点集就是图中一系列结点的集合,比较接近于关系数据库中所最常使用的表。而关系则是图形数据库所特有的组成。所以对于一个习惯于使用关系型数据库开发的人而言,如何正确地理解关系则是正确使用图形数据库的关键。
注:这里的结点集是我本身的翻译。在Neo4J官方文档中,其被称为label。原文为:A label is a named graph construct that is used to group nodes into sets; all nodes labeled with the same label belongs to the same set。我我的以为生硬地取名为标签反而容易让别人混淆,因此选取了“group nodes into sets”的意译,也好让label和node,即结点集和结点之间的关系可以更好地对应。
可是不用担忧,在了解了图形数据库对数据进行抽象的方式以后,您就会以为这些数据抽象方式实际上和关系型数据库仍是很是接近的。简单地说,每一个结点仍具备标示本身所属实体类型的标签,也既是其所属的结点集,并记录一系列描述该结点特性的属性。除此以外,咱们还能够经过关系来链接各个结点。所以各个结点集的抽象实际上与关系型数据库中的各个表的抽象仍是有些相似的:
可是在表示关系的时候,关系型数据库和图形数据库就有很大的不一样了:
从上图中能够看到,在须要表示多对多关系时,咱们经常须要建立一个关联表来记录不一样实体的多对多关系,并且这些关联表经常不用来记录信息。若是两个实体之间拥有多种关系,那么咱们就须要在它们之间建立多个关联表。而在一个图形数据库中,咱们只须要标明二者之间存在着不一样的关系,例如用DirectBy关系指向电影的导演,或用ActBy关系来指定参与电影拍摄的各个演员。同时在ActBy关系中,咱们更能够经过关系中的属性来表示其是不是该电影的主演。并且从上面所展现的关系的名称上能够看出,关系是有向的。若是但愿在两个结点集间创建双向关系,咱们就须要为每一个方向定义一个关系。
也就是说,相对于关系数据库中的各类关联表,图形数据库中的关系能够经过关系可以包含属性这一功能来提供更为丰富的关系展示方式。所以相较于关系型数据库,图形数据库的用户在对事物进行抽象时将拥有一个额外的武器,那就是丰富的关系:
所以在为图形数据库定义数据展示时,咱们应该以一种更为天然的方式来对这些须要展示的事物进行抽象:首先为这些事物定义其所对应的结点集,并定义该结点集所具备的各个属性。接下来辨识出它们之间的关系并建立这些关系的相应抽象。
所以一个图形数据库中所承载的数据最终将有相似于下图所示的结构:
设计一个优质的图
在了解了图形数据库的基础知识以后,咱们就要开始尝试使用图形数据库了。首先咱们要搞清楚一个问题,那就是如何为咱们的图形数据库定义一个设计良好的图?实际上这并不困难,您只须要了解图数据库设计时所使用的一系列要点便可。
首先就是,分清图中结点集,结点以及关系之间的相互联系。在以往的基于关系型数据库的设计中,咱们经常会使用一个表来抽象一类事物。如对于人这个概念,咱们经常会抽象出一个表,并在表中添加表示两我的的记录,Alice和Bob:
而在图数据库中,这里对应着两个概念:结点集和结点。在一般状况下,图形数据库中的数据展现并不使用结点集,而是独立的结点:
而若是须要在图中添加对书籍的支持,那么这些书籍将仍然被表示为一个结点:
也就是说,虽然在一个图数据库中经常拥有结点集的概念,可是它已经再也不做为图数据库的最重要抽象方式了。甚至从某些图形数据库已经容许软件开发人员使用Schemaless结点这一点上来看,它们已经将结点集的概念弱化了。反过来,咱们思考的角度就应该是结点个体,以及这些个体之间所存在的一系列关系。
那么咱们是否是能够随便定义各个结点所具备的数据呢?不是的。这里最为经常使用的一个准则就是:Schemaless这种灵活度能为你带来好处。例如相较于强类型语言,弱类型语言能够为软件开发人员带来更大的开发灵活度,可是其维护性和严谨性经常不如强类型语言。一样地,在使用Schemaless结点时也要兼顾灵活性和维护性。
这样咱们就能够在结点中添加多种多样的关系,而不用像在关系型数据库中那样须要担忧是否须要经过更改数据库的Schema来记录一些外键。这进而容许软件开发人员在各结点间添加多种多样的关系:
所以在一个图形数据库中,结点集这个概念已经不是最重要的那一类概念了。例如在某些图形数据库中,各个结点的ID并非按照结点集来组织的,而是根据结点的建立顺序来赋予的。在调试时您可能会发现,某个结点集内的第一个结点的ID是1,第二个结点的ID就是3了。而具备2这个ID的结点则处于另外一个结点集中。
那么咱们应该如何为业务逻辑定义一个合适的图呢?简单地说,单一事物应该被抽象为一个结点,而同一类型的结点被记录在同一个结点集中。结点集内各结点所包含的数据可能有一些不一样,如一我的可能有不一样的职责并由此经过不一样的关系和其它结点关联。例如一我的既多是演员,多是导演,也多是演员兼导演。在关系型数据库中,咱们可能须要为演员和导演创建不一样的表。而在图形数据库中,这三种类型的人都是人这个结点集内的数据,而不一样的仅仅是它们经过不一样的关系链接到不一样的结点上了而已。也就是说,在图形数据库中,结点集并不会像关系型数据库中的表同样粒度那么小。
一旦抽象出了各个结点集,咱们就须要找出这些结点之间所可能拥有的关系。这些关系不只仅是跨结点集的。有时候,这些关系是同一结点集内的结点之间的关系,甚至是同一结点指向自身的关系:
这些关系一般都具备一个起点和终点。也就是说,图形数据库中的关系经常是有向的。若是但愿在两个结点之间建立一个相互关系,如Alice和Bob彼此相识,咱们就须要在他们之间建立两个KNOW_ABOUT关系。其中一个关系由Alice指向Bob,而另外一个关系则由Bob指向Alice:
须要注意的一点就是,虽说图形数据库中的关系是单向的,可是在一些图形数据库的实现中,如Neo4J,咱们不只仅能够查找到从某个结点所发出的关系,也能够找到指向某个结点的各个关系。也就是说,虽然图中的关系是单向的,可是关系在起点和终点均可以被查找到。
在项目中使用Neo4J
OK,在大概了解了图形数据库的一些基础知识以后,咱们就将以Neo4J为例讲解如何使用一个图形数据库了。Neo4J是Neo Technology所提供的开源图形数据库。其按照上面所介绍的结点/关系模型组织数据,并拥有如下一系列特性:
好,如今咱们就来看一个经过Cypher来建立并操做图形数据库的例子(来自http://neo4j.com/developer/guide-data-modeling/):
1 // 建立Sally这个Person类型的结点,该结点的name属性为Sally,age属性为32 2 CREATE (sally:Person { name: 'Sally', age: 32 }) 3 4 // 建立John结点 5 CREATE (john:Person { name: 'John', age: 27 }) 6 7 // 建立Graph Databases一书所对应的结点 8 CREATE (gdb:Book { title: 'Graph Databases', 9 authors: ['Ian Robinson', 'Jim Webber'] }) 10 11 // 在Sally和John之间创建朋友关系,这里的since值应该是timestamp。请自行回忆各位的日期是如何在关系数据库中记录的~~~ 12 CREATE (sally)-[:FRIEND_OF { since: 1357718400 }]->(john) 13 14 // 在Sally和Graph Databases一书之间创建已读关系 15 CREATE (sally)-[:HAS_READ { rating: 4, on: 1360396800 }]->(gdb) 16 17 // 在John和Graph Databases一书之间创建已读关系 18 CREATE (john)-[:HAS_READ { rating: 5, on: 1359878400 }]->(gdb)
该段语句建立了三个结点:Person结点Sally和John,以及Book结点gdb。同时还指定了它们之间的关系:
注:原图来自http://neo4j.com/developer/guide-data-modeling/
想节省时间花在有用的地方,可是为了完整性不得不写
这里有一点须要注意的地方,那就是关系是单向的。若是但愿创建一个双向的关系,就像上面Sally和John互为朋友关系那样,咱们按理来讲应该须要重复执行建立关系的过程。因为我没有试过最新版本的Neo4J(由于它最近有一个破坏了后向兼容性的更改,咱们暂时没有办法升级Neo4J,也就没有办法确认上面的代码是否是少建立了一次FRIEND_OF),所以请读者注意。若是有谁实验了,请将结果添加到Comment中,感激涕零。
有了数据,咱们就能够对数据进行操做了。虽然Cypher和SQL操做的是不一样的数据结构,可是他们的语法结构仍是很是类似的。例以下面的语句就用来得到Sally和John是何时成为朋友的(来自http://neo4j.com/developer/guide-data-modeling/):
1 MATCH (sally:Person { name: 'Sally' }) 2 MATCH (john:Person { name: 'John' }) 3 MATCH (sally)-[r:FRIEND_OF]-(john) 4 RETURN r.since as friends_since
并且还有一些更复杂的语法。以下面的操做就用来判断Sally和John谁先读了《Graph Databases》一书:
1 MATCH (people:Person) 2 WHERE people.name = 'John' OR people.name = 'Sally' 3 MATCH (people)-[r:HAS_READ]->(gdb:Book { title: 'Graph Databases' }) 4 RETURN people.name as first_reader 5 ORDER BY r.on 6 LIMIT 1
固然,谁都不肯意写SQL,不然Hibernate也发展不起来。一个当前较为流行的解决方案就是Spring Data Neo4J。经过定义一系列Java类并在其上使用一系列标记,咱们就能在系统中使用Neo4J了。如今咱们就以3.4.4版本的Spring Data Neo4J为例讲解如何对其进行使用。
首先,咱们须要为将要存入到Neo4J中的数据定义相应的数据类型(来自于http://projects.spring.io/spring-data-neo4j/):
1 // 经过NodeEntity标记来建立一个须要被存入到Neo4J的数据类型 2 @NodeEntity 3 public class Movie { 4 // 经过GraphId标记来指定做为ID的域。若是是新建一个结点,那么咱们须要将该域置空(null)。不知道4.0.0是否还有这个限制 5 @GraphId Long id; 6 7 // 建立针对该域的索引 8 @Indexed(type = FULLTEXT, indexName = "search") 9 String title; 10 11 // 对Person类的直接引用。在保存时,其将会被自动保存到Person结点集中并保持Movie类对该实例的引用 12 Person director; 13 14 // 经过RelatedTo标记来标示当前集合所引用的各个实体对应于当前Movie实例的关系是ACTS_IN。注意这里标明了方向是INCOMING,也就是说,其方向是从Person指向Movie,也既是Person ACTS_IN Movie。而在Person中,咱们一样能够拥有一个Movie的集合,一样使用RelatedTo标记使用ACTS_IN关系,而direction为OUTGOING 15 // 另,RelatedTo和RelatedToVia标记按理来讲在4.0.0里已经被丢弃了,可是在官方示例中依然被使用 16 @RelatedTo(type="ACTS_IN", direction = INCOMING) 17 Set<Person> actors; 18 19 @RelatedToVia(type = "RATED") 20 Iterable<Rating> ratings; 21 22 // 使用自定义Query读取数据 23 @Query("start movie=node({self}) match 24 movie-->genre<--similar return similar") 25 Iterable<Movie> similarMovies; 26 }
接下来,您就能够建立一个用来对刚刚所定义的类型进行CRUD操做的Repository了:
1 // 从GraphRepository接口直接获得对Movie类进行CRUD的功能 2 interface MovieRepository extends GraphRepository<Movie> { 3 // 经过Cypher语句来执行特定的操做 4 @Query("start movie={0} match m<-[rating:RATED]-user 5 return rating") 6 Iterable<Rating> getRatings(Movie movie); 7 8 // 和Spring Data JPA同样,能够经过特定的规则组合函数名来添加筛选条件 9 Iterable<Person> findByActorsMoviesActorName(name) 10 }
最后咱们须要在Spring的配置文件中指定这些组成所在的位置:
1 <neo4j:config storeDirectory="target/graph.db" base-package="com.example.neo4j.entity"/> 2 <neo4j:repositories base-package="com.example.neo4j.repository" />
Neo4J集群
OK,在了解了如何使用Neo4J以后,下一步要考虑的就是如何经过搭建一个Neo4J集群来提供一个具备高可用性,高吞吐量的解决方案了。首先您要知道的是,和其它NoSQL数据库所提供的近乎无限的横向扩展能力相比,Neo4J集群其实是有必定限制的。为了能更好地理解这些限制,就让咱们首先看一看Neo4J集群的架构以及它究竟是如何工做的:
上图展现了一个由三个Neo4J结点所组成的Master-Slave集群。一般状况下,每一个Neo4J集群都包含一个Master和多个Slave。该集群中的每一个Neo4J实例都包含了图中的全部数据。这样任何一个Neo4J实例的失效都不会致使数据的丢失。集群中的Master主要负责数据的写入,接下来Slave则会将Master中的数据更改同步到自身。若是一个写入请求到达了Slave,那么该Slave也将会就该请求与Master通讯。此时该写入请求将首先被Master执行,再异步地将数据更新到各个Slave中。因此在上图中,您能够看到表示数据写入方式的红线有从Master到Slave,也有从Slave到Master,可是并无从Slave到Slave。而全部这一切都是经过Transaction Propagation组成来协调完成的。
有些读者可能已经注意到了:Neo4J集群中数据的写入是经过Master来完成的,那是否是Master会变成系统的写入瓶颈呢?答案是几乎不会。首先是图数据修改的复杂性致使其并不会像栈,数组等数据类型那样容易被修改。在修改一个图的时候,咱们不但须要修改图结点自己,还要维护各个关系,自己就是一个比较复杂的过程,对用户而言也是较难理解的。所以对图所进行的操做也经常是读比写多不少。同时Neo4J内部还有一个写队列,能够用来暂时缓存向Neo4J实例的写入操做,从而使得Neo4J可以处理忽然到来的大量写入操做。而在最坏的状况就是Neo4J集群须要面对持续的大量的写入操做。在这种状况下,咱们就须要考虑Neo4J集群的纵向扩展了,由于此时横向扩展无益于解决这个问题。
反过来,因为数据的读取能够经过集群中的任意一个Neo4J实例来完成,所以Neo4J集群的读吞吐量能够在理论上作到随集群中Neo4J实例的个数线性增加。例如若是一个拥有5个结点的Neo4J集群能够每秒响应500个读请求,那么再添加一个结点就能够将其扩容为每秒响应600个读请求。
但在请求量很是巨大并且访问的数据分布很是随机的状况下,另外一个问题就可能发生了,那就是Cache-Miss。Neo4J内部使用一个缓存记录最近所访问的数据。这些缓存数据会保存在内存中以便快速地响应数据读取请求。可是在请求量很是巨大并且所访问数据分布随机的状况下,Cache-Miss将会持续地发生,使得每次对数据的读取都要通过磁盘查找来完成,从而大大地下降了Neo4J实例的运行效率。而Neo4J所提供的解决方案被称为Cache-based Sharding。简单地说,就是使用同一个Neo4J实例来响应一个用户所发送的全部需求。其背后的原理也很是简单,那就是同一个用户在一段时间内所访问的数据经常是相似的。所以将这个用户的一系列数据请求发送到同一个Neo4J服务器实例上能够很大程度上下降发生Cache-Miss的几率。
Neo4J数据服务器中的另外一个组成Cluster Management则用来负责同步集群中各个实例的状态,并监控其它Neo4J结点的加入和离开。同时其还负责维护领导选举结果的一致性。若是Neo4J集群中失效的结点个数超过了集群中结点个数的一半,那么该集群将只接受读取操做,直到有效结点从新超过集群结点数量的一半。
在启动时,一个Neo4J数据库实例将首先尝试着加入由配置文件所标明的集群。若是该集群存在,那么它将做为一个Slave加入。不然该集群将被建立,而且其将被做为该集群的Master。
若是Neo4J集群中的一个Neo4J实例失效了,那么其它Neo4J实例会在短期内探测到该状况并将其标示为失效,直到其从新恢复到正常状态并将数据同步到最新。这其中有一个特殊状况,那就是Master失效的状况。在该状况下,Neo4J集群将会经过内置的Leader选举功能选举出新的Master。
在Cluster Management组成的帮助下,咱们还能够建立一个Global Cluster。其拥有一个Master Cluster以及多个Slave Cluster。该集群组建方式容许Master Cluster和Slave Cluster处于不一样区域的服务集群中。这样就能够容许服务的用户访问距离本身最近的服务。和Neo4J集群中的Master及Slave实例的关系相似,数据的写入一般都是在Master Cluster中进行,而Slave Cluster将只负责提供数据读取服务。
提升Neo4J的性能
相信您在上面对Neo4J集群的讲解中已经看出,Neo4J集群实际上仍是有一些限制的。这些限制将可能致使Neo4J集群在总的系统容量,如存储结点的数目或写吞吐量等众多方面存在着必定的瓶颈。在《服务的扩展性》一文中咱们曾经介绍过,经过纵向扩展,咱们一样能够提升服务的总体性能。除了经过为Neo4J提供具备更高容量的硬件以外,更有效地使用Neo4J也是纵向扩展的一个重要方法。
和SQL Server等数据库所提供的Execution Plan相似,Neo4J也提供了Execution Plan。在执行一个请求时,Neo4J将会把这个请求拆解为一系列较小的操做符(Operator)。每一个操做符都将执行一部分工做,并彼此相互协做完成对请求的响应。与SQL Server的Execution Plan相似,Neo4J的Execution Plan一样拥有Scan,Seek,Merge,Filter等多种类型的操做。咱们能够经过EXPLAIN或PROFILE命令获得一个请求将被如何执行的树形表示。经过查看这些树形表示,软件开发人员可以了解一个请求在Neo4J中是如何运行的:
如下是当前版本的Neo4J所支持的全部操做符:http://neo4j.com/docs/stable/execution-plans.html。
有经过Execution Plan调优经验的读者可能第一眼就看到了一个操做符:Node Index Seek。它的名字直接透露了Neo4J中的另外一个调优利器:索引。咱们知道,只要查找出的结果集中记录的数据并非不少,那么SQL Server中的Clusted Index Seek经常是具备最优效率的操做。所以在Neo4J中,咱们一样须要尽可能合理地使用索引,从而使得Neo4J所生成的Execution Plan能使用基于索引的一系列操做符。让咱们回忆一下以前展现给你们的按照Spring Data Neo4J方式抽象出的Movie类:
1 @NodeEntity 2 public class Movie { 3 @GraphId Long id; 4 5 @Indexed(type = FULLTEXT, indexName = "search") 6 String title; 7 8 …… 9 }
上面的代码展现了咱们应该如何经过@Indexed标记来建立一个索引。若是您是直接使用Cypher来操做Neo4J的,那么您能够经过如下语句来建立一个索引:
1 CREATE INDEX ON :Movie(title)
而这里有一个和SQL Server略有不一致的地方,那就是对@GraphId标记的理解。在SQL Server中,Primary Key其实是与索引没有任何关联的,只是在默认状况下,其经常会被自动地添加一个索引。而在Neo4J中,由@GraphId标记所修饰的域则更像是Neo4J的内部实现。Neo4J经过该域所记录的值执行对结点的访问,而不是在其上自动地添加了一个索引。
还有一个可能很是容易影响Neo4J性能的可能,那就是尝试使用Neo4J记录不适合它记录的数据。在本文的一开始咱们就已经介绍了Neo4J所适合的领域,那就是记录图形数据,及结点集和结点之间的关系。而对于其它一些类型的数据,如用户的用户名/密码对,那就不是图形数据库所擅长的领域了。在这些状况下,咱们应该选取合适的数据库来记录这些数据。在一个大型系统中,多种不一样类型的数据库相互协做是常常有的事情,因此没有必要非要将一些原本应该由其它类型的数据库所记录的数据硬生生地记录在Neo4J中。
和其它数据库合做
在上面咱们刚刚提到了不该该由Neo4J记录不适合它记录的数据,以保证Neo4J不被不合理的使用方式拉低其执行效率。那么这些数据应该记录在哪里呢?答案很是简单:适合记录这些数据的其它类型的数据库。
可能你以为我这句话是废话。其实我也这么以为。而我想在这里介绍的是,如何完成Neo4J和其它数据库之间的集成,从而使它们协同工做,向用户提供完整的服务。对于某些系统,咱们能够容许这些数据库之间拥有必定程度的不一致;而对于另一些系统,咱们则须要时刻保证数据的一致性。
Neo4J所提出支持的技术方案主要有三种:Event-based Synchronization,Periodic Synchronization以及Periodic Full Export/Import of Data。Event-based Synchronization实际上就是经过同时向基于Neo4J的后台和基于其它数据库的后台发送相同的消息,并由这些后台完成对数据的写入。Periodic Synchronization则是定时地将一个数据库中对数据的更改同步到另外一个数据库中。而Periodic Full Export/Import of Data则是经过将一个数据库中的全部数据导入到另一个数据库中的方式来完成的。
这三种解决方案都是用来处理Neo4J所记录的数据与其它数据库相同的状况。而更为常见的状况则是,Neo4J记录实体关系比较复杂的图,其它数据库则用来记录具备其它类型表现形式的数据。Neo4J和这些数据库之间的数据只有一部分交集,而每一个数据库都拥有本身所特有的数据。针对这种状况的处理方法则经常是多步提交。例如在一个交友网站中,用户能够在页面上完成自身帐户的设置,如用户名,密码等,并能够在下一步添加好友界面中添加一系列好友以及有关于该好友的注释。那么在该系统中,用户自身的帐户设置就可能记录在关系型数据库中,而有关好友的相关信息则记录在图形数据库中。若是将这两步中的全部信息做为一个请求发送到后台,那么就可能出如今某个数据库上成功保存而在另外一个数据库上保存失败的状况。为了不这种状况,咱们就须要将填充这两部分资料的信息分为两个页面,而在每一个页面下部提供一个”保存并进行下一步”的按钮。这样若是第一步设置帐户的步骤没法正常保存,那么用户就没有办法进行下一步添加朋友的操做。而在添加朋友这步中,若是图形数据库没法正常保存,那么咱们将能够明确地告诉用户添加朋友失败,从而容许用户重试。
其实不少时候,跨不一样数据库保存数据的问题均可以经过调整设计的方式来解决,何况这些数据库所记录的数据经常具备很是不一样的数据结构。所以就用户来讲,分红多步提交经常是一个很是天然的使用方式。
转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/5277051.html
商业转载请事先与我联系:silverfox715@sina.com
公众号必定帮忙别标成原创,由于协调起来太麻烦了。。。