gremlin图遍历语言

图数据库介绍

NoSQL(Not Only SQL,非关系型数据库)的数据存储模型分4类:

  • 键值存储库(Key-Value-Stores)
  • 列存储 (Column-Based-Stores)
  • 文档库(Document-Stores)
  • 图数据库(Graph Database)

图数据库(Graph Database)是指以图表示、存储和查询数据的一类数据库。这里的“图”,与图片、图形、图表等没有关系,而是基于数学领域的“图论”概念,通常用来描述某些事物之间的某种特定关系。例如,

  • 社交网络是图。每个社交网络的参与者是节点,我们在社交网络中的交互,例如“加好友”“点赞”就是连接节点的边。
  • 城市交通是图。每个路口、门牌号、公交站点等都是节点,街道或者公交线路是边,将可以到达的地方连接起来。
  • 知识也是图。每个名称、概念、人物、事件等都是节点,而类属关系、分类关系、因果关系等是边,将节点连接起来,形成庞大、丰富并且随时在演变的知识图谱。

可以说,“图无处不在”(Graphs are everywhere),也正因如此,传统关系型数据库不擅长处理关系的问题,能够被图数据库很好地解决,图数据库正是为解决这一问题而生。

其实,在某些方面,图数据库就像新一代的关系数据库,区别在于图数据库不仅存储实体,还存储实体之间的关系。关系型数据库通过“主键-外键”表示隐含的“关系”连接,但实际上这里的“关系”是关系代数中的概念,与我们现实世界中的“关系”不同。

通过将关系预先物理存储在数据库中(我们称之为“原生”),图数据库将查询性能由原先的数分钟提高到数毫秒,特别是对于JOIN频繁查询,这种优势更加明显。图2中比较了在社交网络数据集上搜索朋友圈的查询,在原生的图数据库和关系数据库的查询执行效率。显然,使用图数据库比使用传统关系数据库效率有极大提升。

在这里插入图片描述
作为NoSQL数据库的一种,图数据库通常不需要先定义严格的数据模式,以及强制的字段类型,这使其在处理结构化和半结构化的数据时同样得心应手。

除了存储和查询效率方面的优势,图数据库也拥有更加丰富的分析能力,我们通过比较这四类主要的非关系型数据库特点(见表1),就可以得知。

在这里插入图片描述

图数据库主要领域

图数据库的主要技术领域包括存储模式、图模型、图查询语言、图分析以及图可视化。
在这里插入图片描述

图数据库存储模式

图数据库以节点和边来对现实世界进行数据建模。对于实际的底层物理存储技术,目前主流有两大类方法:

  • 原生(Native),即按照节点、边和属性组织数据存储。典型代表有Neo4j、JanusGraph、TigerGraph等。

  • 非原生(Non-Native),使用其他存储类型。例如基于列式存储的DataStax、基于键值对的OrientDB和Nebula Graph以及基于文档的MongoDB。部分关系型数据库也在关系存储之上提供类似图的操作。

有的图计算平台底层支持各类存储技术,包括图存储,称作“多模式”,例如百度HugeGraph。

原生的图存储由于针对图数据和图操作的特点进行了优化,并且从物理存储到内存中的图处理,都采用一致的模型而无需进行“模式转换”,在大数据量、深度复杂查询以及高并发情况下,性能普遍优于非原生的图存储。

图的分布式存储

为了支持大规模的图存储和查询,需要对图进行分布式存储。这里有两类分布式的实现方法——分片和分库。

分片(Sharding)

分片就是根据某一原则(例如根据节点的ID随机分布)将数据分布存储在多个存储实例中。根据切分规则,又可以分为:

  • 按点切分:每条边只保存一次,并且出现在同一个分区上。如果处于不同分区的两条边有共同的点,那么点会在各自的分区中复制。这样,邻居多的点(繁忙节点)会被分发到多个分区上,增加了存储空间,并且有可能产生同步问题。这种方法的好处是减少了网络通信。

  • 按边切分:通过边切分之后,顶点只保存一次,切断的边会打断保存在不同分区上。在基于边的操作时,对于两个顶点分到两个不同分区的边来说,需要通过网络传输数据。这增加了网络传输的数据量,但好处是节约了存储空间。

出于优化性能的考虑,目前按点切分的分布式图更加常见。

分库(Partitioning)

由于现实世界中的图往往遵循“幂律分布”,即少数节点拥有大量的边,而多数节点拥有很少的边。分片存储不可避免地会造成大量数据冗余复制,或增加分区间网络通信的负担。因此,另外一种分布式的方法是分库。这是借助图建模的方法,将节点按照业务需求、根据查询类型分布在不同库中,是最小化跨库的网络传输。不同库中的数据则通过联邦式查询(Federated Query)实现。

图模型

在基于图的数据模型中,最常见的两种方法是资源描述框架(Resource Description Framework,RDF)和标签属性图(Labelled Property Graph,LPG)。

RDF(Resource Description Framework,资源描述框架)

RDF是W3C组织指定的标准,它使用Web标识符(URI)来标识事物,并通过属性和属性值来描述资源。根据RDF的定义:

  • 资源是可拥有URI的任何事物,比如 “http://www.w3school.com.cn/rdf”;
  • 属性是拥有名称的资源,比如"author"或"homepage";
  • 属性值是某个属性的值,比如"David"或"http://www.w3school.com.cn"(请注意一个属性值可以是另外一个资源)。

我们来看看RDF是怎样描述 “西湖是位于杭州的一个旅游景点”这个事实的,
在这里插入图片描述

LPG(Labelled Property Graph,标签属性图)

在LPG属性图模型中,数据对象被表示成节点(拥有一个或多个标签)、关系和属性。我们用下面的例子来说明,
在这里插入图片描述

  • 节点/顶点是事物(Object)或者实体(Entity)的抽象,可以是“人”“导演”“电影”“演员”等抽象。节点可以拥有一个或多个标签,例如代表“张艺谋”的节点可以有“个人”“导演”“演员”等标签。
  • 节点的属性。节点的属性为节点提供丰富的语义,根据顶点代表的类型不同,每个顶点可以有不同的属性,比如以“人”作为顶点,属性可以是“姓名”“性别”等。
  • 边/关系。边连接两个节点或同一个节点(指向自己的边),边可以有向或无向。边可以有类型,比如连接“李连杰”和“英雄”的边的类型是“主演”。
  • 边的属性。和顶点的属性类似,每条边上也可以有属性。比如连接“李连杰”和“英雄”的边有属性“角色”,其值是“无名”。

相比RDF,LPG由于可以在节点和边上定义丰富的属性,更加易于我们理解,建模也更加灵活。

图查询语言

从查询语言本身来说,主要有两类:

  • 声明型(Declarative)。声明型查询语言只要求使用者描述要实现的目标,由查询引擎分析查询语句、生成查询计划然后执行。SQL是声明型查询语言。在图数据库领域,Cypher是最流行的声明型查询语言。

  • 命令型(Imperative)。命令型查询语言要求使用者描述具体执行的操作步骤,然后由数据库执行。在图数据库领域,Gremlin是最流行的(近似)命令型的查询语言。

从未来的发展趋势来看,声明型查询语言由于其易于理解、学习门槛低、便于推广等特性,将成为主流的图查询语言。智能、优化的查询执行引擎将成为衡量图数据库技术优势的关键。

图查询语言演进

图查询语言是用于对图数据进行查询和操作的编程语言。随着图数据库的兴起和图数据的应用场景逐渐增多,图查询语言也在不断进化。
在这里插入图片描述

图查询语言的主要演进方向

  1. 查询表达能力的提升:
    图查询语言的进化首先是为了提升对图数据的查询表达能力。新的图查询语言不仅支持更复杂的查询模式和条件,还提供更丰富的查询操作。例如,一些现代的图查询语言支持路径查询、连接查询、聚合查询等。
  2. 性能的优化:
    进化的图查询语言不仅关注查询的灵活性,还注重提高查询的性能。新的语言通过引入索引、优化查询执行计划等手段,提升查询的效率和响应速度。
  3. 易用性的提升:
    为了让开发者更加方便地使用图查询语言,新的语言在语法设计上更加简洁和易读。同时,还提供了更多的工具和文档,使开发者更容易理解和使用语言。

目前主要的图查询语言

Cypher

Cypher 是用于 Neo4j 图数据库的查询语言。它采用类似 SQL 的声明式语法,通过模式匹配和图模式描述来查询和操作图数据。Cypher 具有易学易用的特点,同时支持复杂查询和图形可视化。

SPARQL

SPARQL 是用于 RDF 数据的查询语言,也可以用于查询图数据。它采用类似 SQL 的语法,支持模式匹配、图模式描述、连接查询等。SPARQL 具有丰富的查询功能和强大的表达能力,但学习难度相对较高。

GQL

GQL 是一种通用的图查询语言,与特定的图数据库无关。它是由图查询工作组开发的标准语言,旨在提供统一的图查询接口。GQL 集成了 Cypher、SPARQL 和其他图查询语言的优点,具有较高的灵活性和易用性。

Gremlin

Gremlin 是一种图遍历语言,适用于各种图数据库。它通过遍历图的顶点和边来执行查询,具有非常强大的表达能力和灵活性。Gremlin 的语法较为低级,适合有一定图数据库使用经验的开发者。

对比

在这些图查询语言中,Cypher 注重易用性和可视化,SPARQL 注重表达能力和查询复杂性,GQL 注重通用性和统一接口,而 Gremlin 注重表达能力和灵活性。因此,开发者在选择图查询语言时可以根据自己的需求和经验进行选择。

Gremlin简介

Gremlin 是在某些领域专用的语言,用来遍历属性图(property graphs)。Gremlin 使用 Pipes 来遍历复杂的图。这个语言在图像查询、分析、操作领域有所应用。

目前图数据库领域最主流的两种查询语言为 Cypher 和 Gremlin。Cypher 是数据库Neo4j中实现的属性图数据查询语言。与SPARQL一样,Cypher 也是一种声明式语言,即用户只需要声明“查什么”,而无须关心“怎么查”。

Gremlin是Apache TinkerPop图计算框架提供的属性图查询语言。 Apache TinkerPop被设计为访问图数据库的通用API接口,其作用类似于关系数据库上的JDBC接口。Gremlin的定位是图遍历语言,其执行机制好比是一个人置身于图中沿着有向边,从一个节点到另一个节点进行导航式的游走。这种执行方式决定了用户使用Gremlin需要指明具体的导航步骤。

示例

在这里插入图片描述

gremlin查询过程

gremlin的查询是流式查询,一步一步的进行下去,当然这里的“一步”可能是一个方法(g.V().has())也可能是多个方法组成的一步(g.V().order().by(desc,‘age’))。下面看一个案例

g.V().has('code','AUS').out().value('name','age').order().by('age',desc)
  1. g.V() 标明是对图库中的所有节点进行操作的
  2. has(‘code’,‘AUS’) 获取包含属性code并且该属性的值为AUS的所有节点
  3. out() 获取上个结果集中所有节点的出边对应的节
  4. value(‘name’,‘age’) 获取上个结果集中所有节点的name和age属性值
  5. order().by(‘age’,desc) 对结果集根据age进行降序排序

从上面便可以看出gremlin流式执行的特征,这使得gremlin的查询语句可以十分的灵活,从而满足我们的各种查询需求。想要了解更多全面的查询方法,可以看文末链接1。

常用的查询方法

  1. 首先,这里的g.V()中的g为遍历实例,其创建为:
graph = TinkerGraph.open()
g = graph.traversal()
  1. V()与E()
    在下面的例子中,你会发现几乎每一个查询的开始都会有他们的存在
    V()代表查看图中的所有节点,接下来的操作是对节点进行操作的
    E()代表图中的所有边,接下来的操作就是对边操作的

  2. 使用value获取节点的某一个属性值

g.V().has('code','AUS').out().value('name','age')
//获取AUS的出边对应节点的name和age属性的值
g.V().has('code','AUS').out().value()
//显示所有的属性值
  1. 使用has、hasNot获取(不)包含某一属性值的节点
g.V().has('code','AUS')
//获取拥有code属性并且其属性值为AUS的节点
g.V().has('user','code','AUS')
//相当于 g.V().hasLabel('user').has('code','AUS')
g.V().hasNot('name')
//获取所有不包含name属性的节点,等同于g.V().not(has('name'))
  1. 使用 hasLabel获取label为某值得节点
g.V().hasLabel("user")
//获取label为user的节点

6、使用hasNext方法判断两个节点中是否有查询的边

返回值为boolean类型参数,存在则返回true,不存在则false

g.V().has('code','AUS').out('route').has('code','DFW').hasNext()
 
true
 
g.V().has('code','AUS').out('route').has('code','SYD').hasNext()
 
false
  1. 使用range()获取某一范围内的数据
g.V().hasLabel('airport').range(0,2)
//输出结果集中1,2个节点
g.V().hasLabel('airport').range(3,6)
//输出结果集中4,5,6个节点
g.V().range(3500,-1)
//输出结果集中3500往后的所有节点
  1. 使用skip跳跃节点查找
g.V().has('region','US-TX').skip(5)
//跳过节点集中的前5条数据,从第六条开始,效果等同于下面的语句
g.V().has('region','US-TX').range(5,-1)
  1. 使用order对结果集进行排序
g.V().has('code','AUS').out().order().by()
g.V().has('code','AUS').out().order(local).
g.V().has('code','AUS').out()
g.V().has('code','AUS').out()
  1. 使用 out,in进行查找结点的出边和入边所对应的节点
g.V().has('code','AUS').out()
//获取AUS的节点所有出边对应的节点
g.V().has('code','AUS').out("brought")
//获取AUS节点所有边关系为“brought”的出边对应的节点
g.V().has('code','AUS').in()
//获取AUS的节点所有入边对应的节点
g.V().has('code','AUS').in("brought")
//获取AUS节点所有边关系为“brought”的入边对应的节点
  1. 使用count、groupCount对结果集计数
g.V().has('code','AUS').out().count()
//获取AUS节点的出边的个数
g.V().has('code','AUS').out().groupCount().by("name")
//根据结果集的name属性的值进行分组计数,最终结果类似于:[a:1,b:3,r:6]
  1. 使用dedup进行去重
g.V().has('code','AUS').out().out().dedup().count()
//步骤解读:
1:获取AUS节点的两度出节点,用dedup对结果进行去重
2:使用count()对结果集进行计数
  1. 使用aggregate创建一个临时集合
//获取AUS节点的大于两度出度的节点个数,注意应该不包含一度的节点
g.V().has('code','AUS').out().aggregate('nonstop').
     out().where(without('nonstop')).dedup().count()
//第一行获取一度的节点并将其结果集存储为一个临时集合命名为"nonstop"
//第二行获取二度节点,并且使用临时集合去除掉一度节点,去重,计数
  1. 使用limit、tail、timeLimit限制结果数量
g.V().hasLabel('airport').values('code').limit(20)
//只显示前20个
g.V().hasLabel('airport').values('code').tail(20)
//只显示最后20个
g.V().has('airport','code','AUS').
      repeat(timeLimit(10).out()).until(has('code','LHR')).path().by('code')
//上述作用:获取在10毫秒内查询到的结果
  1. 使用outE、inE或outV、inV指定方向的边
g.V().has('code','AUS').outE().inV().path()
g.V().has('code','AUS').inE().outV().path()
//获取出边或者入边
g.V().has('code','AUS').outE('brought').inV().path()
g.V().has('code','AUS').inE('brought').outV().path()
//获取指定的边关系的出边入边
  1. 获取两个节点之间的边
g.V().has('code','MIA').
    outE().as('e').inV().has('code','DFW').
    select('e')
//第一步:选择源节点
//第二步:outE找到所有出边as('e')将结果存储为标签e
//第三步:inV().has('code','DFW')找到前面结果集的边入节点为code属性为DFW值得节点
//将边显示出来:结果类似于:
e[4127][16-route->8]
这样就获取到了两个节点之间的边
  1. 使用as,select和project来引用遍历步骤,其中as可以将前一个步骤结果集临时存储下来,便于下面使用
g.V().has('code','DFW').as('from').out().
      has('region','US-CA').as('to').
      select('from','to')
//has('code','DFW').as('from')   :将has('code','DFW')的结果集标识为from标签的临时结果,下面使用的时候直接使用from即可
//返回的结果类型
[from:v[8],to:v[13]]
[from:v[8],to:v[23]]
[from:v[8],to:v[24]]

g.V().has('type','airport').limit(10).as('a','b','c').
      select('a','b','c').
        by('code').by('region').by(out().count())
//返回结果为:
[a:ATL,b:US-GA,c:232]
[a:ANC,b:US-AK,c:39]
[a:AUS,b:US-TX,c:59]
  1. project()相当于select和by共同使用的效果
g.V().has('type','airport').limit(10).
      project('a','b','c').
        by('code').by('region').by(out().count())
//效果等同于:只不过上面的写法更加简洁
g.V().has('type','airport').limit(10).as('a','b','c').
      select('a','b','c').
        by('code').by('region').by(out().count())
//输出结果:
[a:ATL,b:US-GA,c:232]
[a:ANC,b:US-AK,c:39]
[a:AUS,b:US-TX,c:59]
  1. 相同标签的处理方式,在select中使用first,last,all参数
g.V(1).as('a').V(2).as('a').select(first,'a')
v[1]
//选择第一个a标签
g.V(1).as('a').V(2).as('a').select(last,'a')
v[2]
//选择最后一个a标签
g.V(1).as('a').V(2).as('a').select(all,'a')
[v[1],v[2]]
//选择所有a标签
g.V().has('code','AUS').as('a').
      out().as('a').limit(10).
      select(last,'a').by('code')

g.V().has('code','AUS').as('a').
      out().as('a').limit(10).
      select(first,'a').by('code')

g.V().has('code','AUS').as('a').
      out().as('a').limit(10).
      select(all,'a').unfold().values('code')
  1. 使用valueMap获取节点或者边的属性

返回结构:kv对数组,key:属性key,v:属性的值列表(list,这样可以显示该属性对应的多个值)

结构类似于:

[country:[US], code:[AUS], longest:[12248], city:[Austin], elev:[542], icao:[KAUS], lon:[-97.6698989868164], type:[airport], region:[US-TX], runways:[2], lat:[30.1944999694824], desc:[Austin Bergstrom International Airport]]

g.V().has('name','gremlin').valueMap()
//获得节点的所有属性
//valueMap在默认情况下不显示ID和label值,必须添加true参数
g.V().has('name','gremlin').valueMap(true)
//返回的集合中包含ID和label值
g.V().has('code','AUS').valueMap(true,'region')
//返回id+label+region三个属性的kv
g.E(5161).valueMap(true)
//返回id为5161边的属性

为了完整起见,还可以使用select来优化valueMap的结果

g.V().has('code','AUS').valueMap().select('code','icao','desc')
//返回的结果为 code+icao+desc属性的kv

如果想要结果集合更容易展现,可以使用unfold方法将其展开,但是结果的结构就变了,只是为了在 console上更加容易看

g.V().has('code','AUS').valueMap(true,'code','icao','desc','city').unfold()
//输出结果形式:
code=[AUS]
city=[Austin]
icao=[KAUS]
id=3
label=airport
desc=[Austin Bergstrom International Airport]
  1. 使用toList,toSet,bulkSet和fill创建集合
  • toList()创建结果集合为list集合,可重复,不排序,要想排序可以使用order方法
listr = g.V().has('airport','region','US-TX').
              values('runways').toList().join(',')
//此处的join(',')是将结果组合起来,用逗号分割,这样最终的结果就是一个字符串
//输出结果:
2,7,5,3,4,3,3,3,3,4,2,3,2,3,2,2,3,2,1,3,2,3,4,3,4,2

//在我们项目中使用一般不会加join,因为最终结果只会是一个字符串
listr = g.V().has('airport','region','US-TX').
        values('runways').toList()
//使用集合的一些操作:
-> listr[1]
7

-> listr.size()
26

-> listr[1,3]
7
3
  • toSet()创建结果集合为Set集合,不可重复
setr = g.V().has('airport','region','US-TX').
             values('runways').toSet().join(',')
//输出结果:
1,2,3,4,5,7
  • toBulkSet()将结果集相同数据放在连续的位置,其余与tolist相同
setb= g.V().has('airport','region','US-TX')
    .values('runways').toBulkSet().join(',')
//输出结果:
2,2,2,2,2,2,2,2,7,5,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,1

//不使用join
setb= g.V().has('airport','region','US-TX').values('runways').toBulkSet()
//一些操作
setb.uniqueSize()   
6

setb.size()
26

setb.asBulk()
2=8
7=1
5=1
3=11
4=4
1=1
  • fill方法将结果集填充入进一个自定义集合中
//案例一:
a = []
g.V().has('airport','region','US-TX').values('runways').fill(a)
//操作
a.size()
26

a[1,3]
73
//案例二:
s = [] as Setg.V().has('airport','region','US-TX').values('runways').fill(s)
//操作
println s
[2, 7, 5, 3, 4, 1]

相关模块

  • 核心模块
<dependency>
  <groupId>org.apache.tinkerpop</groupId>
  <artifactId>gremlin-core</artifactId>
  <version>3.0.2-incubating</version>
</dependency>
  • 图数据库连接模块
<dependency>
   <groupId>org.apache.tinkerpop</groupId>
   <artifactId>gremlin-driver</artifactId>
   <version>3.0.2-incubating</version>
</dependency>
  • Gremlin测试模块
<dependency>
  <groupId>org.apache.tinkerpop</groupId>
  <artifactId>gremlin-test</artifactId>
  <version>3.0.2-incubating</version>
</dependency>
<dependency>
  <groupId>org.apache.tinkerpop</groupId>
  <artifactId>gremlin-groovy-test</artifactId>
  <version>3.0.2-incubating</version>
</dependency>
  • 内存数据库实现模块
<dependency>
   <groupId>org.apache.tinkerpop</groupId>
   <artifactId>tinkergraph-gremlin</artifactId>
   <version>3.0.2-incubating</version>
</dependency>

学习如何使用 Gremlin 时,廷克图(TinkerGraph)是非常有用的,它也可以快速的运行出结果。常见的一个使用场景就是用廷克图来创建大型图的子图,并在本地使用它。

  • Neo4j(高性能、NOSQL图形数据库)模块
<dependency>
   <groupId>org.apache.tinkerpop</groupId>
   <artifactId>neo4j-gremlin</artifactId>
   <version>3.0.2-incubating</version>
</dependency>
<!-- neo4j-tinkerpop-api-impl is NOT Apache 2 licensed - more information below -->
<dependency>
  <groupId>org.neo4j</groupId>
  <artifactId>neo4j-tinkerpop-api-impl</artifactId>
  <version>0.1-2.2</version>
</dependency>
  • HadoopGraph模块
<dependency>
   <groupId>org.apache.tinkerpop</groupId>
   <artifactId>hadoop-gremlin</artifactId>
   <version>3.0.2-incubating</version>
</dependency>

参考链接:

1、gremlin_graph_guide
2、apache_doc_tinkerpop
3、图数据库介绍
4、aliyun_Gremlin查询语法
5、tinkerpop_doc_3.0.2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值