tinkerpop是一个图库标准,一个框架,学习图库,先从这个项目入手比较合适, neo4j, janusGraph只是它两个组件(图storage-engine)的vendor而已。图库是节点&边的集合,边描述了节点间的关联关系。
打开gremlin-console,我们可以通过groovy语言对图进行curd操作,也可以使用gremlin语法进行遍历
$ bin/gremlin.sh
\,,,/
(o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
gremlin>
ourist过程用到的数据库,可视化展示如下:
tinkerpop提供了一个内存图库,并提供了上图demo数据,加载数据
gremlin> graph = TinkerFactory.createModern()
==>tinkergraph[vertices:6 edges:6]
gremlin> g = graph.traversal()
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
开始遍历
gremlin> g.V() //1
==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]
gremlin> g.V(1) //2
==>v[1]
gremlin> g.V(1).values('name') //3
==>marko
gremlin> g.V(1).outE('knows') //4
==>e[7][1-knows->2]
==>e[8][1-knows->4]
gremlin> g.V(1).outE('knows').inV().values('name') //5
==>vadas
==>josh
gremlin> g.V(1).out('knows').values('name') //6
==>vadas
==>josh
gremlin> g.V(1).out('knows').has('age', gt(30)).values('name') //7
==>josh
gremlin查询语法就不在此赘述了,请查阅官网文档。
tinkerpop3 模型核心概念
首先必须明确tinkerpop自带的内存图库(TinkerGraph)是全内存存储,数据条目不会太多。数据结构也就是标准的图的结构,持久化存储方式可参考janusGraph图库方式 让我们先了解Graph,vetex,Edge等数据结构。
Graph成员变量,可以看到有vertices,edges两个集合,分别管理节点、边。
protected Map<Object, Vertex> vertices = new ConcurrentHashMap<>();
protected Map<Object, Edge> edges = new ConcurrentHashMap<>();
自身的成员变量有属性,出度,入度的edge集合
protected Map<String, List<VertexProperty>> properties;
protected Map<String, Set<Edge>> outEdges;
protected Map<String, Set<Edge>> inEdges;
private final TinkerGraph graph; //引用
一条边有传递方向,成员变量有入度及出度的节点
protected Map<String, Property> properties;
protected final Vertex inVertex;
protected final Vertex outVertex;
这样就完成了图的组织,可以看的出来从任意图中的一个起始节点,可以先找到出度的边,然后查询边的出度节点,这样travesal就跳到了下一个节点,反复如此即可完成对图的遍历。
核心在于提供gremlin查询语法及引擎,类似sqlparse,把查询语言转变成执行计划。还有core-api 节点,边的抽象,为底层OLTP&OLAP引擎可以自由切换成其他厂商实现,当然也内嵌了一套内存图库实现,以供vendor参考。
oltp vs olap
OLTP对于图的写操作都比较简单,我们就不讲了。我们来了解下traversal操作 GraphTraversal是由一组step组成,任何gremlin语法都会最终生成一个traversal,由多个步骤组成,如下示例
g.V(1).out("knows").values("name").iterate()
==>[TinkerGraphStep(vertex,[1]), VertexStep(OUT,[knows],vertex), PropertiesStep([name],value), NoneStep]
每个步骤都会变换上个步骤的输出,像管道pipe一样,抽像出以下5种变换方式,每个step其实都从这5种方式派生出来。
图示
gremlin> g.V(marko).out('knows').values('name')
==>vadas
==>josh
当开始执行traversal时,traversal的源在表达式的左边(示例中的vertex1,marko节点)这些steps在traversal中间(示例种 out(‘knows’)以及values(‘name’)) 通过不断执行”traversal.next”输出到右边的结果(示例中的’vadas’和’josh’)
GraphTraversal通过了顶点,边等提供了对图数据的一种解释,并因此提供图形遍历DSL。S是起点,E是终点,包含如下4个主要组件
tinkerpop自带的图库基于内存,demo例子而已,我们看看其他一些供应商使用的一些持久化方案。
janusGraph集成了各大开源存储系统,如hbase,Cassandra,BerkeleyDB,以及整合开源搜索引擎,如solr, ElasticSearch. 总体来说实现了一个OLTP图库,OLAP标准在tinkerpop框架里面是可选的,我们暂时不关心janusGraph在OLAP方面工作.因为我们生产环境只使用hbase+solr,其他组件实现功能是镜像的,重点分析hbase+solr模式就好了。
重点在强调scale-up
我们关注下OLTP方面,主要有api层,实现tinkpop api,底层storage api,这些是跟hbase/solr等打交道的,比较重要的工作在于database逻辑层,包括事务, 数据管理(节点、边curd),优化。 可以看出janusGraph功能还是比较少的,主要精力在数据建模方面,事务实现方面,底层hbase,solr都不支持事务,所以在hbase+solr模式下不支持事务,这方面我们也可以略过。
JanusGraph将邻接表按行row保存在后台存储中。使用64位的顶点Id作Key指向相应顶点的邻接表row。每个边或属性在row中都是一个独立的cell,并且这些cell可以高效的完成插入和删除。每行(row)可以存储的cell最大数在hbase做存储场景下没限制,schema free随意新增列。
后端存储hbase key全局有序保存,指向性query/range query效率很高,key是vetex id,没有前缀匹配场景。
每个边或者属性会保存在顶点的邻接表row的cell中。序列化之后的column数据字节序也反映了原来的Edge标签的key序。一个体系的ID编码和压缩的对象序列化易于使得每个cell保持尽可能少地占用后端存储空间。 一条边信息会被出度、入度vetex保留两遍,便于快速定位到邻接节点,可避免表级联查询。 从当前节点拿到edge信息,拿到邻接vetex id,再做一次指向性query即可traversal到其他节点。
推荐系统中,总有类似关联推荐
如:用户A喜欢某些item,推荐有相同兴趣其他用户所喜欢的item给用户A,在图库里面很容易实现。
/*"What has userA liked? Who else has liked those things? What have they liked that userA hasn't already liked?"*/
g.V(userA).out('liked').aggregate('x').in('liked').out('liked').
where(without('x')).values('name')
在搜索引擎中作为知识图谱弥补自然语言处理的不足 众所周知搜索引擎使用全文搜索的技术,本质上是term->document倒排索引,如下query ”XX明星的老婆的弟弟的舅舅的儿子叫什么“ 使用全文搜索方式完全丧失了答案的正确性,使用图数据库轻而易举能得到正确答案。另外edge提供权重属性,能帮助搜索引擎做rank打分。
g.V(star).out("wife").out("brother").out("uncle").out("son").values("name")