概述
UModel 提供强大的图查询能力,支持基于实体关系的复杂查询操作。本文档介绍基础的图查询方法,包括 graph-match 和 graph-call 功能,用于查询实体之间的关系和路径。
应用案例
当前应用案例相关素材,请参考:umodel.zip
图查询基础概念
节点和边的描述结构
在图查询中,使用特定的语法来描述节点和边:
节点:使用小括号
()
表示。边:使用中括号
[]
表示。描述格式:
<变量名>:<标签> {属性键值对}
。
基础语法示例
// 任意节点
()
// 具有特定标签的节点
(:"apm@apm.service") // graph-match写法
(:`apm@apm.service`) // cypher写法
// 具有标签和属性的节点
(:"apm@apm.service" { __entity_type__: 'apm.service' })
// 命名变量的节点
(s:"apm@apm.service" { __entity_id__: '123456' })
// 任意边
[]
// 命名边
[edge]
// 具有类型的边
[e:calls { __type__: "calls" }]
语法差异说明
graph-match vs Cypher:
graph-match 在 SPL 上下文中,特殊字符需要双引号包裹。
Cypher 作为独立语法,标签使用反引号包裹(注意如果 Cypher 使用反引号字符串格式,内部的反引号需要使用两个反引号包裹)。
// graph-match语法
.topo | graph-match (s:"apm@apm.service" {__entity_id__: '123'})-[e]-(d)
project s, e, d
// Cypher语法 (``apm@apm.service`` 反引号字符串格式,使用两个反引号包裹)
.topo | graph-call cypher(`
MATCH (s:``apm@apm.service`` {__entity_id__: '35af918180394ff853be6c9b458704ea'})-[e]-(d)
RETURN s, e, d
`)
图查询路径语法
图查询路径使用ASCII字符描述关系方向:
路径表达式 | 方向说明 |
| 从A到B的有向关系 |
| 从B到A的有向关系 |
| 双向关系,不限方向 |
返回值结构
节点JSON格式
{
"id": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
"label": "apm@apm.service",
"properties": {
"__domain__": "apm",
"__entity_type__": "apm.service",
"__entity_id__": "347150ad7eaee43d2bd25d113f567569",
"__label__": "apm@apm.service"
}
}
边JSON格式
{
"startNodeId": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
"endNodeId": "apm@apm.service.host:34f627359470c9d36da593708e9f2db7",
"type": "contains",
"properties": {
"__type__": "contains"
}
}
graph-match查询
基础语法
.topo | graph-match <path>
project <output>
| [additional SPL operations]
核心特性
必须指定起始点:需要提供标签 +
__entity_id__
属性。中间节点限制:只能指定标签,不能指定属性。
不支持多级跳跃:不支持
[*2..3]
语法。环路处理:每条边可以被多次使用。
实际应用案例
1. 全链路路径查询
查找从特定操作开始的完整调用链路:
.topo |
graph-match (s:"apm@apm.service.operation" {__entity_id__: '6f0bb4c892effff81538df574a5cfcd9'})
<-[e1]-(v1)-[e2:runs_on]->(v2)-[e3]->(v3)
project s,
"e1.__type__",
"v1.__label__",
"e2.__type__",
"v2.__label__",
"e3.__type__",
"v3.__label__",
v3
返回结果:
s: 起始操作节点。
e1.type: 第一段关系类型。
v1.label: 中间节点标签。
v2, v3: 后续节点信息。
2. 邻居节点统计
统计特定服务的邻居分布情况:
.topo |
graph-match (s:"apm@apm.service" {__entity_id__: 'd6385f422b457a59cce1f0f725ac3e14'})
-[e]-(d)
project eType="e.__type__", dLabel="d.__label__"
| stats cnt=count(1) by dLabel, eType
| sort cnt desc
| limit 20
3. 条件路径查询
查找满足特定条件的路径终点:
.topo |
graph-match (s:"apm@apm.service.operation" {__entity_id__: '6f0bb4c892effff81538df574a5cfcd9'})
<-[e1]-(v1)-[e2:runs_on]->(v2)-[e3]->(v3)
project s,
"e1.__type__",
"v1.__label__",
"e2.__type__",
"v2.__label__",
"e3.__type__",
destId="v3.__entity_id__",
v3
| where destId='9a3ad23aa0826d643c7b2ab7c6897591'
| project s, v3
graph-match 限制
限制类型 | 说明 | 解决方案 |
起始点限制 | 必须指定标签和__entity_id__ | 使用已知实体ID作为起点 |
中间节点限制 | 不能为中间节点指定属性 | 使用SPL进行后续过滤 |
不支持多级跳 | 不支持 | 使用Cypher或分步查询 |
环路处理 | 边可重复使用,可能产生环 | 通过SPL去重或限制深度 |
graph-call 查询
getNeighborNodes 函数
获取指定节点列表的邻居节点,并返回邻居节点和关系类型。
语法格式
.topo | graph-call getNeighborNodes(type, depth, nodeList)
参数说明
参数 | 类型 | 说明 | 可选值 |
type | string | 遍历类型 | sequence, sequence_in, sequence_out, full |
depth | int | 遍历深度 | 正整数,建议不超过5 |
nodeList | array | 起始节点列表 | 节点描述数组 |
返回格式
字段 | 说明 | 示例 |
srcNode | 源节点JSON | {“id”:“app@app.service:347150…”, “labels”:“app@app.service”, …} |
destNode | 目标节点JSON | {“id”:“app@app.operation:73ef19…”, “labels”:“app@app.operation”, …} |
relationType | 关系类型 | “contains”, “calls”, “runs_on” |
srcPosition | 源节点位置 | -1, 0, 1, 2… |
遍历类型说明
sequence:有向序列遍历,即保持一个顺序返回关系。
sequence_in:sequence
的子集,只保持入边顺序的有向序列遍历,即返回的结果中,节点都最终指向起始节点。
sequence_out:sequence
的子集,只保持出边顺序的有向序列遍历,即返回的结果中,节点都最终从起始节点出发。
full:全方向遍历。
不考虑边的方向,全量遍历。
实际示例
.topo | graph-call getNeighborNodes(
'sequence', 1,
[
(:"app@app.operation" {__entity_id__: '73ef19770998ff5d4c1bfd042bc00a0f'})
]
)
getDirectRelations 函数
返回指定节点列表之间的直接关系,不包括间接关系。一般用于多个节点的批量上下游查询。
语法格式
.topo | graph-call getDirectRelations(nodeList)
参数说明
参数 | 类型 | 说明 |
nodeList | array | 节点列表 |
返回格式
字段 | 说明 |
relations | 关系JSON数组,包含startNodeId、endNodeId、type、properties |
实际示例
.topo | graph-call getDirectRelations(
[
(:"app@app.service" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'}),
(:"app@app.operation" {__entity_id__: '73ef19770998ff5d4c1bfd042bc00a0f'})
]
)
{
"startNodeId": "app@app.service:347150ad7eaee43d2bd25d113f567569",
"endNodeId": "app@app.operation:73ef19770998ff5d4c1bfd042bc00a0f",
"type": "contains",
"properties": {"__type__": "contains"}
}
典型应用场景
获取服务的完整邻居关系
-- 获取服务的所有邻居(5跳内)
.topo | graph-call getNeighborNodes(
'full', 3,
[(:"apm@apm.service" {__entity_id__: 'user-service-id'})]
)
| stats cnt=count(1) by relationType
| sort cnt desc
分析服务调用链
-- 分析特定服务的调用模式
.topo |
graph-match (s:"apm@apm.service" {__entity_id__: 'payment-service'})
-[e:calls]-(d:"apm@apm.service")
project
source_service="s.service_name",
target_service="d.service_name",
call_type="e.__type__"
| stats call_count=count(1) by source_service, target_service
| sort call_count desc
Pod到Node的关系链
-- 追踪Pod的完整部署链
.topo |
graph-match (pod:"k8s@k8s.pod" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'})
<-[r1:contains]->(node:"k8s@k8s.node")
<-[r2:contains]-(cluster:"k8s@k8s.cluster")
project
pod_name="pod.pod_name",
node_name="node.node_name",
cluster_name="cluster.cluster_name",
"r1.__type__",
"r2.__type__"
云资源依赖分析
-- 分析ECS实例的网络依赖
.topo | graph-call getNeighborNodes(
'sequence_out', 2,
[(:"acs@acs.ecs.instance" {__entity_id__: 'i-bp1234567890'})]
)
| extend relation_category = CASE
WHEN relationType in ('belongs_to', 'runs_in') THEN 'infrastructure'
WHEN relationType in ('depends_on', 'uses') THEN 'dependency'
WHEN relationType in ('connects_to', 'accesses') THEN 'network'
ELSE 'other' END
| stats cnt=count(1) by relation_category
| sort cnt desc
| limit 0, 100
故障上游影响分析
-- 查找可能影响目标服务的上游服务
.topo | graph-call getNeighborNodes(
'sequence_in', 3,
[(:"apm@apm.service" {__entity_id__: '029ea8f3f65dbc8da226f2c8d55bb8c6'})]
)
| where relationType in ('calls', 'depends_on')
| extend impact_level = CASE
WHEN srcPosition = '-1' THEN 'direct'
WHEN srcPosition = '-2' THEN 'secondary'
ELSE 'indirect' END
| extend parsed_service_name = json_extract_scalar(srcNode, '$.properties.service_name')
| project
upstream_service = parsed_service_name,
impact_level,
relation_type = relationType
| stats cnt=count(1) by impact_level, relation_type
故障下游影响分析
-- 查找受目标服务故障影响的下游服务
.topo | graph-call getNeighborNodes(
'sequence_out', 3,
[(:"apm@apm.service" {__entity_id__: 'failing-service-id'})]
)
| where relationType in ('calls', 'depends_on')
| extend affected_service = json_extract_scalar(destNode, '$.properties.service_name')
| stats impact_count=count(1) by affected_service
| sort impact_count desc
| limit 20
权限链追踪
-- 追踪用户到资源的访问路径
.topo |
graph-match (user:"identity@user" {__entity_id__: 'user-123'})
-[auth:authenticated_to]->(app:"apm@apm.service")
-[access:accesses]->(resource:"acs@acs.rds.instance")
project
user_id="user.user_id",
app_name="app.service_name",
resource_id="resource.instance_id",
auth_method="auth.auth_method",
access_level="access.permission_level"
性能优化建议
1. 查询范围控制
时间范围优化:合理利用时间字段进行范围限制。
限制遍历深度:深度超过5层会显著影响性能。
精确起始点:使用具体的 entity_id 而非模糊匹配。
合理选择遍历类型:根据实际需求选择 sequence 或 full。
2. 结果集控制
使用 SPL 过滤:在图查询后及时过滤不需要的结果。
分批处理:对于大型图查询,考虑分批处理。
结果缓存:对于频繁查询的路径,考虑结果缓存。