作者:隰宗正(霜鍵)
點擊此處,查看視頻演示!
當可觀測數據遇上"關係圖譜"
1.1 從"孤立的實體"到"連接的網絡"
在現代雲原生架構的宏大敍事中,我們習慣於將系統中的每個組件------服務、容器、中間件、基礎設施,視為獨立的"實體"進行監控和管理。我們為它們配置儀表盤,設置告警,追蹤它們的性能指標。然而,這種"個體視角"存在一個根本性的盲點:它忽略了系統最本質的特徵------連接(Connection)。任何一個實體都不是孤立存在的,它們通過調用、依賴、包含等關係,構成了一張複雜巨大、動態變化的"關係圖譜"。
傳統的監控和查詢工具,無論是基於 SQL 還是 SPL,其核心都是處理二維的、表格化的數據。它們擅長回答關於"個體"的問題("這個 Pod 的 CPU 使用率是多少?"),但在回答關於"關係"的問題時卻顯得力不從心。當面對"這個服務的故障會影響哪些下游業務?"或"要訪問到核心數據庫,需要經過哪些中間服務?"這類問題時,傳統工具往往需要複雜的 JOIN 操作、多步查詢,甚至需要工程師結合線下架構圖進行"人腦拼湊"。這種方式不僅效率低下,而且在關係複雜、層級深的情況下幾乎無法完成。我們擁有了所有"點"的數據,卻失去了一張看清"線"的地圖。
1.2 我們的思路:融合圖查詢
面對這一挑戰,我們的解決思路是:將"圖"(Graph)作為可觀測數據模型的重要組成。我們認為,系統的真實形態本就是一張圖,那麼對它的查詢和分析,也應該使用最符合其本質的方式------圖查詢。
為了實現這一點,我們在 UModel 體系的核心構建了 EntityStore。它採用了創新的雙存儲架構,同時維護了 __entity__ 日誌庫(存儲實體的詳細屬性)和 __topo__ 日誌庫(存儲實體間的拓撲關係)。這相當於我們為整個可觀測系統建立了一個實時更新的、可查詢的數字孿生圖譜。
基於這個圖譜,我們提供了從易到難、層層遞進的三種圖查詢能力,以滿足不同用户的需求:
graph-match:為最常見的路徑查詢場景設計,語法直觀,讓用户能像描述一句話一樣("A 經過 B 調用了 C")來快速查找特定鏈路。graph-call:封裝了最高頻的圖算法(如鄰居查找、直接關係查詢),通過函數式接口提供,用户只需關心意圖("找 A 的 3 跳鄰居")而無需關心實現細節。Cypher:引入業界標準的圖查詢語言,提供最完整、最強大的圖查詢能力,支持任意複雜的模式匹配、多級跳躍、聚合分析,是處理複雜圖問題的終極武器。
這一整套解決方案,旨在將強大的圖分析能力,以一種低門檻、工程化的方式,提供給每一位運維和開發工程師。
1.3 核心價值:解鎖系統洞察的新維度
引入圖查詢能力,不僅僅是增加了一種新的查詢語法,更是為系統洞察解鎖了一個全新的維度。
- 全局化的故障影響分析(爆炸半徑分析): 當故障發生時,可以通過一次查詢,快速確定該故障點向下遊輻射的所有可能路徑和受影響的業務範圍,為故障處理的優先級排序和決策提供實時數據支持。
- 端到端的根因溯源: 與影響分析相反,當某個底層服務出現問題時,可以向上游回溯,快速定位是哪個業務或變更觸發了異常,實現精準的根因定位。
- 架構健康度與合規性審計: 可以通過圖查詢來驗證線上系統的實際架構是否與設計相符。例如,查詢是否存在"跨網絡域的非法調用",或者"某個核心數據服務是否被非授權的應用依賴",從而實現架構的持續治理。
- 安全與權限鏈路分析: 在安全審計中,可以追蹤從用户到具體資源的完整訪問路徑,確保每一層權限授予都符合安全規範,防止潛在的數據泄露風險。
總而言之,圖查詢能力將我們對系統的認知從"點的集合"提升到了"結構化的網絡",使得我們能夠基於系統組件之間的真實關係進行提問和分析,從而獲得前所未有的深度洞察力。它是一把鑰匙,開啓了在複雜系統中進行高效故障排查、架構治理和安全審計的大門。
圖查詢相關概念
2.1 相關概念
協作關係:
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,接口更友好。
2.2 總覽
圖查詢基礎概念
在深入使用圖查詢之前,理解其基礎概念至關重要。圖查詢的核心思想是將數據抽象為圖(Graph)結構:實體是節點(Node),關係是邊(Edge)。每個節點都有標籤(Label)和屬性(Properties),標籤用於標識節點的類型,屬性用於存儲節點的詳細信息。同樣,每條邊也有類型(Type)和屬性,類型表示關係的類別,屬性可以存儲關係的額外信息。
3.1 節點和邊的描述語法
在圖查詢中,使用特定的語法來描述節點和邊:
- 節點: 使用小括號
()表示 - 邊: 使用中括號
[]表示 - 描述格式:
<變量名>:<標籤> {屬性鍵值對}
下面是一些基礎語法示例:
// 任意節點
()
// 具有特定標籤的節點
(:"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:在 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)
3.2 路徑語法與方向
圖查詢路徑使用 ASCII 字符描述關係方向:
3.3 返回值結構
在 EntityStore 的體系中,節點的標籤格式為 domain@entity_type,例如 apm@apm.service 表示域為 apm、實體類型為 apm.service 的節點。這種標籤設計不僅清晰地表示了節點的歸屬和類型,還支持基於域的快速過濾和查詢。節點的屬性包括了系統內置的屬性(如 __entity_id__、__domain__、__entity_type__)以及用户在寫入時自定義的屬性(如 servicename、instanceid 等)。邊的類型同樣可以用字符串表示,比如 calls、runs_on、contains 等,每條邊也會攜帶相應的屬性信息。
3.3.1 節點 JSON 格式
{
"id": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
"label": "apm@apm.service",
"properties": {
"__domain__": "apm",
"__entity_type__": "apm.service",
"__entity_id__": "347150ad7eaee43d2bd25d113f567569",
"__label__": "apm@apm.service"
}
}
3.3.2 邊 JSON 格式
{
"startNodeId": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
"endNodeId": "apm@apm.service.host:34f627359470c9d36da593708e9f2db7",
"type": "contains",
"properties": {
"__type__": "contains"
}
}
圖查詢的本質是模式匹配:用户描述一個圖模式(Pattern),系統在圖中查找所有符合該模式的子圖。圖模式可以用路徑表達式來表示,最基礎的路徑表達式就是 (節點)-[邊]->(節點),這表示從源節點通過一條邊到達目標節點。路徑表達式可以擴展為更復雜的模式,比如 (A)-[e1]->(B)-[e2]->(C) 表示從 A 經過 B 到達 C 的兩跳路徑,或者 (A)-[*1..3]->(B) 表示從 A 到 B 的可能的多跳路徑。這種表達方式既直觀又強大,能夠描述從簡單的一對一關係到複雜的多層級網絡路徑。
graph-match:直觀的路徑查詢
graph-match 是圖查詢中最直觀、最容易上手的功能。它的設計哲學是讓用户能夠用接近自然語言的方式描述查詢意圖,然後系統自動執行查詢並返回結果。graph-match 的語法結構相對簡單,主要由路徑描述和結果投影兩部分組成。
graph-match 的核心特點是必須從已知的起始點開始查詢。起始點需要同時指定標籤和 __entity_id__ 屬性,這確保了查詢能夠快速定位到具體的實體。從技術實現的角度看,這種設計是有意為之:圖的遍歷通常是一個指數級複雜度的操作,如果允許從任意模式開始查詢,可能會導致全圖掃描,性能無法保證。而強制指定起始點後,系統可以基於該點進行有向遍歷,將搜索空間限制在可控範圍內。
路徑描述的語法遵循直觀的方向性表達。(A)-[e]->(B) 表示從 A 到 B 的有向邊,(A)<-[e]-(B) 表示從 B 到 A 的有向邊,(A)-[e]-(B) 表示雙向邊(不限制方向)。用户可以為路徑中的每個節點和邊命名變量,這些變量可以在後續的 project 語句中使用。路徑可以連接多個節點和邊,形成多跳路徑,比如 (start)-[e1]->(mid)-[e2]->(end)。
project 語句用於指定返回的內容。用户可以直接返回節點或邊的 JSON 對象,也可以使用點號語法提取特定的屬性,如 "node.__entity_type__"、"edge.__type__ attribution。project 還支持重命名操作,讓返回的字段具有更友好的名稱。這種靈活的輸出方式讓 graph-match 既能滿足快速探索的需求(返回完整對象),也能滿足數據分析的需求(提取特定字段)。
4.1 實際應用案例
4.1.1 全鏈路路徑查詢
查找從特定操作開始的完整調用鏈路:
.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
返回結果:
- s:起始操作節點
- e1.type:第一段關係類型
- v1.label:中間節點標籤
- v2, v3:後續節點信息
4.1.2 鄰居節點統計
統計特定服務的鄰居分佈情況:
.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
4.1.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
4.1.4 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__"
4.1.5 graph-match 限制
儘管 graph-match 非常直觀易用,但它也有一些限制:
graph-call:函數式圖操作
graph-call 提供了一套函數式的圖查詢接口,這些函數封裝了常見的圖操作模式,讓用户能夠更高效地執行特定類型的查詢。graph-call 的設計理念是提供聲明式的函數接口,用户只需指定意圖和參數,具體的遍歷算法由系統優化執行。
getNeighborNodes 是最常用的 graph-call 函數,它用於獲取指定節點的鄰居節點。函數的簽名是 getNeighborNodes(type, depth, nodeList),其中 type 參數控制遍歷的類型,depth 參數控制遍歷的深度,nodeList 參數指定起始節點列表。type 參數的取值包括:sequence(有向序列遍歷,保持邊的方向性)、sequence_in(只返回指向起始節點的路徑)、sequence_out(只返回從起始節點出發的路徑)、full(全方向遍歷,不考慮邊的方向)。這種類型劃分讓用户能夠根據實際需求選擇合適的遍歷策略。
depth 參數控制遍歷的深度,實際使用中建議不要設置過大,一般 3 到 5 層已經足夠覆蓋大多數場景。過深的遍歷不僅會帶來性能問題,返回的結果也可能因為關聯關係過多而失去實際意義。nodeList 參數接受一個節點描述數組,每個節點描述遵循與 graph-match 相同的語法,需要指定標籤和 __entity_id__。getNeighborNodes 會為每個起始節點分別執行遍歷,然後合併結果返回。
getNeighborNodes 的返回結果包含四個字段:srcNode(源節點 JSON)、destNode(目標節點 JSON)、relationType(關係類型)、srcPosition(源節點在路徑中的位置,-1 表示直接鄰居)。srcPosition 字段特別有用,它讓用户能夠區分直接關係和間接關係,在做統計分析時可以按位置分組,瞭解不同層級的關係分佈。
getDirectRelations 函數用於批量查詢節點之間的直接關係。與 getNeighborNodes 不同,getDirectRelations 只返回直接相連的關係,不進行多跳遍歷。這個函數特別適合批量檢查多個已知節點之間的關係,比如檢查一組服務之間是否存在調用關係,或者檢查一組資源之間的依賴關係。函數的參數是一個節點列表,返回結果是關係數組,每個關係包含完整的節點和邊信息。
5.1 實際應用案例
5.1.1 獲取服務的完整鄰居關係
-- 獲取服務的所有鄰居(2跳內)
.topo | graph-call getNeighborNodes(
'full', 2,
[(:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})]
)
| stats cnt=count(1) by relationType
| sort cnt desc
5.1.2 故障上游影響分析
查找可能影響目標服務的上游服務:
.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
5.1.3 故障下游影響分析
查找受目標服務故障影響的下游服務:
.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, '$.id')
| stats impact_count=count(1) by affected_service
| sort impact_count desc
| limit 20
5.1.4 雲資源依賴分析
分析 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
5.1.5 批量查詢節點間的直接關係
.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"}
}
graph-call 的函數式設計帶來的優勢是查詢意圖清晰,系統能夠針對特定模式進行優化。但這也意味着它只適合預定義的查詢模式,對於需要自定義複雜路徑模式的場景,還是需要使用 Cypher。在實際使用中,建議優先考慮 graph-call 的預定義函數,只有當預定義函數無法滿足需求時,再考慮使用更靈活 Cypher。
Cypher:強大的聲明式查詢語言
Cypher 是圖數據庫領域的標準查詢語言,借鑑了 SQL 的易用性和聲明式風格,同時針對圖結構進行了專門優化。在 EntityStore 中,Cypher 提供了最強大和靈活的圖查詢能力,能夠處理從簡單的單節點查詢到複雜的多級跳路徑網絡的各種場景。
Cypher 的語法遵循三段式結構:MATCH、WHERE、RETURN,這與 SQL 的 SELECT、WHERE、FROM 結構類似,但邏輯更符合圖查詢的思維模式。MATCH 子句用於描述圖模式,WHERE 子句用於添加篩選條件,RETURN 子句用於指定返回的內容。這種結構化的語法讓複雜的圖查詢也變得易於閲讀和維護。
MATCH 子句的強大之處在於它支持的圖模式描述。用户可以在 MATCH 中指定任意複雜的路徑模式,包括多級跳、可選路徑、路徑變量等。多級跳的語法是 [*min..max],其中範圍是左閉右開的,比如 [*2..3] 表示只查詢 2 跳路徑。這種語法設計讓用户能夠靈活地控制遍歷深度,在精度和性能之間取得平衡。MATCH 還支持多個路徑模式的組合,用户可以同時描述多個路徑模式,系統會找到所有滿足任一模式的子圖。
WHERE 子句支持豐富的篩選條件。用户可以對節點的屬性、邊的屬性進行各種條件判斷,包括相等、包含、以某字符串開頭或結尾、範圍判斷等。WHERE 子句還支持邏輯組合(AND、OR、NOT)和複雜的表達式。相比 graph-match,Cypher 的 WHERE 子句更加靈活,不僅可以在查詢時進行篩選,還可以對中間節點進行條件限制,這對於複雜路徑模式的查詢特別有用。
RETURN 子句提供了靈活的輸出控制。用户可以返回節點對象、邊對象、路徑對象,也可以提取特定的屬性字段。RETURN 還支持聚合函數(如 count、sum、avg 等)和分組操作,這讓 Cypher 不僅能夠進行圖遍歷,還能夠進行圖分析。結合 SPL 的強大處理能力,Cypher + SPL 的組合能夠完成從數據查詢到分析計算的全流程。
6.1 基礎查詢示例
6.1.1 單節點查詢
-- 查詢特定類型的所有節點
.topo | graph-call cypher(`
MATCH (n {__entity_type__:"apm.service"})
WHERE n.__domain__ STARTS WITH 'a' AND n.__entity_type__ = "apm.service"
RETURN n
`)
相比 graph-match 的優勢:
- 支持 WHERE 子句進行復雜篩選
- MATCH 可以只包含節點,無需指定關係
- 支持更多的屬性查詢(
__entity_type__、__domain__等)
6.1.2 關係查詢
-- 查詢服務間調用關係
.topo | graph-call cypher(`
MATCH (src:``apm@apm.service``)-[e:calls]->(dest:``apm@apm.service``)
WHERE src.cluster = 'production' AND dest.cluster = 'production'
RETURN src.service, dest.service, e.__type__
`)
6.2 多級跳查詢
6.2.1 基礎多級跳語法
-- 查找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__
`)
重要説明:
- 多級跳規則是左閉右開:
*2..4表示查詢 2 跳和 3 跳 *1..3表示 1 跳或 2 跳,不包括 3 跳
6.2.2 連通性分析
-- 查找服務間的可達路徑
.topo | graph-call cypher(`
MATCH (startNode:``apm@apm.service`` {service: 'gateway'})
-[path:calls*1..4]->
(endNode:``apm@apm.service`` {service: 'database'})
RETURN startNode.service, length(path) as hop_count, endNode.service
`)
6.2.3 影響鏈分析
-- 分析故障傳播路徑
.topo | graph-call cypher(`
MATCH (failed:``apm@apm.test_service`` {status: 'error'})
-[impact:depends_on*1..3]->
(affected)
WHERE affected.__entity_type__ = 'apm.service'
RETURN failed.service,
length(impact) as impact_distance,
affected.service
ORDER BY impact_distance ASC
`)
6.2.4 節點聚合統計
-- 統計不同域的服務數量
.topo | graph-call cypher(`
MATCH (src {__entity_type__:"apm.service"})-[e:calls*2..3]->(dest)
WHERE dest.__domain__ = 'apm'
RETURN src, count(src) as connection_count
`)
適用場景:
- 連通分量分析:識別圖中的連通子圖
- 中心度計算:找出網絡中的關鍵節點
- 集羣檢測:發現緊密連接的節點羣組
6.2.5 路徑模式查找
-- 查找特定的拓撲模式
.topo | graph-call cypher(`
MATCH (src:``acs@acs.vpc.vswitch``)-[e1]->(n1)<-[e2]-(n2)-[e3]->(n3)
WHERE NOT (src = n2 AND e1.__type__ = e2.__type__)
AND n1.__entity_type__ <> n3.__entity_type__
AND NOT (src)<-[e1:``calls``]-(n1)
RETURN src, e1.__type__, n1, e2.__type__, n2, e3.__type__, n3
`)
適用場景:
- 安全審計:發現異常的網絡連接模式
- 合規檢查:驗證網絡架構的合規性
- 模式檢測:識別特定的系統拓撲結構
Cypher 的一個重要特性是支持基於實體自定義屬性的查詢。在 graph-match 中,中間節點只能通過標籤進行過濾,但在 Cypher 中,用户可以基於實體的任意自定義屬性進行查詢和篩選。這個特性讓 Cypher 能夠處理更加細粒度的查詢需求,比如查找所有 CPU 使用率大於 80% 的實例,或者查找所有屬於某個特定用户的資源。
6.3 自定義屬性查詢示例
基於實體自定義屬性的查詢是完整版 Cypher 的核心亮點。在標準查詢中,雖然可以通過 Usearch 獲取實體的詳細信息,但在圖遍歷過程中使用實體屬性進行篩選還是有限制的。完整版 Cypher 實現了真正的屬性級查詢,用户可以在 MATCH 或 WHERE 子句中直接使用實體的自定義屬性,系統會自動從 EntityStore 中獲取實體的詳細信息,並基於這些信息進行過濾。這種設計讓圖查詢不再只是基於拓撲結構的遍歷,還能夠基於實體的實際屬性進行智能篩選,大大提升了查詢的精確度。
多級路徑輸出是另一個重要特性。在傳統的圖查詢中,多級跳查詢通常只返回起點和終點,中間的路徑信息可能會丟失。但在故障排查和影響分析場景中,瞭解完整的路徑往往比只知道起點和終點更有價值。完整版 Cypher 支持返回路徑對象,路徑對象包含了路徑中所有節點和邊的信息,用户可以通過路徑對象瞭解數據流轉的完整鏈路。這個特性特別適用於分析故障傳播路徑、追蹤數據流、理解系統架構等場景。
6.3.1 基於實體自定義屬性查詢
-- 使用實體的自定義屬性進行查詢 (僅為示例,實際屬性kv以真實場景為準)
.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
`)
6.3.2 複雜的屬性條件查詢
-- 複雜的屬性條件查詢 (僅為示例,實際屬性kv以真實場景為準)
.topo | graph-call cypher(`
MATCH (instance:``acs@acs.ecs.instance``)
WHERE instance.instance_type STARTS WITH 'ecs.c6'
AND instance.cpu_cores >= 4
AND instance.memory_gb >= 8
AND instance.status = 'Running'
RETURN
instance.instance_id,
instance.instance_type,
instance.cpu_cores,
instance.memory_gb,
instance.availability_zone
ORDER BY instance.cpu_cores DESC, instance.memory_gb DESC
`)
6.4 多級路徑輸出
6.4.1 返回完整路徑信息
-- 返回多級跳的完整路徑信息
.topo | graph-call cypher(`
MATCH (n:``acs@acs.alb.listener``)-[e:``calls``*2..3]-()
RETURN e
`)
路徑結果格式:
- 返回路徑中所有邊的數組
- 每個邊包含完整的起止節點和屬性信息
- 支持路徑長度和路徑權重計算
6.5 細粒度鏈路控制的連通性查找
6.5.1 跨網絡層級的連接分析
-- 查找ECS實例到負載均衡器的連接路徑
.topo | graph-call cypher(`
MATCH (start_node:``acs@acs.ecs.instance``)
-[e*2..3]-
(mid_node {listener_name: 'entity-test-listener-zuozhi'})
-[e2*1..2]-
(end_node:``acs@acs.alb.loadbalancer``)
WHERE start_node.__entity_id__ <> mid_node.__entity_id__
AND start_node.__entity_type__ <> mid_node.__entity_type__
RETURN
start_node.instance_name,
e,
mid_node.__entity_type__,
e2,
end_node.instance_name
`)
6.5.2 服務網格連接分析
-- 分析微服務網格中的流量路徑
.topo | graph-call cypher(`
MATCH (client:``apm@apm.service``)
-[request:calls]->
(gateway:``apm@apm.gateway``)
-[route:routes_to]->
(service:``apm@apm.service``)
-[backend:calls]->
(database:``middleware@database``)
WHERE client.environment = 'production'
AND request.protocol = 'HTTP'
AND route.load_balancer_type = 'round_robin'
RETURN
client.service,
gateway.gateway_name,
service.service,
database.database_name,
request.request_count,
backend.connection_pool_size
`)
6.5.3 級聯故障分析
-- 分析服務故障的級聯影響
.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
`)
典型應用場景
圖查詢在實際運維和分析場景中的應用非常廣泛,以下列舉幾個典型的應用模式,幫助用户更好地理解如何將圖查詢能力應用到實際工作中。
7.1 分析服務調用鏈
-- 分析特定服務的調用模式
.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
7.2 權限鏈追蹤
在複雜的系統中,理解用户的權限是如何傳遞到資源的,對於安全審計和合規檢查至關重要:
-- 追蹤用户到資源的訪問路徑
.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"
7.3 數據完整性檢查
7.3.1 檢查數據完整性
.topo | graph-call cypher(`
MATCH (n)-[e]->(m)
RETURN
count(DISTINCT n) as unique_nodes,
count(DISTINCT e) as unique_edges,
count(DISTINCT e.__type__) as edge_types
`)
7.3.2 識別懸掛關係
-- 查找指向不存在實體的關係
.let topoData = .topo | graph-call cypher(`
MATCH ()-[e]->()
RETURN e
`)
| extend startNodeId = json_extract_scalar(e, '$.startNodeId'), endNodeId = json_extract_scalar(e, '$.endNodeId'), relationType = json_extract_scalar(e, '$.type')
| project startNodeId, endNodeId, relationType;
--$topoData
.let entityData = .entity with(domain='*', type='*')
| project __entity_id__, __entity_type__, __domain__
| extend matchedId = concat(__domain__, '@', __entity_type__, ':', __entity_id__)
| join -kind='left' $topoData on matchedId = $topoData.endNodeId
| project matchedId, startNodeId, endNodeId, relationType
| extend status = COALESCE(startNodeId, '懸掛')
| where status = '懸掛';
$entityData
數據完整性與查詢模式選擇
在使用圖查詢時,數據完整性是一個需要特別關注的問題。EntityStore 的圖查詢能力依賴於三方面的數據:UModel(數據模型定義)、Entity(實體數據)、Topo(拓撲關係數據)。這三方面的數據完整性直接影響了查詢的能力和結果。
8.1 數據缺失場景分析
8.2 pure-topo 模式
需要注意的是,完整版 Cypher 依賴於 UModel、Entity 和 Topo 三方面的數據都要完備。如果 Entity 數據不完整,雖然仍然可以進行拓撲查詢,但無法使用自定義屬性進行篩選。為了解決這個問題,系統提供了 pure-topo 模式:
-- 標準模式(需要完整數據)
.topo | graph-call cypher(`
MATCH (n:``acs@acs.alb.listener`` {ListenerId: 'lsn-123'})-[e]->(d)
WHERE d.vSwitchId CONTAINS 'vsw-456'
RETURN n, e, d
`)
-- pure-topo模式(僅依賴關係數據)
.topo | graph-call cypher(`
MATCH (n:``acs@acs.alb.listener``)-[e]->(d)
RETURN n, e, d
`, 'pure-topo')
pure-topo 模式特點:
- 優勢:不依賴 Entity 數據,查詢速度更快
- 限制:無法使用實體的自定義屬性進行篩選
- 適用:拓撲結構分析、關係驗證等場景
8.3 查詢模式選擇策略
當三方面數據都完整時,用户可以使用完整版 Cypher 的所有功能,包括基於自定義屬性的查詢、多級路徑輸出等。當 Entity 數據不完整但 Topo 數據完整時,可以使用 pure-topo 模式進行查詢,這種模式下查詢速度會更快,但只能基於拓撲結構進行查詢,無法使用實體屬性進行篩選。當 Topo 數據不完整時,雖然 Entity 數據完整,也無法進行圖查詢,因為圖查詢的核心是關係,沒有關係數據就無法構成圖。
在實際使用中,用户應該根據數據的完整性情況選擇合適的查詢方式。如果數據完整性足夠,優先使用完整版 Cypher,享受屬性級查詢的便利。如果性能是首要考慮,且只需要拓撲結構信息,可以使用 pure-topo 模式。如果需要進行數據完整性檢查,可以先使用簡單的查詢測試數據的完整性,然後再執行復雜的查詢。
性能優化與最佳實踐
圖查詢雖然強大,但在大數據量的情況下,性能也可能成為瓶頸。合理的使用方法和優化策略能夠顯著提升查詢性能,確保系統在高負載下也能穩定響應。
9.1 查詢結構優化
9.1.1 合理使用索引
-- ❌ 優化前:全表掃描
.topo | graph-call cypher(`
MATCH (n) WHERE n.service = 'web-app'
RETURN n
`)
-- ✅ 優化後:使用標籤索引
.topo | graph-call cypher(`
MATCH (n:``apm@apm.service`` {service: 'web-app'})
RETURN n
`)
9.1.2 早期條件過濾
-- ❌ 優化前:後期過濾
.topo | graph-call cypher(`
MATCH (start)-[*1..5]->(endNode)
WHERE start.environment = 'production' AND endNode.status = 'active'
RETURN start, endNode
`)
-- ✅ 優化後:早期過濾
.topo | graph-call cypher(`
MATCH (start {environment: 'production'})-[*1..5]->(endNode {status: 'active'})
RETURN start, endNode
`)
9.2 查詢範圍控制
查詢範圍的精確控制是最重要的優化策略:
- 時間範圍優化:合理利用時間字段進行範圍限制
- 限制遍歷深度:深度超過 5 層會顯著影響性能
- 精確起始點:使用具體的 entity_id 而非模糊匹配
- 合理選擇遍歷類型:根據實際需求選擇 sequence 或 full
9.3 結果集控制
9.3.1 分頁和限制
-- 使用LIMIT控制結果數量
.topo | graph-call cypher(`
MATCH (service:``apm@apm.service``)-[calls:calls]->(target)
WHERE calls.request_count > 1000
RETURN service.service, target.service, calls.request_count
ORDER BY calls.request_count DESC
LIMIT 50
`)
9.3.2 結果採樣
-- 對大結果集進行採樣
.topo | graph-call cypher(`
MATCH (n:``apm@apm.service``)
RETURN n.service
LIMIT 100
`)
| extend seed = random()
| where seed < 0.1
9.4 多級跳優化
9.4.1 控制跳躍深度
-- 避免過深的遍歷
.topo | graph-call cypher(`
MATCH (start)-[path*1..3]->(endNode)
WHERE length(path) <= 2
RETURN path
`)
9.4.2 使用方向性優化
-- 利用關係方向減少搜索空間
.topo | graph-call cypher(`
MATCH (start)-[calls:calls*1..3]->(endNode) -- 明確方向
WHERE start.__entity_type__ = 'apm.service'
RETURN start, endNode
`)
9.5 最佳實踐建議
- 使用 SPL 過濾:在圖查詢後及時過濾不需要的結果
- 分批處理:對於大型圖查詢,考慮分批處理
- 結果緩存:對於頻繁查詢的路徑,考慮結果緩存
- 查詢拆分:將複雜查詢拆分為多個簡單查詢,然後使用 SPL 合併
常見問題
10.1 邊類型恰好與 Cypher 關鍵字重合
.topo | graph-call cypher(`
MATCH (s)-[e:``contains``]->(d)
WHERE s.__domain__ CONTAINS "apm"
RETURN e
`)
contains 是 cypher 關鍵字,同時也是邊類型,此時作為 Cypher 語法需要在邊類型上面加入 back-tick 標識進行包裹,又因為在 SPL 上下文中,所以作為 SPL 語法需要變為雙 back-tick 標識進行包裹。
10.2 多級跳語法説明
-- 查找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__
`)
重要説明:
- 多級跳規則是左閉右開:
*2..4表示查詢 2 跳和 3 跳 *1..3表示 1 跳或 2 跳,不包括 3 跳
驗證該結論:
.topo | graph-call cypher(`
MATCH (s)-[e*1..3]->(d)
RETURN length(e) as len
`, 'pure-topo')
| stats cnt=count(1) by len
| project len, cnt
10.3 不支持簡寫 Cypher 關係
✅ 支持的寫法:
.topo | graph-call cypher(`
MATCH (s)-[]->(d)
RETURN s
`, 'pure-topo')
❌ 不支持的寫法:
.topo | graph-call cypher(`
MATCH (s)-->(d)
RETURN s
`, 'pure-topo')
點擊此處查看視頻演示。