如何使用GES进行社交关系考据?---GES查询能力介绍

举报
弓乙 发表于 2021/10/19 17:16:11 2021/10/19
【摘要】 开发者`李雷`小朋友维护了一个自己的关系链图数据库,他怎么能从图数据库中查询出与他互相关注且年龄大于30的朋友呢?这里先介绍几种图原生查询语言写法:1.gremlin ... 2.cypher ... 以上两种写法等价,只是使用的图查询语言有区别。前者使用gremlin(Apache软件基金会下TinkerPop开发的graph traversal language)编写, 后者为Neo4j...

开发者李雷小朋友维护了一个自己的关系链图数据库,他怎么能从图数据库中查询出与他互相关注且年龄大于30的朋友呢?

image.png

这里先介绍几种图原生查询语言写法:

1.gremlin

g.V("李雷").outE('friend').has('age',gt(30)).otherV().where(out('friend').
(hasId('李雷'))).limit(100)

2.cypher

match (a)-[r1:friend]->(b)-[r2:friend]->(c) where a.mid='李雷' and r1.age>30 and a=c return id(b) limit 100

以上两种写法等价,只是使用的图查询语言有区别。前者使用gremlin(Apache软件基金会下TinkerPop开发的graph traversal language)编写, 后者为Neo4j于2015年发布的图查询语言开源版本openCypher

查询方式一览

GES支持多种查询方式,本文主要讨论复杂查询,如多跳过滤,简单环路查询,模式匹配等复杂查询类。当前GES主要通过gremlin,cypher和一些原生API来支持各种场景的查询需求。

优点 缺点 说明
Gremlin functional language 1.表达能力(图灵完备) 2.支持groovy脚本 1.性能差(exponential runtime tree) 2.复杂的查询书写困难 适用于体验,demo,或不追求性能的场景
Cypher Pattern match style
declarative
1.类SQL写法 2.深度集成于GES,性能比gremlin好一些 1.表达能力对图来说差一些(仅为SQL complete) 2.流式支持有限 适用于一般性场景
原生API json parameter 1.性能非常好 2.傻瓜参数形式 1.灵活性差 2.使用场景有限 3.表达能力差 提供常用查询API,适用于最求性能,高并发,低延迟的场景。

性能

本节以上文所述李雷的好友场景展示一个查询k=2环路的性能测试,用以帮助大家更直观地了解各个查询间的性能差距:

其中,

  • filterV2 - GES原生API,该API性能最佳,TPS与延迟表现优异。
  • Cypher - GES的cypher查询。
  • Neo4j - Neo4j community 4.2.3版本cypher。
  • gremlin - GES的gremlin,未在图中体现,实际性能最差,故未做对比。

image-20210730165835807.png

gremlin使用手册

gremlin包括OLTP和OLAP两个部分的语法。其OLTP的语法灵活多变,符合图原生的表达方式,被广泛集成于各个图数据库厂商。

GES查询能力优异,但我们仅建议在demo/简单查询中使用gremlin。其性能远远低于内核原生API与cypher, 这是由gremlin的集成方式决定的,虽然GES在常用语法上做了适当的优化,但是并不能完全覆盖所有的gremlin查询。我们向您推荐使用集成度更加高的cypher查看后续cypher章节进行常态化查询,如果有更需要性能的场景,请使用原生API。

查看以下参考资料获取更多信息:

  1. tinkerpop documentation tinkerpop官方文档。内容详实,各个step都有案例介绍。
  2. gremlin 实践案例 gremlin实践案例

常用语法

g.V() //获取图中所有点。注意该行为在大图上是高危操作,小心使用。

g.V().limit(10) //取图中10个点。非随机。

g.V('小霞','小智').values('age')  //获取图中id为小智和小霞的属性值age的值。
> [20, 22]

g.V('小智').out('朋友').out('朋友')  //获取小智的朋友的朋友
> [小明]

g.V('小智').outE('朋友').inV().outE('朋友').inV()  //该条语句含义和上条完全一致。

g.V('小智').out('朋友').has('age',gt(30))  //获取小智年龄大于30的朋友

g.V('小智').out('朋友').values('age').sum()  //统计小智所有朋友的年龄总和

GES gremlin特殊语法/优化

GES集成了gremlin中的OLTP功能,并在一定程度上做了部分功能增强与strategy优化。

增强版Text Predicate

g.V().has('name', Text.textSubString('xx'))...
Predicate 描述
textSubString 子字符串
textCISubString 忽略大小写的子字符串
textFuzzy 模糊匹配
textPrefix 前缀查询
textRegex 正则匹配

注意事项

在指定schema时,最好不要给属性取名为id, label, property, properties

在进行gremlin操作时,有很多step会把结果转化为map结果。众所周知,在map结构中,是不允许出现两个相同key的,一般来说当我们向一个map中重复insert多个相同的key,其value会被覆盖 or 该操作被取消。

故如果我们把属性名取为id, label, property, properties,在很多操作中,如果id与属性中的id一起返回,结果将是不完整的。

cypher使用手册

cypher在语法上更接近于SQL,表达能力稍微欠缺一些(sql完备),但已能应对绝大多数场景。

GES对于cypher的集成更加接近内核端,充分利用了内核的性能优势。

查看以下参考资料获取更多信息:

  1. cypher refcard cypher语法参考卡。
  2. GES cypher GES cypher公有云API文档,包含兼容性说明,数据类型支持,表达式,函数等说明。

常用语法

match (n) return n //获取图中所有点。注意该行为在大图上需小心使用。

match (n) return n limit 10 //取图中10个点。非随机。

match (n) where n.movieid IN ['小霞','小智']  return n.age  //获取图中id为小智和小霞的属性值age的值。

match (n)-->(m1:朋友)-->(s1)-->(m2:朋友)-->(s2) where id(n)='小智' return s2 //获取小智的朋友的朋友

match (n)-->(m1:朋友)-->(s) where id(n)='小智' and s.age>30 return s //获取小智年龄大于30的朋友

兼容性说明

GES对cypher会进行持续性的优化与加强。当前因为特性/开发进度的原因暂不支持包括:

  1. 目前暂不支持union、merge、foreach、optional等操作,暂不支持使用Cypher语句增删索引,后续会逐渐开放支持。
  2. 由于GES的元数据不是Schema Free的,点边label属性等有严格的限制,因此不支持Remove操作。
  3. Order by子句不支持List类型的排序,当属性值的Cardinality不为single时,排序结果未知。

以上说明可在cypher文档中获取最新的支持情况。

GES cypher 特殊化处理

预置条件

为了加速查询以及优化查询计划,GES的Cypher查询编译过程中使用了基于label的点边索引。可通过界面/API添加相关的索引新建索引API

POST http://{SERVER_URL}/ges/v1.0/{project_id}/graphs/{graph_name}/indices
{
        "indexName": "cypher_vertex_index",
        "indexType": "GlobalCompositeVertexIndex",
        "hasLabel": "true",
        "indexProperty": []
}

原生API使用手册

内核原生API是专门为常用场景提供的高性能版本。其输入输出均为json格式。

GES提供了丰富多样的原生API接口,包括管理面API(主要是图操作,导入导出备份等),业务面API。业务面API包括基础的图数据库功能,如点边的CRUD,索引的管理,高级查询path query,高级算法库。

查看以下参考资料获取更多信息:

  1. 管理面API 管理面API概览
  2. 业务面API 业务面API概览,业务面API的调用方法可以参考此篇博文
  3. 算法说明 执行算法说明

常用API说明

一.Path Query

该接口支持对多跳过滤,循环执行遍历查询进行加速。

例如以下gremlin语句:

g.V('node1').repeat(out('label_1')).times(3).emit()

以上gremlin是一个三跳查询,从点node1出发,查询其出边关系为label1的邻居的邻居的邻居。并返回过程中所有涉及到的点。该脚本通过repeat来组织查询,并通过times来控制loop的次数,甚至后续还可以通过关键字until来终止traversal。

以上gremlin使用path query调用可使用:

POST /ges/v1.0/{projectId}/graphs/{graphName}/action?action_id=path-query
{
  "repeat": [
    {
      "operator": "outV",
      "edge_filter": {
        "property_filter": {
          "leftvalue": {
            "label_name": "labelName"
          },
          "predicate": "=",
          "rightvalue": {
            "value": "label_1"
          }
        }
      }
    }
  ],
  "emit": true,
  "times": 3,
  "vertices": [
    "node1"
  ]
}

1. by mode

例如针对二跳邻居,我们可以通过参数by对id,label,property进行过滤:

g.V("a").repeat(out()).times(2).by(id()) //输出a的所有一跳二跳邻居的id。
g.V("a").repeat(out()).times(2).by(label()) //输出a的所有一跳二跳邻居的label。

而使用path query我们可以很轻易地把上述gremlin转化为原生API以获得更加优异的性能,关键字含义几乎一致。在by中可以指定输出的形式,如输出id:

{
  "repeat": [
    {
      "operator": "outV"
    }
  ],
  "times": 2,
  "vertices": [
    "a"
  ],
  "by": [{"id": true}]
}

2. select+by mode

image-20210409171246106.png

该模式可任意选择traverse路径上经过的N层。

如上图,我们希望显示李雷与其第二跳好友的关联关系:

gremlin写法:

g.V('李雷').as('v0').repeat(out()).times(2).as('v2').select('v0','v2').by(id()).by(id()).dedup()
{//返回样例
  "results": [
    {
      "v0": "李雷",
      "v2": "小智"
    },
    {
      "v0": "李雷",
      "v2": "小霞"
    }
  ]
}

cypher写法:

match (v0)<--(v1)<--(v2) where id(v0)='1' return distinct id(v0),id(v2)

//返回json如下,形式稍显冗余:
{
  "data": [
    {
      "row": [
        "李雷",
        "小智"
      ],
      "meta": [
        null,
        null
      ]
    },
    {
      "row": [
        "李雷",
        "小霞"
      ],
      "meta": [
        null,
        null
      ]
    }
  ]
}

使用path query:

{
  "repeat": [
    {
      "operator": "outV"
    }
  ],
  "times": 2,
  "vertices": [
    "李雷"
  ],
  "by": [{"id": true},{"id": true}],
  "select": ["v0", "v2"]
}

//返回json如下,直接返回select中的参数:
{
  "select": [
       ["李雷", "小智"],
       ["李雷", "小霞"]
  ]
}

二.算法API

算法API提供丰富的原生算法库,可以通过界面或者API使用。

algorithm1

在界面上直接运行算法能够可视化地查看算法结果,关于每个算法的详情,也可以查询官方算法文档

参数 是否必选 说明 类型 取值范围 默认值
alpha 权重系数(又称阻尼系数)。 Double 0~1,不包括0和1。 0.85
convergence 收敛精度。 Double 0~1,不包括0和1。 0.00001
max_iterations 最大迭代次数。 Integer 1~2000。 1000
directed 是否考虑边的方向。 Boolean true或false。 true

如上pagerank算法参数列表,列出了参数说明,取值范围以及默认值。
此外,我们也可以直接使用RESTful API的形式调用算法,大部分算法以异步的形式交互:调用算法后我们可以获得一个jobId使用jobId我们可以查询算法运行的状态及运行结果。同时也可以对结果进行分页及数量截取。

GET http://{SERVER_URL}/ges/v1.0/{project_id}/graphs/{graph_name}/jobs/{job_id}/status?offset=0&limit=2

最短路径 shortest_path

最短路径属于一个传统的图论问题,其包含各种不同的形式。如单源最短路,多源最短路。常用的算法如Dijkstra, Bellman-Ford, Floyd-Warshall, Johnson等算法。

GES提供多种场景的最短路径,如shortest_path, all_shortest_paths, shortest_path_of_vertex_sets等。均以原生API或图形界面的方式提供

该API属于算法API,其调用方式查看文档, 其参数说明查看此处

image.png

POST /ges/v1.0/{project_id}/graphs/{graph_name}/action?action_id=execute-algorithm
{
    "executionMode": "sync",
    "algorithmName": "shortest_path",
    "parameters": {
        "source": "Alexander Payne",
        "target": "Harrison Ford",
        "weight":"score"
    }
}

不同查询方式场景详解

案例1-好友推荐

tuijian1

我们向李雷推荐好友,思路是:向他推荐其好友的好友,但是推荐的好友中不应包含李雷本身的好友,比如图中韩梅梅同时是李雷的一跳好友和二跳好友。这时我们不应向李雷推荐韩梅梅,因为她已经是李雷的好友了。

下面将分别展示使用gremlin,cypherpath query查询,返回均为推荐路径:李雷->李雷好友->推荐好友

gremlin

gremlin>
g.V("李雷").repeat(out("friend").simplePath().where(without('1hop')).store('1hop')).
times(2).path().by("name").limit(100)

gremlin>
[李雷,小明,小智]
[李雷,韩梅梅,小智]
[李雷,韩梅梅,小霞]

cypher

match (a)-[:friend]->(d) where id(a)='李雷' with a, collect(d) as neighbor
match (a)-[:friend]-(b)-[:friend]-(c)
where not (c in neighbor)
return a.name, b.name, c.name

//返回样例
[
  {
    "row": ["李雷", "小明","小智"],
    "meta": [null, null, null]
  },
  {
    "row": ["李雷","韩梅梅", "小智"],
    "meta": [null, null, null]
  },
  {
    "row": ["李雷", "韩梅梅", "小霞"],
    "meta": [null, null, null]
  }
]

path query

{
  "repeat": [
    {
      "operator": "outV",
      "edge_filter": {
        "property_filter": {
          "leftvalue": {
            "label_name": "labelName"
          },
          "predicate": "=",
          "rightvalue": {
            "value": "friend"
          }
        }
      }
    }
  ],
  "times": 2,
  "vertices": [
    "李雷"
  ],
  "by": [{"id": true},{"id": true},{"id": true}],
  "select": ["v0", "v1", "v2"]
}
{
  "select": [
	["李雷", "小明","小智"],
	["李雷","韩梅梅", "小智"],
	["李雷", "韩梅梅", "小霞"]
  ]
}

案例2-(k=2环路)

开发者李雷小朋友维护了一个自己的关系链图数据库,他怎么能从图数据库中查询出与他互相关注且年龄大于30的朋友呢?

image.png

回到文章开头,我们如何帮助李雷完成查询呢?这其实可以归结为一个环路问题。
我们需要获取李雷的双向好友。即从李雷出发的k=2且边上过滤条件为age>30,friend的环路。

下面将分别展示使用gremlin,cypherpath query环路算法进行查询,该四种方式均可以帮助李雷完成目标。

gremlin

gremlin>
g.V("李雷").outE().has('age',gt(30)).otherV().where(out('friend').
(hasId('李雷'))).limit(100)

cypher

match (a)-[r1]->(b)-[r2:friend]->(c) where a.mid='李雷' and r1.age>30 and a=c return id(b) limit 100

path query

POST /ges/v1.0/{projectId}/graphs/{graphName}/action?action_id=path-query
{
    "repeat": [
        {
            "operator": "outV",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "property_name": "age"
                    },
                    "predicate": ">",
                    "rightvalue": {
                        "value": "30"
                    }
                }
            }
        },
        {
            "operator": "outV",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "label_name": "label_name"
                    },
                    "predicate": "=",
                    "rightvalue": {
                        "value": "friend"
                    }
                }
            }
        }
    ],
    "limit": 100,
    "times": 2,
    "vertices": ["李雷"],
    "by": [{ "id": true}],
    "select": ["v1"]
}

环路算法

{
    "algorithmName": "filtered_circle_detection",
    "parameters": {
        "sources": "李雷",
        "n": 100
    },
    "filters": [
        {},
        {
            "operator": "out",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "property_name": "age"
                    },
                    "predicate": ">",
                    "rightvalue": {
                        "value": "30"
                    }
                }
            }
        },
        {
            "operator": "out",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "label_name": "label_name"
                    },
                    "predicate": "=",
                    "rightvalue": {
                        "value": "friends"
                    }
                }
            }
        }
    ]
}

参考资料

1.tinkerpop documentation tinkerpop官方文档。内容详实,各个step都有案例介绍。
2.gremlin 实践案例 gremlin实践案例
3.cypher refcard cypher语法参考卡。
4.GES cypher GES cypher公有云API文档,包含兼容性说明,数据类型支持,表达式,函数等说明。
5.业务面API的调用方法(https://bbs.huaweicloud.com/blogs/296930)。
6.GES算法API说明

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。