图解Janusgraph系列-查询图数据过程源码分析
大家好,我是洋仔,JanusGraph图解系列文章,实时更新
~
图数据库文章总目录:
- 整理所有图相关文章,请移步(超链):图数据库系列-文章总目录
- 地址:https://liyangyang.blog.csdn.net/article/details/111031257
源码分析相关可查看github(求star~~)
: https://github.com/YYDreamer/janusgraph
下述流程高清大图地址:https://www.processon.com/view/link/5f471b2e7d9c086b9903b629
版本:JanusGraph-0.5.2
转载文章请保留以下声明:
作者:洋仔聊编程
微信公众号:匠心Java
原文地址:https://liyangyang.blog.csdn.net/
一:查询场景
1.1 图数据
使用JanusGraph官方提供的测试图诸神之图
来测试,如下图:
具体的诸神之图
的创建分析,请看《JanusGraph-官方测试图:诸神之图分析》文章
1.2 查询语句
查询语句如下:
@Test
public void searchTest(){
// 获取name为hercules节点
GraphTraversal<Vertex, Vertex> herculesVertex = graph.traversal().V().has("name", "hercules");
// hercules节点战斗(battled)过12次的怪兽(monster)
GraphTraversal<Vertex, Vertex> monsterVertices = herculesVertex.outE().has(T.label, "battled").dedup().has("time", "12").inV();
// 获取monsterVertices节点对应的主人
GraphTraversal<Vertex, Vertex> plutoVertex = monsterVertices.inE("pet").outV().has("age", 4000);
if (plutoVertex.hasNext()){
Vertex next = plutoVertex.next();
// 输出主人的姓名
System.out.println(next.property("name"));
}
}
执行结果:
vp[name->pluto]
我们查询就是诸神之图
的左下角部分,包含了
- vertex的查找
- edge的查找
- property的过滤
- signature key的过滤(time)
- composite index的使用(name)
- 第三方索引支持的mixed index使用(age)
二: 查询流程分析
查询流程可以大致分为三部分:组装查询语句、优化查询语句、执行算子链
预备知识
gremlin语句官网:http://kelvinlawrence.net/book/Gremlin-Graph-Guide.html
在gremlin语句中,查询语句的类型其中包含三种哦:初始语句、中间语句、最终语句(触发执行查询语句)
- 初始语句:代表开始gremlin的语句,类似于V()、E()两种
- 中间语句:不会触发
查询语句执行
的语句,类似于has()、outE()、inV()等,大部分都是中间语句 - 最终语句:也叫作触发语句,这些语句会触发查询语句的
执行
! 访问图库并进行图数据查询;类似于hasNext()、toList()等语句
只有在执行到最终语句
部分时,gremlin语句才会真正的执行!
2.1 组装查询语句
在Janusgrahp
的查询中,首先做的就是组装查询对象; 根据我们写的Gremlin语句,将gremlin
语句拆分成不同的算子
;不同的语句组装成对应的算子步骤step
对象保存,生成一个查询执行算子step
链;
在组装查询语句时,所有的查询Gremlin语句,通常都是通过.V()
或者.E()
开头,在这两部分中会创建一个查询对象,用于存放之后的每一个查询step
;
例如上述执行查询语句,当执行完成以下语句时,便是进行组装查询对象,不没有真正的去查询底层存储的数据!
// 获取name为hercules节点
GraphTraversal<Vertex, Vertex> herculesVertex = graph.traversal().V().has("name", "hercules");
// hercules节点战斗(battled)过12次的怪兽(monster)
GraphTraversal<Vertex, Vertex> monsterVertices = herculesVertex.outE().has(T.label, "battled").dedup().has("time", "12").inV();
// 获取monsterVertices节点对应的主人
GraphTraversal<Vertex, Vertex> plutoVertex = monsterVertices.inE("pet").outV().has("age", 4000);
执行完成后,查看组装后的查询对象:
其中包含:图实例对象、事务对象、查询步骤链等,我们看下最终的查询步骤链
,在对象中steps
的属性:
steps = {ArrayList@5271} size = 10
0 = {GraphStep@5350} "GraphStep(vertex,[])"
1 = {HasStep@5351} "HasStep([name.eq(hercules)])"
2 = {VertexStep@5352} "VertexStep(OUT,edge)"
3 = {HasStep@5353} "HasStep([~label.eq(battled)])"
4 = {DedupGlobalStep@5354} "DedupGlobalStep"
5 = {HasStep@5355} "HasStep([time.eq(12)])"
6 = {EdgeVertexStep@5356} "EdgeVertexStep(IN)"
7 = {VertexStep@5357} "VertexStep(IN,[pet],edge)"
8 = {EdgeVertexStep@5358} "EdgeVertexStep(OUT)"
9 = {HasStep@5359} "HasStep([age.eq(4000)])"
上述的gremlin
查询语句主要被分为10个算子step
执行;
2.2 优化查询语句
在Gremlin
的查询优化中,存在由策略模式
设计实现的19个执行策略类,包含:
- Connective Strategy:连接策略
- Match Predicate Strategy:匹配谓词策略
- Filter Ranking Strategy:过滤排名策略
- Inline Filter Strategy:内联过滤策略
- Incident To Adjacent Strategy:相邻策略事件
- Adjacent To Incident Strategy:邻近事件策略
- Early Limit Strategy:早期限制策略
- Count Strategy:计数策略
- Repeat Unroll Strategy:重复展开策略
- Path Retraction Strategy:路径收回策略
- Lazy Barrier Strategy:惰性屏障策略
- Adjacent Vertex Has Id Optimizer Strategy:相邻顶点有Id优化器策略
- Adjacent Vertex Is Optimizer Strategy:相邻顶点是优化器策略
- Adjacent Vertex Filter Optimizer Strategy:相邻顶点过滤器优化器策略
- JanusGraph Local Query Optimizer Strategy:JanusGraph局部查询优化器策略
- JanusGraph Step Strategy:JanusGraph步骤策略
- JanusGraphIo Registration Strategy:JanusGraphIo注册策略
- Profile Strategy:配置文件策略
- Standard Verification Strategy:标准验证策略
将上述的查询算子链
循环执行所有的19个策略,针对不同的策略进行不同的处理;
我们主要收一下上述的Match Predicate Strategy
、:
Filter Ranking Strategy
依据优先级调整算子链
的算子执行顺序
! 采用while+for循环的方式,将所有的算子调整到对应的位置!
while (modified) {
modified = false;
final List<Step> steps = traversal.getSteps();
for (int i = 0; i < steps.size() - 1; i++) {
// do somthing
// update modified!
}
}
优先级如下,数字越大,优先级越低,执行的时机越靠后:
if (!(step instanceof FilterStep || step instanceof OrderGlobalStep))
return 0;
else if (step instanceof IsStep || step instanceof ClassFilterStep)
rank = 1;
else if (step instanceof HasStep)
rank = 2;
else if (step instanceof WherePredicateStep && ((WherePredicateStep) step).getLocalChildren().isEmpty())
rank = 3;
else if (step instanceof TraversalFilterStep || step instanceof NotStep)
rank = 4;
else if (step instanceof WhereTraversalStep)
rank = 5;
else if (step instanceof OrStep)
rank = 6;
else if (step instanceof AndStep)
rank = 7;
else if (step instanceof WherePredicateStep) // has by()-modulation
rank = 8;
else if (step instanceof DedupGlobalStep)
rank = 9;
else if (step instanceof OrderGlobalStep)
rank = 10;
调整过后的算子链:
steps = {ArrayList@5271} size = 10
0 = {GraphStep@5350} "GraphStep(vertex,[])"
1 = {HasStep@5351} "HasStep([name.eq(hercules)])"
2 = {VertexStep@5352} "VertexStep(OUT,edge)"
3 = {HasStep@5353} "HasStep([~label.eq(battled)])"
4 = {HasStep@5355} "HasStep([time.eq(12)])"
5 = {DedupGlobalStep@5354} "DedupGlobalStep"
6 = {EdgeVertexStep@5356} "EdgeVertexStep(IN)"
7 = {VertexStep@5357} "VertexStep(IN,[pet],edge)"
8 = {EdgeVertexStep@5358} "EdgeVertexStep(OUT)"
9 = {HasStep@5359} "HasStep([age.eq(4000)])"
Inline Filter Strategy
将同作用域
的多个算子
内联为一个一个算子
;
相同作用域下,相同算子的内联整合:
例如,下述语句的两个has
会在第一部分的步骤生成两个has算子
:
has(T.label,"battled").has("time", "12")
两个has
同时作用于V()
在同一作用域,又是相同类型,所以在Inline Filter Strategy
策略中,会将两个has内联为一个has算子
;
相同作用域下,不同算子的内联整合:
包含好多种情况,也是举一个例子,如果一个has算子
一个vertex算子
如下:
outE().has(label,"battled")
满足以下三个条件:
- has算子的前置算子为vertex算子(也就是上述out()产生的算子类型)
- 前置的vertex算子返回的类型时edge类型,也就是outE、inE、bothE这三种
- 前置的vertex算子,也就是outE、inE、bothE这三种没有指定edge label
的前提下,has算子的内容为过滤Edge的label;则可以将上述的两种组合成一个vertex算子
,可以用以下语句表示:
outE().has(label,"battled") ==内联为==> outE("battled")
调整过后的算子链:
steps = {ArrayList@5271} size = 9
0 = {GraphStep@5350} "GraphStep(vertex,[])"
1 = {HasStep@5351} "HasStep([name.eq(hercules)])"
2 = {VertexStep@5463} "VertexStep(OUT,[battled],edge)"
3 = {HasStep@5355} "HasStep([time.eq(12)])"
4 = {DedupGlobalStep@5354} "DedupGlobalStep"
5 = {EdgeVertexStep@5356} "EdgeVertexStep(IN)"
6 = {VertexStep@5357} "VertexStep(IN,[pet],edge)"
7 = {EdgeVertexStep@5358} "EdgeVertexStep(OUT)"
8 = {HasStep@5359} "HasStep([age.eq(4000)])"
Incident To Adjacent Strategy
作用也是合并算子,但是合并的是vertex算子(outE、inE、bothE)和edge算子(outV、inV、bothV);
举个例子,如下语句,包含一个vertex算子(inE)
和一个edge算子(outV)
inE("pet").outV()
这个策略的作用就是将这两个算子合成一个in("pet")
算子,如下:
inE("pet").outV() ==合并==> in("pet")
调整后的算子链:
steps = {ArrayList@5271} size = 8
0 = {GraphStep@5350} "GraphStep(vertex,[])"
1 = {HasStep@5351} "HasStep([name.eq(hercules)])"
2 = {VertexStep@5463} "VertexStep(OUT,[battled],edge)"
3 = {HasStep@5355} "HasStep([time.eq(12)])"
4 = {DedupGlobalStep@5354} "DedupGlobalStep"
5 = {EdgeVertexStep@5356} "EdgeVertexStep(IN)"
6 = {VertexStep@5514} "VertexStep(IN,[pet],vertex)"
7 = {HasStep@5359} "HasStep([age.eq(4000)])"
JanusGraph Step Strategy
获取GraphStep算子,也就是V()、E()等对应产生的算子; 将Gremlin的GraphStep算子
转换为图库自身的JanusGraphStep算子
对象;
JanusGraphStep算子对象中包含查询图库获取数据
的lambda语句;在下一部分执行查询
中通过调用get()
来进行图库数据查询!
调整后的算子链:
steps = {ArrayList@5291} size = 7
0 = {JanusGraphStep@5630} "JanusGraphStep([],[name.eq(hercules)])"
1 = {JanusGraphVertexStep@5610} "JanusGraphVertexStep([time.eq(12)])"
2 = {DedupGlobalStep@5341} "DedupGlobalStep"
3 = {EdgeVertexStep@5343} "EdgeVertexStep(IN)"
4 = {JanusGraphVertexStep@5611} "JanusGraphVertexStep(IN,[pet],vertex)"
5 = {NoOpBarrierStep@5513} "NoOpBarrierStep(2500)"
6 = {HasStep@5346} "HasStep([age.eq(4000)])"
其他
策略共19种,每一种都有自己的作用;优化算子链、优化调整index的使用、count语句的优化、repeat多度路径查询的优化等等
此查询语句优化
部分,对用户自定义的gremlin
语句进行正确性验证
和优化查询过程的算子链
两个作用;
最终,执行到第优化后的算子链为:
steps = {ArrayList@5271} size = 7
0 = {JanusGraphStep@6302} "JanusGraphStep([],[name.eq(hercules)])"
1 = {JanusGraphVertexStep@6124} "JanusGraphVertexStep([time.eq(12)])"
2 = {DedupGlobalStep@5354} "DedupGlobalStep"
3 = {EdgeVertexStep@5356} "EdgeVertexStep(IN)"
4 = {JanusGraphVertexStep@6125} "JanusGraphVertexStep(IN,[pet],vertex)"
5 = {NoOpBarrierStep@5857} "NoOpBarrierStep(2500)"
6 = {HasStep@5359} "HasStep([age.eq(4000)])"
扩展:
Gremlin查询语句执行过程,把执行语句转为由多个step组成对应的算子链,经过优化策略优化调整转化为确定最终可执行算子链。
Gremlin语言执行过程:
- [D] ecoration-应用级的迭代逻辑上迭代策略
- [O]ptimization在ThinkPop图架构级别上高效迭代策略
- [P]rovider optimization 从系统,语言,驱动级别优化
- [F]inalization 对以上策略做调整确定最终执行策略
- [V]erification:迭代策略做迭代引擎的验证
如下图所示:
2.3 执行算子链
执行算子链
的触发时机在最终语句
时进行触发;也就是我们示例语句中下述语句的hasNext()
:
plutoVertex.hasNext()
对于hasNext
的源码伪代码如下:
@Override
public boolean hasNext() {
if (null != this.nextEnd)
return true;
else {
try {
while (true) {
this.nextEnd = this.processNextStart();
if (null != this.nextEnd.get() && 0 != this.nextEnd.bulk())
return true;
else
this.nextEnd = null;
}
} catch (final NoSuchElementException e) {
return false;
}
}
}
对于数据执行调用get()
触发lambda表达式内容;
针对不同的算子针对中间结果进行顺序遍历;
三:源码分析
源码分析已经push到github:https://github.com/YYDreamer/janusgraph
四:总结
整体流程如下: