在现代云原生架构中,系统组件间的连接关系错综复杂。传统的监控工具擅长处理孤立的指标,却难以揭示服务、容器、基础设施之间的依赖网络。本文将深入探讨如何利用阿里云SLS的EntityStore,通过图查询技术,将可观测数据的分析维度从“点”提升到“关系网络”,实现更高效的故障排查与架构洞察。
从“孤立的实体”到“连接的网络”
我们习惯于将系统中的每个组件——服务、容器、中间件、基础设施——视为独立的“实体”进行监控。然而,这种方式存在一个根本性盲点:它忽略了系统最本质的特征——连接(Connection)。任何一个实体都不是孤立存在的,它们通过调用、依赖、包含等关系,构成了一张复杂巨大、动态变化的“关系图谱”。
传统的监控和查询工具,其核心是处理二维的、表格化的数据。它们擅长回答关于“个体”的问题,但在回答关于“关系”的问题时却显得力不从心。例如,“这个服务的故障会影响哪些下游业务?”或“要访问到核心数据库,需要经过哪些中间服务?”面对这类问题,传统工具往往需要复杂的JOIN操作、多步查询,甚至需要工程师结合线下架构图进行“人脑拼凑”。

解决思路:融合图查询
面对这一挑战,我们的解决思路是:将“图”(Graph)作为可观测数据模型的重要组成。我们认为,系统的真实形态本就是一张图,那么对它的查询和分析,也应该使用最符合其本质的方式——图查询。
为了实现这一点,我们在SLS的UModel体系核心构建了EntityStore。它采用了创新的双存储架构,同时维护了__entity__日志库(存储实体的详细属性)和__topo__日志库(存储实体间的拓扑关系)。这相当于为整个可观测系统建立了一个实时更新的、可查询的数字孪生图谱。
基于这个图谱,我们提供了从易到难、层层递进的三种图查询能力:
- graph-match:为最常见的路径查询场景设计,语法直观,让用户能像描述一句话一样来快速查找特定链路。
- graph-call:封装了最高频的图算法(如邻居查找),通过函数式接口提供,用户只需关心意图而无需关心实现细节。
- Cypher:引入业界标准的图查询语言,提供最完整、最强大的图查询能力,是处理复杂图问题的终极武器。
这一整套解决方案,旨在将强大的图分析能力,以一种低门槛、工程化的方式,提供给每一位运维和开发工程师。
核心价值:解锁系统洞察的新维度
引入图查询能力,不仅仅是增加了一种新的查询语法,更是为系统洞察解锁了一个全新的维度:
- 全局化的故障影响分析(爆炸半径分析):当故障发生时,可以通过一次查询,快速确定该故障点向下游辐射的所有可能路径和受影响的业务范围。
- 端到端的根因溯源:与影响分析相反,当某个底层服务出现问题时,可以向上游回溯,快速定位触发异常的业务或变更。
- 架构健康度与合规性审计:可以通过图查询来验证线上系统的实际架构是否与设计相符,例如查询是否存在“跨网络域的非法调用”。
- 安全与权限链路分析:在安全审计中,可以追踪从用户到具体资源的完整访问路径,确保每一层权限授予都符合安全规范。
总而言之,图查询能力将我们对系统的认知从“点的集合”提升到了“结构化的网络”,使得我们能够基于系统组件之间的真实关系进行提问和分析,从而获得前所未有的深度洞察力。
图查询相关概念
在EntityStore的体系中,数据被抽象为知识图谱。
UModel (知识图谱)
├── EntitySet: apm.service (类型定义)
│ ├── Entity: user-service (实例1)
│ ├── Entity: payment-service (实例2)
│ └── Entity: order-service (实例3)
├── EntitySet: k8s.pod (类型定义)
│ ├── Entity: web-pod-123 (实例1)
│ └── Entity: api-pod-456 (实例2)
└── EntitySetLink: service_runs_on_pod (关系定义)
├── Relation: user-service -> web-pod-123
└── Relation: payment-service -> api-pod-456
EntityStore 借助于 SLS LogStore 资源实现数据写入、消费等功能。在创建 EntityStore 时,会同步创建以下 LogStore 资产:
${workspace}__entity:用于写入实体数据
${workspace}__topo:用于写入关系数据
本文介绍的图查询用法,是针对写入${workspace}__topo的关系数据的查询,支持多跳关系路径分析、实体邻接关系分析、自定义拓扑模式识别等能力。
注意:本文介绍的图查询用法,系可观测 2.0 高阶 PaaS API 的底层查询,适合高度定制化自由查询模式的资深用户。若仅需简单的关联查找、查询信息等能力,推荐使用高阶 PaaS API,接口更友好。

图查询基础概念
在深入使用图查询之前,理解其基础概念至关重要。图查询的核心思想是将数据抽象为图(Graph)结构:实体是节点(Node),关系是边(Edge)。每个节点都有标签(Label)和属性(Properties),每条边也有类型(Type)和属性。
节点和边的描述语法
在图查询中,使用特定的语法来描述节点和边:
- 节点:使用小括号
() 表示
- 边:使用中括号
[] 表示
- 描述格式:
<变量名>:<标签> {属性键值对}
基础语法示例:
// 任意节点
()
// 具有特定标签的节点 (graph-match写法)
(:"apm@apm.service")
// 具有特定标签的节点 (cypher写法)
(:`apm@apm.service`)
// 具有标签和属性的节点
(:"apm@apm.service" { __entity_type__: 'apm.service' })
// 命名变量的节点
(s:"apm@apm.service" { __entity_id__: '123456' })
// 任意边
[]
// 命名边
[edge]
// 具有类型的边
[e:calls { __type__: "calls" }]
语法差异说明:
- graph-match:在 SPL 上下文中,特殊字符需要双引号包裹。
- 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)-[e]->(B):从 A 到 B 的有向边。
(A)<-[e]-(B):从 B 到 A 的有向边。
(A)-[e]-(B):无向边(不限制方向)。

返回值结构
在 EntityStore 的体系中,节点的标签格式为domain@entity_type,例如apm@apm.service表示域为 apm、实体类型为 apm.service 的节点。节点的属性包括了系统内置的属性以及用户在写入时自定义的属性。边的类型用字符串表示,比如calls、runs_on、contains等。
节点 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"
}
}
图查询的本质是模式匹配:用户描述一个图模式(Pattern),系统在图中查找所有符合该模式的子图。
graph-match:直观的路径查询
graph-match 是图查询中最直观、最容易上手的功能。它的设计哲学是让用户能够用接近自然语言的方式描述查询意图。graph-match 的核心特点是必须从已知的起始点开始查询,这确保了查询性能。
实际应用案例:
-
全链路路径查询
查找从特定操作开始的完整调用链路:
.topo | graph-match (s:"apm@apm.operation" {__entity_id__: '925f76b2a7943e910187fd5961125288'})
<-[e1]-(v1)-[e2:calls]->(v2)-[e3]->(v3)
project s,
"e1.__type__",
"v1.__label__",
"e2.__type__",
"v2.__label__",
"e3.__type__",
"v3.__label__",
v3
-
邻居节点统计
统计特定服务的邻居分布情况:
.topo | graph-match (s:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})
-[e]-(d)
project eType="e.__type__", dLabel="d.__label__"
| stats cnt=count(1) by dLabel, eType
| sort cnt desc
| limit 20
-
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,
node,
cluster,
"r1.__type__",
"r2.__type__"
graph-match 限制:
- 必须指定明确的起始点(标签 + entity_id)。
- 路径模式相对固定,不支持像 Cypher 那样的灵活模式匹配和属性过滤。
- 中间节点只能通过标签过滤,无法使用自定义属性进行条件判断。
graph-call:函数式图操作
graph-call 提供了一套函数式的图查询接口,封装了常见的图操作模式,让用户能够更高效地执行特定类型的查询。
getNeighborNodes 是最常用的函数,用于获取指定节点的邻居节点。其签名为 getNeighborNodes(type, depth, nodeList):
- type: 控制遍历类型 (
sequence-有向序列, sequence_in-指向起始节点, sequence_out-从起始节点出发, full-全方向)。
- depth: 控制遍历深度(建议 3-5 层)。
- nodeList: 起始节点列表。
getDirectRelations 函数用于批量查询节点之间的直接关系,只返回直接相连的关系。
实际应用案例:
-
获取服务的完整邻居关系
.topo | graph-call getNeighborNodes(
'full', 2, [(:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})])
| stats cnt=count(1) by relationType
| sort cnt desc
-
故障上游影响分析
查找可能影响目标服务的上游服务,为运维工程师提供决策支持:
.topo | graph-call getNeighborNodes(
'sequence_in', 3, [(:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})])
| 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_id = json_extract_scalar(srcNode, '$.id')
| project
upstream_service = parsed_service_id,
impact_level,
relation_type = relationType
| stats cnt=count(1) by impact_level, relation_type
-
批量查询节点间的直接关系
.topo | graph-call getDirectRelations(
[
(:"app@app.service" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'}),
(:"app@app.operation" {__entity_id__: '73ef19770998ff5d4c1bfd042bc00a0f'})
])
Cypher:强大的声明式查询语言
Cypher 是图数据库领域的标准查询语言,提供了最强大和灵活的图查询能力。其语法遵循三段式结构:MATCH(描述图模式)、WHERE(添加筛选条件)、RETURN(指定返回内容)。
基础查询示例:
-
单节点查询
.topo | graph-call cypher(`
MATCH (n {__entity_type__:"apm.service"})
WHERE n.__domain__ STARTS WITH 'a' AND n.__entity_type__ = "apm.service"
RETURN n
`)
-
多级跳查询
查找2-3跳的调用链路(注意:范围左闭右开,*2..4表示2跳和3跳):
.topo | graph-call cypher(`
MATCH (src {__entity_type__:"acs.service"})-[e:calls*2..4]->(dest)
WHERE dest.__domain__ = 'acs'
RETURN src, dest, dest.__entity_type__
`)
-
基于实体自定义属性查询(完整Cypher能力)
.topo | graph-call cypher(`
MATCH (n:``acs@acs.alb.listener`` {listener_id: 'lsn-rxp57*****'})-[e]->(d)
WHERE d.vSwitchId CONTAINS 'vsw-bp1gvyids******'
AND d.user_id IN ['1654*******', '2']
AND d.dns_name ENDS WITH '.com'
RETURN n, e, d
`)
-
级联故障分析
.topo | graph-call cypher(`
MATCH (failed_service:``apm@apm.service`` {service: 'load-generator'})
MATCH (failed_service)-[cascade_path*1..4]->(affected_service:``apm@apm.service``)
RETURN
failed_service.service as root_cause,
length(cascade_path) as impact_depth,
affected_service.service as affected_service,
cascade_path as dependency_chain
ORDER BY impact_depth ASC
`)
典型应用场景
-
分析服务调用链
.topo | graph-match (s:"apm@apm.service" {__entity_id__: 'abcdefg123123'})
-[e:calls]-(d:"apm@apm.service")
project
source_service="s.service",
target_service="d.service",
call_type="e.__type__"
| stats call_count=count(1) by source_service, target_service
| sort call_count desc
-
权限链追踪
.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",
resource_id="resource.instance_id",
auth_method="auth.auth_method",
access_level="access.permission_level"
数据完整性与查询模式选择
图查询能力依赖于三方面数据的完整性:UModel(模型定义)、Entity(实体数据)、Topo(拓扑关系数据)。
- 数据完备:可使用完整版Cypher,支持属性查询。
- 仅Topo数据完备:可使用
pure-topo 模式进行快速拓扑查询,但无法使用实体属性筛选。
.topo | graph-call cypher(`
MATCH (n:``acs@acs.alb.listener``)-[e]->(d)
RETURN n, e, d
`, 'pure-topo')
- Topo数据缺失:无法进行有效的图查询。
性能优化与最佳实践
-
查询结构优化:使用标签索引,进行早期条件过滤。
-- ✅ 优化后:使用标签索引
.topo | graph-call cypher(`
MATCH (n:``apm@apm.service`` {service: 'web-app'})
RETURN n
`)
-
控制查询范围:合理限制时间范围、遍历深度(建议不超过5层),使用具体的 entity_id 作为起始点。
-
控制结果集:使用 LIMIT 分页,或对大型结果集进行采样。
-
利用SPL后处理:在图查询后,及时使用SPL语句过滤和聚合结果,发挥SLS日志处理的综合优势。
常见问题
-
边类型与Cypher关键字重合
当边类型(如 contains)恰好是Cypher关键字时,需要用反引号包裹。在SPL语境下,需用双反引号。
.topo | graph-call cypher(`
MATCH (s)-[e:``contains``]->(d)
WHERE s.__domain__ CONTAINS "apm"
RETURN e
`)
-
不支持简写的Cypher关系语法
✅ 支持的写法:(s)-[]->(d)
❌ 不支持的写法:(s)-->(d)
通过掌握以上图查询技术,您可以将可观测性数据的价值最大化,在复杂的云原生环境中实现更智能、更高效的运维与架构治理。