3.3.3
图形计算导论
> \< dependency\>
> \< groupId \>org.apache.tinkerpop\< / groupId \>
> \< artifactId \>gremlin-core\< / artifactId \>
> \< version \>3.3.3\> \< / version\>
> \< /dependency\>
图是由顶点(节点、点)和边(弧线、线)组成的数据结构。当在计算机中建模图并将其应用于现代数据集和实践时,一般的以数学为导向的二进制图被扩展为同时支持标签和键/值属性。这种结构称为属性图。更正式地说,它是一个有向、二进制、带属性的多图。下面是一个示例属性图。这个图形示例将在整个文档中被广泛使用,并被称为“TinkerPop经典”,因为它是2009年TinkerPop0发布的原始演示图形(也就是说,那是最好的时代,也是最坏的时代)。
提示 | TinkerPop图形可用TinkerGraph通过TinkerFactory.createModern ()。 TinkerGraph是TinkerPop3的参考实现,在本文档中的几乎所有示例中都使用了它。 注意,也存在classicTinkerFactory.createClassic ()它是TinkerPop2中使用的 图形,不包括顶点标签。 |
---|
图1所示。TinkerPop现代
TinkerPop3是TinkerPop图形计算框架的第三代模型。与一般的计算类似,图计算区分了structure
(graph)和 process (traversal)。图的结构是由顶点/边/属性拓扑定义的数据模型。图的过程是结构分析的手段。图处理的典型形式称为遍历。
TinkerPop3 structure API的主要组件
-
Graph:维护一组顶点和边,以及对数据库功能(如事务)的访问。
-
Element:维护属性集合和表示元素类型的字符串标签。
-
Vertex:扩展元素并维护一组传入和传出的边。
-
Edge:扩展元素并维护传入和传出的顶点。
-
-
Property<V>:与V值关联的字符串键。
- VertexProperty :与V值和属性集合相关联的字符串键 属性(顶点)
TinkerPop3 process API的主要组件
-
TraversalSource:用于特定图、领域特定语言(DSL)和执行引擎的遍历生成器。
-
Traversal<S,E> :将类型S的对象转换为类型E的对象的函数数据流过程。
- GraphTraversal:面向原始图语义(即顶点、边等)的遍历DSL。
-
-
GraphComputer:并行处理图形的系统,可能分布在多机集群上。
-
VertexProgram:在所有顶点上以逻辑并行的方式执行的代码,通过消息传递进行相互通信。
-
MapReduce:一种并行分析图中所有顶点并产生一个简化结果的计算方法。
-
IMPORTANT | TinkerPop3授权为 Apache2 自由软件许可证。但是,请注意,TinkerPop3使用的底层图形引擎可能有不同的许可证。因此,一定要尊重graph系统产品的许可限制。 |
---|
当graph 系统使用the TinkerPop3 structure and
process APIs来实现时,他们的技术被考虑TinkerPop3-enabled并变得几乎无法区别于任何其他tinkerpop支持的图系统,节省各自的时间和空间的复杂性。本文档的目的是详细描述结构/过程二分法,并解释如何利用TinkerPop3实现图形系统无关的图形计算的唯一目的。在深入研究各种结构/流程api之前,先简要介绍一下这两种api。
NOTE | TinkerPop3 API在提供简洁的“查询语言”方法名称和尊重Java方法命名标准之间找到了一条很好的界限。TinkerPop3中使用的一般约定是,如果一个方法是“用户公开的”,那么就会提供一个简洁的名称(例如:“用户公开的” . out(), path(), repeat())。如果该方法主要用于图形系统提供程序,则遵循标准的Java命名约定(例如。getNextStep (), getSteps (), getElementComputeKeys ())。 |
---|
图的结构
图的结构是由图的顶点、边和属性之间的显式引用形成的拓扑。一个顶点有关联的边。一个顶点与另一个顶点相邻,如果它们共享一条关联边。属性附加到元素,而元素具有一组属性。属性是键/值对,其中键总是字符字符串。TinkerPop3的图结构API提供了创建这种结构所需的方法。前面绘制的TinkerPop图可以用以下Java
8代码创建。注意,这个图可以作为内存中的TinkerGraph 使用TinkerFactory.createClassic
()访问。
> Graph graph = TinkerGraph.open();
> Vertex marko = graph.addVertex(T.label, "person", T.id, 1, "name", "marko",
> "age", 29); ;
> Vertex vadas = graph.addVertex(T.label, "person", T.id, 2, "name", "vadas",
> "age", 27);
> Vertex lop = graph.addVertex(T.label, "software", T.id, 3, "name", "lop",
> "lang", "java");
> Vertex josh = graph.addVertex(T.label, "person", T.id, 4, "name", "josh",
> "age", 32);
> Vertex ripple = graph.addVertex(T.label, "software", T.id, 5, "name",
> "ripple", "lang", "java");
> Vertex peter = graph.addVertex(T.label, "person", T.id, 6, "name", "peter",
> "age", 35);
> marko.addEdge("knows", vadas, T.id, 7, "weight", 0.5f);
> marko.addEdge("knows", josh, T.id, 8, "weight", 1.0f);
> marko.addEdge("created", lop, T.id, 9, "weight", 0.4f);
> josh.addEdge("created", ripple, T.id, 10, "weight", 1.0f);
> josh.addEdge("created", lop, T.id, 11, "weight", 0.4f);
> peter.addEdge("created", lop, T.id, 12, "weight", 0.2f);
-
创建一个内存TinkerGraph 赋值给变量graph;
-
用T创建一个顶点和一组键/值对。label是顶点label和T。id是顶点id。
-
创建一条边和一组键/值对,并将边标签指定为第一个参数。
在上面的代码中,首先创建所有的顶点,然后创建它们各自的边。有两个“访问令牌”:T。id和T.label。当其中任何一个键值对以及一组其他键值对被提供给Graph.addVertex(Object…)或vertext
. addedge
(String,Vertex,Object…)时,相应的元素将被创建,并附带提供的键/值对属性。
WARNING | 许多图形系统不允许用户指定元素的ID,在这种情况下,会抛出异常。 |
---|---|
NOTE | 在TinkerPop3中,顶点允许使用单个不可变字符串标签(类似于边标签)。这个功能在TinkerPop2中不存在。和在TinkerPop2中一样,元素id在TinkerPop3中仍然是不可变的。 |
图的变更
下面是用Java
8表示对Graph基本变更操作序列。TinkerPop2和TinkerPop3之间的一个主要区别是,在TinkerPop3中,使用setter和getter的Java约定已经被抛弃,取而代之的是更符合TinkerPop2中gremling
- groovy语法的语法。由于包含了Java8 lambdas,
Gremlin-Java8和Gremlin-Groovy几乎是相同的,因此我们做了大量工作来确保这两种语言尽可能地相似。
WARNING | 在本文档中提供的代码示例中,使用了Gremlin-Java8或Gremlin-Groovy。通过将鼠标移到代码块上,可以确定使用的是Gremlin的哪个导数。单词“JAVA”或“GROOVY”将出现在代码块的右上角。 |
---|
> Graph graph = TinkerGraph.open();
> *// add a software vertex with a name property*
> Vertex gremlin = graph.addVertex(T.label, "software",
> "name", "gremlin"); 1
> *// only one vertex should exist*
> **assert**(IteratorUtils.count(graph.vertices()) == 1)
> *// no edges should exist as none have been created*
> **assert**(IteratorUtils.count(graph.edges()) == 0)
> *// add a new property*
> gremlin.property("created",2009)
> *// add a new software vertex to the graph*
> Vertex blueprints = graph.addVertex(T.label, "software",
> "name", "blueprints"); //
> *// connect gremlin to blueprints via a dependsOn-edge*
> gremlin.addEdge("dependsOn",blueprints); //
> *// now there are two vertices and one edge*
> **assert**(IteratorUtils.count(graph.vertices()) == 2)
> **assert**(IteratorUtils.count(graph.edges()) == 1)
> *// add a property to blueprints*
> blueprints.property("created",2010) //
> *// remove that property*
> blueprints.property("created").remove() //
> *// connect gremlin to blueprints via encapsulates*
> gremlin.addEdge("encapsulates",blueprints)
> **assert**(IteratorUtils.count(graph.vertices()) == 2)
> **assert**(IteratorUtils.count(graph.edges()) == 2)
> *// removing a vertex removes all its incident edges as well*
> blueprints.remove() //
> gremlin.remove() //
> *// the graph is now empty*
> **assert**(IteratorUtils.count(graph.vertices()) == 0)
> **assert**(IteratorUtils.count(graph.edges()) == 0)
> *// tada!*
| IMPORTANT |
~ groovy logo
Gremlin-Groovy利用*Groovy
2。x语言*来表达Gremlin遍历。Groovy的主要好处之一是包含了一个运行时控制台,它使开发人员可以轻松地使用Gremlin语言进行实践联系,并使生产用户可以以交互的方式连接到他们的图并执行遍历。此外,Gremlin-Groovy提供了更为简洁的语法。
| TIP |
~ gremlin sugar
如果希望使用Gremlin2语法,请参阅SugarPlugin。这个插件通常在运行时提供语法糖。它可以通过编程方式加载SugarLoader.load
()。一旦加载,可以使用g.V.out.name替代g.V().out().values(‘name’) 进行访问,还有许多其他的便利等待发现。
下面是相同功能的代码,在Gremlin控制台中使用了Gremlin- groovy。
> \$ bin/gremlin.sh
> \\,,,/
> (o o)
> \-----oOOo-(3)-oOOo-----
> gremlin\> graph = TinkerGraph.open()
> ==\>tinkergraph[vertices:0 edges:0]
> gremlin\> gremlin = graph.addVertex(label,'software','name','gremlin')
> ==\>v[0]
> gremlin\> gremlin.property('created',2009)
> ==\>vp[created-\>2009]
> gremlin\> blueprints = graph.addVertex(label,'software','name','blueprints')
> ==\>v[3]
> gremlin\> gremlin.addEdge('dependsOn',blueprints)
> ==\>e[5][0-dependsOn-\>3]
> gremlin\> blueprints.property('created',2010)
> ==\>vp[created-\>2010]
> gremlin\> blueprints.property('created').remove()
> ==\>null /
> gremlin\> gremlin.addEdge('encapsulates',blueprints)
> ==\>e[7][0-encapsulates-\>3]
> gremlin\> blueprints.remove()
> ==\>null
> gremlin\> gremlin.remove()
> ==\>null
- A
=null 输出通常来自一个void方法调用,通常这样调用没有问题。如果有问题,将输出错误或抛出异常。
IMPORTANT | TinkerGraph不是一个事务性图库。有关事务处理(对于那些支持事务处理的图形系统)的更多信息,请参阅章节transactions.。 |
---|
图的加工过程
图数据库处理的主要方式是通过图形遍历(graph traversals)。TinkerPop3 process
API主要关注于允许用户通过使用在上一节定义的结构上以语法友好的方式创建图形遍历。遍历是一种根据图数据结构中显式的参考结构遍历图元素的算法遍历。例如:*“What
software does vertex 1’s friends work
on?”*这个英语语句可以用以下算法/遍历方式表示:
-
从顶点1开始。
-
遍历事件knows-edges到各自相邻的friend 顶点1。
-
通过created-edges从friend 顶点移动到software顶点。
-
最后,选择当前software顶点的name-property值。
Gremlin中的遍历是从TraversalSource派生的。GraphTraversalSource是贯穿文档的典型的“graph-oriented”DSL,在TinkerPop应用程序中很可能是最常用的DSL。GraphTraversalSource提供了两个遍历方法。
-
GraphTraversalSource。V(Object…id):生成从图中的顶点开始的遍历(如果没有提供id参数,则默认为查询所有顶点)。
-
GraphTraversalSource。E(Object…id):生成从图中的边开始的遍历(如果没有提供id参数,则默认为查询所有边)。
V()和E()的返回类型是GraphTraversal。GraphTraversal 维护许多返回GraphTraversal 的方法。通过这种方式,GraphTraversal 支持组合方法。GraphTraversal 的每个方法都称为一个step,每个step都用五种常用方法中的一种来调整前一步的结果。
-
map:将传入遍历器的对象转换为另一个对象(S→E)。
-
flatMap:将传入遍历器的对象转换为其他对象的迭代器(S→E*)。
-
filter:根据过滤条件来决定是否允许遍历器继续进行下一步(S→E⊆S)。
-
sideEffect:在加工过程中转换器保持不变,但过程中可通过sideEffect
来特殊指定一些计算(S ↬ S)。 -
分支:拆分遍历器并将每个遍历器发送到遍历中的任意位置(S→{S1→E*,…,Sn→E*}→E*)。
GraphTraversal中的每一个step均是MapStep、FlatMapStep、FilterStep、SideEffectStep或BranchStep的扩展调用。
TIP | GraphTraversal是一个独异点因为它是只有一个组合的二元运算的代数结构,。二元操作是一系列方法的组合(即方法链接),其一致性是通过step identity()来进行识别。这样就保持了函数式编程中所述的单一原则。 |
---|
给定TinkerPop图,下面的查询将返回marko-vertex认识的所有人的名字。下面的查询使用Gremlin-Groovy演示。
> \$ bin/gremlin.sh
> \\,,,/
> (o o)
> \-----oOOo-(3)-oOOo-----
> gremlin\> graph = TinkerFactory.createModern()
> ==\>tinkergraph[vertices:6 edges:6]
> gremlin\> g = graph.traversal()
> ==\>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
> gremlin\> g.V().has('name','marko').out('knows').values('name')
> ==\>vadas
> ==\>josh
-
打开toy graph,通过变量graph引用。
-
使用标准的OLTP遍历引擎从图中创建一个图遍历源。
-
从遍历源再派生出一个遍历,该遍历将从name属性=marko的节点中,符合out定义下knows条件的查询里,取出name属性。
图2。马克认识的人的名字
或者,如果marko-vertex已经通过直接引用指针(即变量)实现,那么遍历就可以从那个顶点派生出来。
> gremlin\> marko = g.V().has('name','marko').next()
> ==\>v[1]
> gremlin\> g.V(marko).out('knows') \\
> ==\>v[2]
> ==\>v[4]
> gremlin\> g.V(marko).out('knows').values('name') /\\
> ==\>vadas
> ==\>josh
-
将变量marko设置为图g中名为marko的顶点。
-
通过已知边获得与标记顶点相邻的out顶点。
-
获取marko-vertex out邻节点的名字。
遍历器Traverser
当执行遍历时,遍历的源在表达式的左边(例如顶点1),step在遍历的中间(例如. out(‘knows’))和values(‘name’)
),结果在遍历的右边(例如:name)是“traversal.next()’。“vadas”和“josh”)。
在TinkerPop3中,通过遍历传播的对象包装在一个Traverser 中。Traverser是TinkerPop3的新概念,它提供了使步骤保持无状态的方法( steps
remain
stateless)。遍历器维护关于遍历的所有元数据,例如。遍历器遍历一个循环的次数,遍历器的历史路径,当前被遍历的对象,等等。可以通过一个步骤访问Traverser 元数据。例如使用path()这个step
。
> gremlin\> g.V(marko).out('knows').values('name').path()
> ==\>[v[1],v[2],vadas]
> ==\>[v[1],v[4],josh]
WARNING | 路径计算的空间开销很大,因为前面看到的对象数组存储在各自遍历器的每个路径中。因此,遍历策略分析遍历以确定是否需要路径元数据。如果没有必要,则关闭路径计算。 |
---|
另一个例子是repeat()步骤,它考虑遍历器遍历表达式的特定部分(即循环)的次数。
> gremlin\> g.V(marko).repeat(out()).times(2).values('name')
> ==\>ripple
> ==\>lop
WARNING | 遍历的结果永远不会排序,除非显式地调用 order()。因此,不要依赖TinkerPop3版本之间的迭代顺序,在一个版本中也一样(因为遍历优化可能会改变流程)。 |
---|
Gremlin语言的衍生版本
Gremlin是用Java 8编写的。Gremlin有多种语言实现版本,比如Gremlin-
groovy(TinkerPop3已包含 )、Gremlin- python(TinkerPop3已包含)、Gremlin-
scala、Gremlin- javascript、Gremlin- clojure(称为Ogre)等。
Gremlin的优势在于它是一种不需要指定编程语言的图遍历类型。在开发人员熟悉的编程语言中,有一种他们可以使用的Gremlin变体,它利用了该语言的习惯用法。至少,提供Gremlin实现的编程语言必须支持链式编程(在复杂或特别的Gremlin
Step中,推荐使用
lambdas/anonymous
等函数形式来完成该步骤的计算)。
在整个文档中,提供的示例主要是用Gremlin-Groovy编写的。原因是Gremlin
Console
——一个不需要代码编译的交互式编程环境。对于致力于学习TinkerPop3并以特别的方式与实时图形系统打交道的情况来说,使用Gremlin控制台当之无愧。但是,对于对开发人员则提供Gremlin-Java
方式访问,下面提供了一些groovto - java模式。
> g.V().out('knows').values('name') //
> g.V().out('knows').map{it.get().value('name') + ' is the friend name'} //
> g.V().out('knows').sideEffect(System.out.&println)
> g.V().as('person').out('knows').as('friend').select('person','friend').by{it.value('name').length()}
> /
> g.V().out("knows").values("name")
> g.V().out("knows").map(t -\> t.get().value("name") + " is the friend name")
> g.V().out("knows").sideEffect(System.out::println)
> g.V().as("person").out("knows").as("friend").select("person","friend").by((Function\<Vertex,
> Integer\>) v -\> v.\<String\>value("name").length())
> //
-
在Gremlin-Groovy和Gremlin-Java中,非lambda的 step
chain是相同的。同时请注意,Groovy支持 字符串的单引号和双引号两种写法。 -
在Groovy中,lambdas被称为闭包,具有不同的语法,其中Groovy支持it关键字,而Java不支持此类写法,需要命名所有参数。
-
Java和Gremlin-Groovy的方法引用语法略有不同。
-
Groovy对对象类型不敏感,而Java不是。如果lambda的参数类型未知,则需要强制类型转换。
有关对象类型的更多信息,请参见Gremlin Variants章节。
图形系统集成
TinkerPop是一个由各种交互组件组成的框架。在 core TinkerPop3
API
的基础上就定义了Graph, Vertex, Edge等等。图形系统提供者至少必须实现core
API。一旦实现,Gremlin遍历语言可用于图形系统的用户。同时,图服务提供者可以更进一步,具体开发TraversalStrategy允许图系统在运行时检查Gremlin查询并针对其特定实现对其进行优化的优化(例如索引查找、步骤重新排序等优化)。如果图系统是一个图形处理器(即提供OLAP功能),则系统应该实现GraphComputerAPI。这个API定义了
message/traverser
如何在通信工作者(即线程或机器)之间传递。实现之后,对图数据库(OLTP)和图处理器(OLAP)执行相同的Gremlin遍历。请注意,Gremlin语言根据Vertex和Edge来解释图——即。Gremlin是一种基于图形的领域特定语言。用户可以创建自己的领域特定语言来处理高阶结构(如人、公司及其各种关系)的图。最后,Gremlin
Server可以与支持tinkerpop的图系统进行通信。Gremlin
库(OLTP)和图处理器(OLAP)执行相同的Gremlin遍历。请注意,Gremlin语言根据Vertex和Edge来解释图——即。Gremlin是一种基于图形的领域特定语言。用户可以创建自己的领域特定语言来处理高阶结构(如人、公司及其各种关系)的图。最后,Gremlin
Server可以与支持tinkerpop的图系统进行通信。Gremlin
Server提供了一个可配置的通信接口以及度量和监视功能。以上就是TinkerPop的系统模型。