四种nosql 数据库对比
自80年代以来,在许多不同的数据模型中 ,关系模型一直占据主导地位,其实现方式包括Oracle , MySQL和MSSQL (也称为关系数据库管理系统(RDBMS))。 但是,近来,在越来越多的情况下,由于缺乏数据关系以及数据建模方面的问题以及对多台服务器和大量数据的水平可伸缩性的限制,使用关系数据库会导致问题。 有两种趋势使这些问题引起国际软件界的注意:
关系数据库为解决这些趋势而面临着越来越多的问题。 这导致针对这些问题的特殊方面的多种不同技术可以一起使用,也可以与现有的RDBMS一起使用,也称为Polyglot Persistence 。 替代数据库并不是什么新鲜事物,它们以诸如对象数据库(OODBMS) ,分层数据库(例如LDAP)等形式存在了很长时间。 但是在最近几年中,已经启动了许多新项目,这些新项目一起被称为NOSQL-databases。
本文旨在概述Graph数据库在NOSQL运动中的位置。 第二部分是对Neo4j(基于Java的图形数据库)的介绍。
对于一组不遵循关系数据模型并且不使用SQL作为查询语言的持久性解决方案, NOSQL (不仅限于 SQL)确实是一个非常广泛的类别。
简而言之,可以将NOSQL数据库根据其数据模型分为以下四类:
对于诸如伏地魔或东京内阁之类的键/值系统,最小的建模单位是键-值对。 使用BigTable-克隆时,它是具有可变数量属性的元组,而文档数据库如CouchDB和MongoDB 。 图数据库将整个数据集建模为一个大的密集网络结构。
在这里,将深入研究NOSQL数据库的两个有趣方面-可伸缩性和复杂性。
为了保证数据的完整性,大多数经典数据库系统都是基于事务的。 这样可以确保在数据管理的所有情况下数据的一致性。 这些事务性特性也称为ACID (A tomicity,C onsistency, 我溶胶化,d urability)。 但是,向外扩展符合ACID的系统已显示出问题。 在不能完全解决的分布式系统中,高可用性的不同方面之间会发生冲突-称为CAP定理:
CAP定理假定横向扩展的三个不同方面中只有两个完全可以同时实现。
为了能够与大型分布式系统一起使用,对不同的CAP特性进行了仔细研究。
最重要的是,许多NOSQL数据库已经放宽了对C持久性的要求,以实现更好的A可用性和P分配。 这导致系统知道的BASE (B asically 一个 vailable,S经常状态,E ventually一致)。 这些没有经典意义上的事务,并且在数据模型上引入了约束,以实现更好的分区方案(例如Dynamo系统等)。 本简介中提供了有关CAP,ACID和BASE的更全面的讨论。
蛋白质同源网络 , Alex Adai提供 :德克萨斯大学细胞与分子生物学研究所
数据与系统之间日益增加的互连性导致更密集的数据集,无法以任何明显,简单或与领域无关的方式进行缩放和自动分片,甚至Todd Hoff也指出。 在Visual Complexity上了解有关大型和复杂数据集的可视化的更多信息
在认为关系数据模型过时之前,我们不应忘记关系数据库系统成功的原因之一是根据EF Codd的关系Datenmodell原则上能够建模任何数据结构而不会造成冗余或信息丢失的能力- 规范化 。 在建模阶段之后,可以通过SQL以非常强大的方式插入,修改和查询数据。 甚至还有RDBMS为不同的用例(例如OLTP,OLAP,Web应用程序或报告)实现优化的方案,例如插入速度或多维查询(例如星形方案)。
这是理论。 然而,实际上,RDBMS遇到了前面提到的CAP问题的约束,并且存在涉及高性能实现的,跨越多个表联接的“深度” SQL查询的高性能查询问题。 其他问题包括可伸缩性,模式随时间的演变,树结构的建模,半结构化数据,层次结构和网络等等。
而且,关系模型与当前的软件开发方法(如面向对象的语言和动态语言,称为对象关系阻抗不匹配 )的一致性很差。 在这里,已经开发出ORM层(例如Java的Hibernate)并将其应用于混合结果。 它们无疑减轻了将对象模型映射到关系数据模型的任务,但没有提供最佳的查询性能。 特别是半结构化数据通常被建模为大型表,其中许多列对于大多数行都是空的(稀疏表),这会导致性能下降。 由于联接是RDBMS中性能非常昂贵的集合操作,因此甚至无法在大量联接表中对这些结构进行建模的替代方案也存在问题。
查看域模型在数据结构上的投影,有两种主要的学派-用于RDBMS的关系方法和用于例如语义Web的图形网络结构 。
尽管理论上甚至在RDBMS中图形结构都是可标准化的,但是由于关系数据库的实现特性,这对递归结构(例如文件树)和网络结构(例如社交图)具有严重的查询性能影响。 网络关系上的每个操作都会在RDBMS中导致“联接”操作,实现为两个表的主键集之间的设置操作-缓慢的操作,并且不能随着这些表中元组数量的增长而扩展。
标记属性图的基本术语
关于图的面积,术语尚无普遍共识。 有许多不同类型的图模型 。 但是,需要做出一些努力来创建属性图模型 ,以统一大多数不同的图实现。 据此,可以使用三个基本构建块对属性图中的信息进行建模:
更具体地说,该模型是标记和定向的多重图。 带标签的图形的每个边缘都有一个标签,该标签用作该边缘的类型。 有向图允许从尾节点或源节点到头节点或目标节点使用固定方向的边。 属性图允许每个节点和边的属性可变列表,其中属性是与名称关联的值,从而简化了图结构。 多重图形允许两个节点之间有多个边。 这意味着即使两个边缘的尾部,头部和标签相同,两个节点也可以通过不同的边缘多次连接。
该图显示了一个小的标记属性图。
TinkerPop附近的人的小图
图论在各个领域的许多问题中都具有巨大的实用性和相关性。 应用最广泛的图论算法包括各种类型的海岸路径计算 , 测地线路径 ,诸如PageRank的中心性度量,特征向量中心性,紧密度,中间度,HITS等。 但是,在许多情况下,这些算法的应用仅限于研究,因为在实践中,尚无任何可用于生产的高性能图形数据库实现。 幸运的是,近年来,这种情况已经改变。 考虑到24/7生产方案,已经开发了几个项目:
下图显示了在复杂性和可伸缩性方面的主要NOSQL类别的定位。
有关“按比例缩放与复杂性缩放”的更多信息,请参见Emil Eifrem的博客文章 。
Neo4j是一个用Java编写的完全兼容ACID事务的图形数据库。 数据作为图形网络的优化数据结构存储在磁盘上。 Neo4j内核是一种非常快速的图形引擎,具有生产数据库预期的所有特性,如恢复-两阶段提交事务,XA遵从性等。Neo4j自2003年以来一直以24/7的生产方式使用。该项目仅作为版本发布1.0-有关稳定性和社区测试的重要里程碑。 通过在线备份和主从复制实现的高可用性在beta中,以及发布路线图中的下一个。 Neo4j既可以用作嵌入式数据库,管理费用为零,又可以用作具有广泛REST接口的独立服务器,以便轻松集成到基于PHP ,.NET和JavaScript的环境中。 但是,本文将重点讨论Neo4j的直接使用。
开发人员通过Java-API直接使用图形模型,该API公开了非常灵活的数据结构。 有良好的社区贡献的绑定到其他语言,例如JRuby / Ruby , Scala , Python , Clojure等。 Neo4j的典型数据特征是:
甚至“传统的” RDBMS应用程序也倾向于包含许多具有挑战性的数据集,这些数据集最好通过图形进行处理,例如文件夹结构,产品配置,产品装配体和类别,媒体元数据,语义交易以及金融部门的欺诈检测等。 。
Neo4j在内核周围有许多可选组件 。 支持通过元模型 ,符合SAIL和SparQL的RDF TripleStore 实现或许多常见图形算法的实现来构造图 。
如果要将Neo4j作为单独的服务器运行,则可以使用REST-包装器。 这适用于使用LAMP堆栈构建的体系结构。 REST甚至通过memcached ,e-tag和基于Apache的缓存和Web层简化了更大的读取负载的扩展。
很难在性能基准中给出确切的数字,因为它们非常依赖于基础硬件,所使用的数据集和其他因素。 大小明智的Neo4j可以立即处理数十亿个节点,关系和属性的大小图。 正常情况下,每线程具有热缓存的完全事务处理的毫秒级读取性能为每毫秒2000个关系遍历(每秒约1-2百万个遍历步骤)。 通过最短路径计算,Neo4j甚至可以在几千个节点的小图中,比MySQL快1000倍,差异随着图的大小而增加。
原因是在Neo4j中, 图形遍历以恒定速度执行 ,而与图形的总大小无关。 正如RDBMS中的join操作所见,没有涉及设置操作的操作会降低性能 。 Neo4j以一种懒惰的方式遍历该图-当结果迭代器要求它们时,首先遍历并返回节点和关系,从而通过大深度遍历提高了性能。
写入速度很大程度上取决于文件系统和硬件的查找时间。 Ext3文件系统和SSD磁盘是很好的组合,它们的事务性写入速度约为ca。 每秒100,000次操作。
如前所述,社交网络仅占图形数据库应用程序的一小部分,但是对于本示例而言,它们很容易理解。 为了演示Neo4j的基本功能,下面是Matrix电影中的一幅小图,使用基于Eclipse RCP的Neo4j的Neoclipse进行可视化:
为了方便起见,该图连接到已知参考节点(id = 0),以便从已知起点查找进入网络的路径。 这不是必需的,但实践证明非常有用。
Java实现看起来像这样:
在文件夹“ target / neo”中创建一个新的图形数据库
EmbeddedGraphDatabase graphdb = new EmbeddedGraphDatabase("target/neo");
关系类型可以即时创建:
RelationshipType KNOWS = DynamicRelationshipType.withName("KNOWS");
或通过typesafe Java Enum:
enum Relationships implements RelationshipType { KNOWS, INLOVE, HAS_CODED, MATRIX }
现在,创建两个节点,并在每个节点上附加一个“ name”属性。 然后,以KNOWS关系连接这些节点:
Node neo = graphdb.createNode();
neo.setProperty("name", "Neo");
Node morpheus = graphdb.createNode();
morpheus.setProperty("name", "Morpheus");
neo.createRelationshipTo(morpheus, KNOWS);
任何修改图形或需要数据隔离级别的操作都封装在一个事务中,因此回滚和恢复是开箱即用的:
Transaction tx = graphdb.beginTx();
try {
Node neo = graphdb.createNode();
...
tx.success();
} catch (Exception e) {
tx.failure();
} finally {
tx.finish();
}
创建Matrix图的完整代码如下所示:
graphdb = new EmbeddedGraphDatabase("target/neo4j");
index = new LuceneIndexService(graphdb);
Transaction tx = graphdb.beginTx();
try {
Node root = graphdb.getReferenceNode();
// we connect Neo with the root node, to gain an entry point to the graph
// not neccessary but practical.
neo = createAndConnectNode("Neo", root, MATRIX);
Node morpheus = createAndConnectNode("Morpheus", neo, KNOWS);
Node cypher = createAndConnectNode("Cypher", morpheus, KNOWS);
Node trinity = createAndConnectNode("Trinity", morpheus, KNOWS);
Node agentSmith = createAndConnectNode("Agent Smith", cypher, KNOWS);
architect = createAndConnectNode("The Architect", agentSmith, HAS_CODED);
// Trinity loves Neo. But he doesn't know.
trinity.createRelationshipTo(neo, LOVES);
tx.success();
} catch (Exception e) {
tx.failure();
} finally {
tx.finish();
}
具有会员功能
private Node createAndConnectNode(String name, Node otherNode,
RelationshipType relationshipType) {
Node node = graphdb.createNode();
node.setProperty("name", name);
node.createRelationshipTo(otherNode, relationshipType);
index.index(node, "name", name);
return node;
}
创建节点和关系。
Neo4j的API有许多面向Java集合的方法来回答简单的查询。 在这里,查看“ Neo”节点的关系足以找到他的朋友:
for (Relationship rel : neo.getRelationships(KNOWS)) {
Node friend = rel.getOtherNode(neo);
System.out.println(friend.getProperty("name"));
}
returns "Morpheus" as the only friend.
Neo4j的真正功能来自Traverser-API的使用,该API支持更复杂的遍历描述和过滤器。 它由一个Traverser组成,该Traverser评估Stop条件的StopEvaluator以及返回一个节点的ReturnableEvaluator。 同样,可以指定要遍历的关系的类型和方向。 遍历器实现Java Iterator接口,在例如for {...}循环中请求节点时,首先加载节点并延迟地逐步遍历图。 内置了两个常见的评估程序和默认值:
Traverser friends = neo.traverse(Order.BREADTH_FIRST,
StopEvaluator.DEPTH_ONE,
ReturnableEvaluator.ALL_BUT_START_NODE, KNOWS, Direction.BOTH);
for (Node friend : friends) {
System.out.println(friend.getProperty("name"));
}
我们首先要访问从起始节点到同一深度的所有节点,然后再继续到更远的级别(Order.BREADTH_FIRST)的节点,在经过一个深度后停止(StopEvaluator.DEPTH_ONE),然后返回除起始节点( “ Neo”)(ReturnableEvaluator.ALL_BUT_START_NODE)。 我们仅在两个方向(Direction.BOTH)上遍历类型为KNOWS的关系。 该遍历者再次将Morpheus返回为Neo的唯一直接朋友。
为了调查Neo的朋友的朋友是谁,需要遵循KNOWS-Kanten的2个步骤,从Neo开始,返回Trinity和Cypher。 通过编程,可以通过调整Traverser的StopEvaluator来将遍历深度限制为2:
StopEvaluator twoSteps = new StopEvaluator() {
@Override
public boolean isStopNode(TraversalPosition position) {
return position.depth() == 2;
}
};
此外,自定义ReturnableEvaluator仅返回深度2处找到的节点:
ReturnableEvaluator nodesAtDepthTwo = new ReturnableEvaluator() {
@Override
public boolean isReturnableNode(TraversalPosition position) {
return position.depth() == 2;
}
};
朋友遍历者现在变为:
Traverser friendsOfFriends = neo.traverse(Order.BREADTH_FIRST,
twoSteps, nodesAtDepthTwo, KNOWS, Direction.BOTH);
for (Node friend : friendsOfFriends) {
System.out.println(friend.getProperty("name"));
}
结果返回Cypher和Trinity。
另一个有趣的问题是,图中是否有人在恋爱,例如The Architect。
这次应该从架构师开始(考虑到他的node-ID,但是稍后会介绍更多)来检查整个图,并返回具有传出LOVE关系的节点。 自定义的ReturnableEvaluator将执行以下操作:
ReturnableEvaluator findLove = new ReturnableEvaluator() {
@Override
public boolean isReturnableNode(TraversalPosition position) {
return position.currentNode().hasRelationship(LOVES, Direction.OUTGOING);
}
};
为了遍历所有关系,需要整个图中的所有关系类型:
List
这将Trinity作为唯一节点返回,因为我们仅返回具有传出LOVE关系的节点。
虽然沿关系进行遍历操作是Neo4j的优点之一,但通常需要在整个图形上使用面向集合的功能。 典型示例是对所有节点上的属性进行全文搜索。 在这里,Neo4j正在使用外部索引系统,以免重新发明轮子。 对于基于文本的搜索的常见情况,Neo4j具有Lucene和Solr的紧密集成,从而增加了在Lucene / Solr中使用事务语义对任意节点属性进行索引的功能。
在矩阵示例中,例如,“名称”属性可以用
GraphDatabaseService graphDb = // a GraphDatabaseService instance
IndexService index = new LuceneIndexService( graphDb );
//create a new node and index the "name" property
Node neo = graphDb.createNode();
neo.setProperty( "name", "Neo" );
index.index( neo, "name", neo.getProperty( "name" ) );
//search for the first node with "name=Neo"
Node node = index.getSingleNode( "name", "Neo" );
Lucene是该图的外部索引的一个示例。 但是,拥有快速的图形引擎,这是在图形本身中建立索引结构的大量策略,可缩短特殊数据集和域的遍历模式。 例如,一维数据有时间轴和B树,R维树和四叉树索引了二维数据(在空间和GIS社区中非常常见),还有许多其他内容。 通常,另一个有用的模式是将重要的子图直接连接到根节点,以捷径化重要的起始节点。
Neo4j具有许多良好的语言绑定,可以更轻松地处理图结构。 这个例子使用了出色的Neo4j-JRuby-bindings ,它极大地减少了代码总量:
在安装neo4j gem之后
>gem install neo4j
整个Matrix图和前面提到的查询的JRuby代码如下所示:
require "rubygems"
require "neo4j"
class Person
include Neo4j::NodeMixin
#the properties on the nodes
property :name
#the relationship types
has_n :friends
# Lucene index for some properties
index :name
end
#the players
neo = Person.new :name => 'Neo'
morpheus = Person.new :name => 'Morpheus'
trinity = Person.new :name => 'Trinity'
cypher = Person.new :name => 'Cypher'
smith = Person.new :name => 'Agent Smith'
architect = Person.new :name => 'Architect'
#the connections
cypher.friends << morpheus
cypher.friends << smith
neo.friends << morpheus
morpheus.friends << trinity
trinity.rels.outgoing(:loves) << neo
architect.rels.outgoing(:has_coded) << smith
#Neos friends
neo.friends.each { |n| puts n }
#Neos friends-of-friends
neo.friends.depth(2).each { |n| puts n }
#Who is in love?
architect.traverse.both(:friends, :has_coded, :loves).depth(:all).filter do
outgoing(:loves).to_a.size > 0
end.each do |n|
puts 'In love: ' + n.name
end
直到最近,还没有任何查询语言能够覆盖图形和图形相关项目的广阔领域。 在语义Web / RDF域中,有SPARQL ,这是一种SQL风格的查询语言,专注于描述用于匹配三元组的示例图的描述。 但是,有很多图与RDF不兼容,并且对数据建模采取了不同或更实用的方法,例如本文的Matrix示例和其他特定于域的数据集。 其他查询语言也是面向JSON的,例如MQL ( Freebase的查询语言)。 这些语言仅在它们自己定义的数据模型上运行,并且对深图算法和启发式分析方法不提供任何支持,或者提供的支持很差,而这对于当今的大图是必需的。
对于各种图数据模型(包括RDF)更复杂,更有趣的查询, 小鬼 -一个XPath的取向,图灵完备的图形编程语言-正由开发TinkerPop有关团队,主要由驱动马尔科A.罗德里格斯 。 通过引入属性图模型 ,它为大多数现有模型创建了一个超集和最不常见的支配者。 而且,它允许连接其他图形框架(例如, 使用 JUNG的 Gremlin ),并允许在不同的图形实现上表达图形遍历。 已经支持了两种实现,从简单的实现(例如内存中的TinkerGraph) ,到其他通过RDF-SAIL-adapter(用于AllegroGraph , Sesame和ThinkerPop LinkedData SAIL) (最初由Josh Shinavier为Ripple编程语言开发)实现的所有实现。通往Neo4j的方式。
Gremlin的语法基于XPath ,以便能够使用简单的表达式在图形中表达更深的路径描述。 许多简单的情况看起来几乎像普通的XPath。
安装Gremlin或在线尝试后,Matrix-example图上的Gremlin会话可能如下所示:
peterneubauer$ ~/code/gremlin/gremlin.sh
\,,,/
(o o)
-----oOOo-(_)-oOOo-----
gremlin> #open a new neo4j graph as the default graph ($_g)
gremlin> $_g := neo4j:open('tmp/matrix')
==>neo4jgraph[tmp/matrix]
gremlin> #the vertices
gremlin> $neo := g:add-v(g:map('name','Neo'))
==>v[1]
gremlin> $morpheus := g:add-v(g:map('name','Morpheus'))
==>v[2]
gremlin> $trinity := g:add-v(g:map('name','Trinity'))
==>v[3]
gremlin> $cypher := g:add-v(g:map('name','Cypher'))
==>v[4]
gremlin> $smith := g:add-v(g:map('name','Agent Smith'))
==>v[5]
gremlin> $architect := g:add-v(g:map('name','The Architect'))
==>v[6]
gremlin> #the edges
gremlin> g:list($cypher,$neo,$trinity)[g:add-e($morpheus,'KNOWS',.)]
==>v[4]
==>v[1]
==>v[3]
gremlin> g:add-e($cypher,'KNOWS',$smith)
==>e[3][4-KNOWS->5]
gremlin> g:add-e($trinity,'LOVES',$neo)
==>e[4][3-LOVES->1]
gremlin> g:add-e($architect,'HAS_CODED',$smith)
==>e[5][6-HAS_CODED->5]
gremlin> #go to Neo as the current root ($_) via a full-text index search
gremlin> $_ := g:key('name','Neo')
==>v[1]
gremlin> #is this Neo?
gremlin> ./@name
==>Neo
gremlin> #what links to here and from here?
gremlin> ./bothE
==>e[0][1-KNOWS->2]
==>e[4][3-LOVES->1]
gremlin> #only take the KNOWS-edges
gremlin> ./bothE[@label='KNOWS']
==>e[0][1-KNOWS->2]
gremlin> #Neo's friend's names
gremlin> ./bothE[@label='KNOWS']/inV/@name
==>Morpheus
gremlin>
gremlin> #Neo's Friends of friends, 2 steps
gremlin> repeat 2
$_ := ./outE[@label='KNOWS']/inV
end
==>v[4]
==>v[3]
gremlin> #What are their names?
gremlin> ./@name
==>Cypher
==>Trinity
gremlin> #every node in the whole graph with an outgoing LOVES edge
gremlin> $_g/V/outE[@label='LOVES']/../@name
==>Trinity
鉴于Gremlin的强大功能,Matrix示例非常幼稚。 更有趣的是大图算法的开发和测试。 特征向量中心性和Dijkstra之类的穷举算法无法缩放到这些图,因为它们需要接触网络中的每个顶点。 对于诸如此类的问题,启发式方法更适合采用基于语法的随机助行器和扩展激活 ( 此处由Marko Rodriguez进行更深入的解释)的概念。 Google PageRank算法是一种启发式算法,可以使用以下代码在Gremlin中进行建模(例如, Greatful Dead的歌曲,音乐会和专辑的图表示例,从此处加载,循环2500次,每次重复的能量损失为15%) :
$_g := tg:open()
g:load('data/graph-example-2.xml')
$m := g:map()
$_ := g:key('type', 'song')[g:rand-nat()]
repeat 2500
$_ := ./outE[@label='followed_by'][g:rand-nat()]/inV
if count($_) > 0
g:op-value('+',$m,$_[1]/@name, 1.0)
end
if g:rand-real() > 0.85 or count($_) = 0
$_ := g:key('type', 'song')[g:rand-nat()]
end
end
g:sort($m,'value',true())
它会返回以下歌曲的加权列表:
==>DRUMS=44.0
==>PLAYING IN THE BAND=38.0
==>ME AND MY UNCLE=32.0
==>TRUCKING=31.0
==>CUMBERLAND BLUES=29.0
==>PROMISED LAND=29.0
==>THE MUSIC NEVER STOPPED=29.0
==>CASSIDY=26.0
==>DARK STAR=26.0
==>NOT FADE AWAY=26.0
==>CHINA CAT SUNFLOWER=25.0
==>JACK STRAW=25.0
==>TOUCH OF GREY=24.0
==>BEAT IT ON DOWN THE LINE=23.0
==>BERTHA=23.0
另一个有趣的示例(其中基础图是Internet上的LiveLinkData图)是LinkedData和DBPedia上的音乐推荐算法 。
图形并不是解决所有问题的灵丹妙药,就像RDBMS和所有其他持久性解决方案一样。 最重要的方面是要处理的数据,数据的类型以及查询和操作结构的类型,以及对可伸缩性和CAP有哪些要求。
通常,既不需要也不希望使用NOSQL数据库的高可伸缩性方面作为使用非关系解决方案的唯一理由。 使用Neo4j和当今的硬件,在大多数情况下,可以在单个实例中对数十亿个域对象进行保存并查询整个域模型。
如果这还不够的话,总有可能引入领域最佳分片概念,而无需引入文档或键/值系统的硬数据建模限制。 是否生成dokumenten模型,特定于域的“对象数据库”或其他内容取决于域上下文和应用程序场景。
本文的代码在此处提供 。
对于出色的反馈和有用的建议,我要感谢Michael Hunger和Marko Rodriguez 。
翻译自: https://www.infoq.com/articles/graph-nosql-neo4j/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
四种nosql 数据库对比