图形数据库NOSQL和Neo4j文档格式.docx
《图形数据库NOSQL和Neo4j文档格式.docx》由会员分享,可在线阅读,更多相关《图形数据库NOSQL和Neo4j文档格式.docx(19页珍藏版)》请在冰豆网上搜索。
伸缩性和复杂度。
1.伸缩性
CAP:
ACIDvs.BASE
为了保证数据完整性,大多数经典数据库系统都是以事务为基础的。
这全方位保证了数据管理中数据的一致性。
这些事务特性也被称为ACID(A代表原子性、C表示一致性、I是隔离性、D则为持久性)。
然而,ACID兼容系统的向外扩展已经表现为一个问题。
在分布式系统中,高可用性不同方面之间产生的冲突没有完全得到解决-亦称CAP法则:
∙强一致性(C):
所有客户端看到的数据是同一个版本,即使是数据集发生了更新-如利用两阶段提交协议(XA事务),和ACID,
∙高可用性(A):
所有客户端总能找到所请求数据的至少一个版本,即使集群中某些机器已经宕机,
∙分区容忍性(P):
整个系统保持自己的特征,即使是被部署到不同服务器上的时候,这对客户端来讲是透明的。
CAP法则假定向外扩展的3个不同方面中只有两个可以同时完全实现。
为了能处理大型分布式系统,让我们深入了解所采用的不同CAP特征。
很多NOSQL数据库首先已经放宽了对于一致性(C)的要求,以期得到更好的可用性(A)和分区容忍性(P)。
这产生了被称为BASE(基本(B)可用性(A)、软状态(S)、最终一致性(E))的系统。
它们没有经典意义上的事务,并且在数据模型上引入了约束,以支持更好的分区模式(如Dynamo系统等)。
关于CAP、ACID和BASE的更深入讨论可以在这篇介绍里找到。
2.复杂度
蛋白质同源网络(ProteinHomologyNetwork),感谢AlexAdai:
细胞和分子生物学院-德州大学
数据和系统的互联性增加产生了一种无法用简单明了或领域无关(domain-independent)方式进行伸缩和自动分区的稠密数据集,甚至连ToddHoff也提到了这一问题。
关于大型复杂数据集的可视化内容可以访问可视化复杂度(VisualComplexity)。
关系模型
在把关系数据模型扔进故纸堆之前,我们不应该忘记关系数据库系统成功的一个原因是遵照E.F.Codd的想法,关系数据模型通过规范化的手段原则上能够建模任何数据结构且没有信息冗余和丢失。
建模完成之后,就可以使用SQL以一种非常强大的方式插入、修改和查询数据。
甚至有些数据库,为了插入速度或针对不同使用情况(如OLTP、OLAP、Web应用或报表)的多维查询(星形模式),对模式实现了优化。
这只是理论。
然而在实践中,RDBM遇到了前面提到的CAP问题的限制,以及由高性能查询实现而产生的问题:
联结大量表、深度嵌套的SQL查询。
其他问题包括伸缩性、随时间的模式演变,树形结构的建模,半结构化数据,层级和网络等。
关系模型也很难适应当前软件开发的方法,如面向对象和动态语言,这被称为对象-关系阻抗失配。
由此,象Java的Hibernate这样的ORM层被开发了出来,而且被应用到这种混合环境里。
它们固然简化了把对象模型映射到关系数据模型的任务,但是没有优化查询的性能。
尤其是半结构化数据往往被建模成具有许多列的大型表,其中很多行的许多列是空的(稀疏表),这导致了拙劣的性能。
甚至作为替代方法,把这些结构建模成大量的联结表,也有问题。
因为RDBMS中的联结是一种非常昂贵的集合操作。
图形是关系规范化的一种替代技术
看看领域模型在数据结构上的方案,有两个主流学派-RDBMS采用的关系方法和图-即网络结构,如语义网用到的。
尽管图结构在理论上甚至可以用RDBMS规范化,但由于关系数据库的实现特点,对于象文件树这样的递归结构和象社交图这样的网络结构有严重的查询性能影响。
网络关系上的每次操作都会导致RDBMS上的一次"
联结"
操作,以两个表的主键集合间的集合操作来实现,这种操作不仅缓慢并且无法随着这些表中元组数量的增加而伸缩。
属性图形(PropertyGraph)的基本术语
在图的领域,并没有一套被广泛接受的术语,存在着很多不同类型的图模型。
但是,有人致力于创建一种属性图形模型(PropertyGraphModel),以期统一大多数不同的图实现。
按照该模型,属性图里信息的建模使用3种构造单元:
∙节点(即顶点)
∙关系(即边)-具有方向和类型(标记和标向)
∙节点和关系上面的属性(即特性)
更特殊的是,这个模型是一个被标记和标向的属性多重图(multigraph)。
被标记的图每条边都有一个标签,它被用来作为那条边的类型。
有向图允许边有一个固定的方向,从末或源节点到首或目标节点。
属性图允许每个节点和边有一组可变的属性列表,其中的属性是关联某个名字的值,简化了图形结构。
多重图允许两个节点之间存在多条边。
这意味着两个节点可以由不同边连接多次,即使两条边有相同的尾、头和标记。
下图显示了一个被标记的小型属性图。
TinkerPop有关的小型人员图
图论的巨大用途被得到了认可,它跟不同领域的很多问题都有关联。
最常用的图论算法包括各种类型的最短路径计算、测地线(GeodesicPath)、集中度测量(如PageRank、特征向量集中度、亲密度、关系度、HITS等)。
然而,在很多情况下,这些算法的应用仅限制于研究,因为实际中没有任何可用于产品环境下的高性能图形数据库实现。
幸运的是,近些年情况有所改观。
有几个项目已经被开发出来,而且目标直指24/7的产品环境:
∙Neo4j-开源的Java属性图形模型
∙AllegroGraph,闭源,RDF-QuadStore
∙Sones-闭源,关注于.NET
∙Virtuoso-闭源,关注于RDF
∙HyergraphDB-开源的Java超图模型
∙OtherslikeInfoGrid、Filament、FlockDB等。
下图展示了在复杂度和伸缩性方面背景下的主要NOSQL分类的位置。
关于“规模扩展和复杂度扩展的比较”的更多内容,请阅读EmilEifrem的博文。
Neo4j-基于Java的图形数据库
Neo4j是一个用Java实现、完全兼容ACID的图形数据库。
数据以一种针对图形网络进行过优化的格式保存在磁盘上。
Neo4j的内核是一种极快的图形引擎,具有数据库产品期望的所有特性,如恢复、两阶段提交、符合XA等。
自2003年起,Neo4j就已经被作为24/7的产品使用。
该项目刚刚发布了1.0版-关于伸缩性和社区测试的一个主要里程碑。
通过联机备份实现的高可用性和主从复制目前处于测试阶段,预计在下一版本中发布。
Neo4j既可作为无需任何管理开销的内嵌数据库使用;
也可以作为单独的服务器使用,在这种使用场景下,它提供了广泛使用的REST接口,能够方便地集成到基于PHP、.NET和JavaScript的环境里。
但本文的重点主要在于讨论Neo4j的直接使用。
开发者可以通过Java-API直接与图形模型交互,这个API暴露了非常灵活的数据结构。
至于象JRuby/Ruby、Scala、Python、Clojure等其他语言,社区也贡献了优秀的绑定库。
Neo4j的典型数据特征:
∙数据结构不是必须的,甚至可以完全没有,这可以简化模式变更和延迟数据迁移。
∙可以方便建模常见的复杂领域数据集,如CMS里的访问控制可被建模成细粒度的访问控制表,类对象数据库的用例、TripleStores以及其他例子。
∙典型使用的领域如语义网和RDF、LinkedData、GIS、基因分析、社交网络数据建模、深度推荐算法以及其他领域。
甚至“传统”RDBMS应用往往也会包含一些具有挑战性、非常适合用图来处理的数据集,如文件夹结构、产品配置、产品组装和分类、媒体元数据、金融领域的语义交易和欺诈检测等。
围绕内核,Neo4j提供了一组可选的组件。
其中有支持通过元模型构造图形结构、SAIL-一种SparQL兼容的RDFTripleStore实现或一组公共图形算法的实现。
要是你想将Neo4j作为单独的服务器运行,还可以找到REST包装器。
这非常适合使用LAMP软件搭建的架构。
通过memcached、e-tag和基于Apache的缓存和Web层,REST甚至简化了大规模读负荷的伸缩。
高性能?
要给出确切的性能基准数据很难,因为它们跟底层的硬件、使用的数据集和其他因素关联很大。
自适应规模的Neo4j无需任何额外的工作便可以处理包含数十亿节点、关系和属性的图。
它的读性能可以很轻松地实现每毫秒(大约每秒1-2百万遍历步骤)遍历2000关系,这完全是事务性的,每个线程都有热缓存。
使用最短路径计算,Neo4j在处理包含数千个节点的小型图时,甚至比MySQL快1000倍,随着图规模的增加,差距也越来越大。
这其中的原因在于,在Neo4j里,图遍历执行的速度是常数,跟图的规模大小无关。
不象在RDBMS里常见的联结操作那样,这里不涉及降低性能的集合操作。
Neo4j以一种延迟风格遍历图-节点和关系只有在结果迭代器需要访问它们的时候才会被遍历并返回,对于大规模深度遍历而言,这极大地提高了性能。
写速度跟文件系统的查找时间和硬件有很大关系。
Ext3文件系统和SSD磁盘是不错的组合,这会导致每秒大约100,000写事务操作。
示例-黑客帝国
图
前面已经说过,社交网络只是代表了图形数据库应用的冰山一角,但用它们来作为例子可以让人很容易理解。
为了阐述Neo4j的基本功能,下面这个小型图来自黑客帝国这部电影。
该图是用Neo4j的Neoclipse产生的,该插件基于EclipseRCP:
这个图链接到一个已知的引用节点(id=0),这是为了方便的从一个已知起点找到条路进入这个网络。
这个节点不是必须的,但实践证明它非常有用。
Java的实现看上去大概是这个样子:
在“target/neo”目录创建一个新的图形数据库
EmbeddedGraphDatabasegraphdb=newEmbeddedGraphDatabase("
target/neo"
);
关系类型可以动态创建:
RelationshipTypeKNOWS=DynamicRelationshipType.withName("
KNOWS"
或通过类型安全的JavaEnum:
enumRelationshipsimplementsRelationshipType{KNOWS,INLOVE,HAS_CODED,MATRIX}
现在,创建2个节点,给每个节点加上“name”属性。
接着,把两个节点用一个“KNOWS”关系联系起来:
Nodeneo=graphdb.createNode();
node.setProperty("
name"
"
Neo"
Nodemorpheus=graphdb.createNode();
morpheus.setProperty("
Morpheus"
neo.createRelationshipTo(morpheus,KNOWS);
任何修改图或需要数据隔离级别的操作要包在事务中,这样可以利用内置的回滚和恢复功能:
Transactiontx=graphdb.beginTx();
try{
Nodeneo=graphdb.createNode();
...
tx.success();
}catch(Exceptione){
tx.failure();
}finally{
tx.finish();
}
创建“黑客帝国”图的完整代码:
graphdb=newEmbeddedGraphDatabase("
target/neo4j"
index=newLuceneIndexService(graphdb);
Noderoot=graphdb.getReferenceNode();
//weconnectNeowiththerootnode,togainanentrypointtothegraph
//notneccessarybutpractical.
neo=createAndConnectNode("
root,MATRIX);
Nodemorpheus=createAndConnectNode("
neo,KNOWS);
Nodecypher=createAndConnectNode("
Cypher"
morpheus,KNOWS);
Nodetrinity=createAndConnectNode("
Trinity"
NodeagentSmith=createAndConnectNode("
AgentSmith"
cypher,KNOWS);
architect=createAndConnectNode("
TheArchitect"
agentSmith,HAS_CODED);
//TrinitylovesNeo.Buthedoesn'
tknow.
trinity.createRelationshipTo(neo,LOVES);
以及创建节点和关系的成员函数
privateNodecreateAndConnectNode(Stringname,NodeotherNode,
RelationshipTyperelationshipType){
Nodenode=graphdb.createNode();
node.setProperty("
name);
node.createRelationshipTo(otherNode,relationshipType);
index.index(node,"
returnnode;
谁是Neo的朋友?
Neo4j的API有一组面向Java集合的方法可轻易地完成查询。
这里,只消看看“Neo”节点的关系便足以找出他的朋友:
for(Relationshiprel:
neo.getRelationships(KNOWS)){
Nodefriend=rel.getOtherNode(neo);
System.out.println(friend.getProperty("
));
returns"
astheonlyfriend.
但是,Neo4j的真正威力源自Traverser-API的使用,它可以完成非常复杂的遍历描述和过滤器。
它由Traverser和ReturnableEvaluator组成,前者计算StopEvaluator来获知何时停止,后者则用于在结果中包含哪些节点。
此外,你还可以指定要遍历关系的类型和方向。
Traverser实现了Java的Iterator接口,负责延迟加载和遍历整个图,在节点被首次要求访问(如for{...}循环)时进行。
它还内置了一些常用的Evaluator和缺省值:
Traverserfriends=neo.traverse(Order.BREADTH_FIRST,
StopEvaluator.DEPTH_ONE,
ReturnableEvaluator.ALL_BUT_START_NODE,KNOWS,Direction.BOTH);
for(Nodefriend:
friends){
我们在继续访问更深一级的节点之前首先从起点访问处于同一深度的所有节点(Order.BREADTH_FIRST),在深度为1的一次遍历后停止(StopEvaluator.DEPTH_ONE),然后返回除了起点("
)之外的所有节点(ReturnableEvaluator.ALL_BUT_START_NODE)。
我们在两个方向只遍历类型为KNOWS的关系。
这个遍历器再次返回Morpheus是Neo仅有的直接朋友。
朋友的朋友?
为了调查谁是Neo朋友的朋友,KNOWS网络需要再进行深度为2的步骤,由Neo开始,返回Trinity和Cypher。
实际编程中,这可以通过调整我们的Traverser的StopEvaluator,限制遍历深度为2来实现:
StopEvaluatortwoSteps=newStopEvaluator(){
@Override
publicbooleanisStopNode(TraversalPositionposition){
returnposition.depth()==2;
}
};
还要定制ReturnableEvaluator,只返回在深度2找到的节点:
ReturnableEvaluatornodesAtDepthTwo=newReturnableEvaluator(){
publicbooleanisReturnableNode(TraversalPositionposition){
现在“朋友的朋友”遍历器就成了:
TraverserfriendsOfFriends=neo.traverse(Order.BREADTH_FIRST,
twoSteps,nodesAtDepthTwo,KNOWS,Direction.BOTH);
friendsOfFriends){
它的结果是Cypher和Trinity。
谁在恋爱?
另一个有趣的问题是,这个图上是否有人正在热恋,比方说从架构师(Architect)开始。
这次,整个图需要沿着由架构师(假定他的节点ID是已知的,但要到很晚才知道)开始的任何关系开始检查,返回拥有向外LOVE关系的节点。
一个定制的ReturnableEvaluator可以完成这件事:
ReturnableEvaluatorfindLove=newReturnableEvaluator(){
@Override
returnposition.currentNode().hasRelationship(LOVES,Direction.OUTGOING);
为了遍历所有关系,需要知道整个图的所有关系类型:
List<
Object>
types=newArrayList<
();
//wehavetoconsiderallrelationshiptypesofthewholegraph
//(inbothdirections)
for(RelationshipTypetype:
graphdb.getRelationshipTypes()){
types.add(type);
types.add(Direction.BOTH);
//let'
sgo!
TraverserinLove=architect.traverse(Order.BREADTH_FIRST,
StopEvaluator.END_OF_GRAPH,findLove,types.toArray());
for(Nodelover:
inLove){
System.out.println(lover.getProperty("
上述代码的返回结果只有一个节点:
Trinity,因为我们只返回拥有向外LOVE关系的节点。
给图建立索引
尽管沿着所有关系的遍历操作是Neo4j的亮点之一,但也需要在整个图之上进行面向集合的操作。
所有节点属性的全文检索就是一个典型的例子。
为了不重新发明轮子,Neo4j在这里使用了外部索引系统。
针对常见的基于文本的搜索,Neo4j已经跟Lucene和Solr进行了深度集成,在Lucene/Solr里增加了给具有事务语义的任意节点属性创建索引的功能。
在黑客帝国的例子里,如给“name”属性创建索引:
GraphDatabaseServicegraphDb=/